Приветствую всех! Это моя первая статья, потому просьба отнестить с пониманием.
С тех пор как я впервые увидел RAP и OSGi — прошел год, но с первого взгляда я просто влюбился в эти технологии. К сожалению даже в сети очень мало документации по RAP, позволяющей написать что-то крутое (кроме hello world) с нуля. Для начала, конечно, нужно знать что такое OSGi. По этой теме инфы в сети достаточно — можно загуглить. Так как статья о RAP, подразумевается, что читатель уже знает как создавать OSGi бандлы, как их устанавливать и запускать.
То есть задача ставится так: «сделать кастомный интерфейс сайта заюзав RAP». Как создать в Eclipse проект OSGi бандла, читатель, я предполагаю, знает.
Итак. Практически все примеры использования RAP базируются, видимо, на одном приципе — никто никогда не будет использовать ничего кроме workbench. Спору нет, воркбенч замечателен в бизнес-приложениях, когда нужно оперировать табличными данными и интерфейс не должен быть шибко интуитивным и гибким. Однако, когда задача звучит как «сделать сайт с использованием RAP» — воркбенч уже не подходит.
Для начала нужно сделать точку вход в приложение. Причем без использования Declarative Services. В этом деле и конкретно в этом случае DS — зло. Сразу скажу, что в моем приложении еще есть MVC, где Application является View, есть еще и usecase концепция… Такое «безобразие» вообще с помощью DS сделать нельзя. Но мы пойдем по упрощенному пути, для начала.
Создаем абстрактный класс точки входа в приложение, чтобы потом унаследовать от него наше приложение:
abstract public class ApplicationEntryPoint implements EntryPoint {
private ApplicationSession mSession;
private ApplicationWindow mApplicationWindow;
private ApplicationController mController;
private String mDeferredUsecaseRun;
public ApplicationEntryPoint(UseCaseRegister usecaseRegister) {
mSession = new ApplicationSession();
}
public ApplicationSession getSession() {
return mSession;
}
protected Shell createMainShell( Display display ) {
Shell shell = new Shell(display, SWT.NO_TRIM);
shell.setMaximized(true);
shell.setData(RWT.CUSTOM_VARIANT, "mainshell");
shell.setLayout(new FillLayout());
return shell;
}
protected void clearShell(Shell shell) {
Control[] controls = shell.getChildren();
for (int i = 0; i < controls.length; i++) {
if(controls[i] != null) {
controls[i].dispose();
}
}
}
}
Вот такой упрощенный класс точки входа. Нам интересны два метода: createMainShell — который создает Shell — в терминах SWT это главное окно приложения, а у нас — это страница в броузере. И clearShell — воторый просто удаляет все, что есть на странице, то есть в главном окне приложения. Если нужно показать другое содержимое — просто уничтожаем все, что было в шелле и наполняем новыми данными.
Далее создаем ApplicationConfig:
public class ApplicationConfig implements ApplicationConfiguration {
public ApplicationConfig() {}
public void configure( Application application ) {
application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
application.addResource("/images/16/help2.png", new ResourceLoader() {
@Override
public InputStream getResourceAsStream(String arg0)
throws IOException {
return
ApplicationConfig.class.
GetClassLoader().
getResourceAsStream("/images/16/help2.png");
}
});
Map<String, String> properties = new HashMap<String, String>();
properties.put( WebClient.FAVICON, "/images/16/help2.png" );
properties.put( WebClient.PAGE_TITLE, "Public area" );
application.addEntryPoint("/public",
new PublicAreaEntryPointFactory(),
properties);
properties = new HashMap<String, String>();
properties.put( WebClient.PAGE_TITLE, "Main area" );
application.addEntryPoint("/main",
new MainApplicationEntryPointFactory(),
properties);
}
}
Здесь подразумевается, что можно сконфигурировать несколько областей, досупных по разным URI: в данном случае /public и /main. У каждой области можно задать свой favicon и заголовок страницы. Приложение в RWT создается посредством фабрики, которую мы сейчас и сделаем.
abstract public class ApplicationEntryPointFactory implements EntryPointFactory {
public ApplicationEntryPointFactory() {}
public EntryPoint create() {
return null;
}
}
Это абстрактная фабрика, мало ли что, пригодится. Чем потом перекраивать все приложение — сделаем сразу. Скажу, что мне — пригодилась.
Чуть не забыли про класс сессии приложения, который как раз испоьзуется насквозь, во всем приложении:
final public class ApplicationSession {
private PersistentManager mPersistent;
private HttpSession mHttpSession;
private String mLogin = "";
private Boolean mLoggedIn = false;
private Locale mLocale = null;
private User mUser;
private Logger mLogger;
public ApplicationSession() {
mLocale = new Locale("ru", "RU");
mHttpSession = RWT.getUISession().getHttpSession();
mPersistent = new PersistentManager("mo");
/*
* Восстанавливаем данные из сессии в объект.
*/
if (mHttpSession.getAttribute("login") != null)
mLogin = (String) mHttpSession.getAttribute("login");
if (mHttpSession.getAttribute("user") != null)
mUser = (User) mHttpSession.getAttribute("user");
mLogger = LoggerFactory.getLogger(ApplicationSession.class);
}
final public void login(User user, String login) {
mLogin = login;
mPersistent.setUser(user);
mHttpSession.setAttribute("login", mLogin);
mHttpSession.setAttribute("user", user);
logger().debug("Начата сессия пользователя {}", mLogin);
}
final public Logger logger() {
return mLogger;
}
final public void setUser(User user) {
mUser = user;
mHttpSession.setAttribute("user", user);
}
final public User getUser() {
return mUser;
}
final public PersistentManager persistent() {
return mPersistent;
}
final public String getId() {
return mHttpSession.getId();
}
final public Locale getLocale() {
return mLocale;
}
final public void setLanguage(String language) {
if (language.toUpperCase().equals("RUSSIAN") ||
language.toUpperCase().equals("RU")) {
mLocale = new Locale("ru", "RU");
} else {
mLocale = new Locale("en", "EN");
}
}
}
Как видно, в сессии мы храним все что только можно. Тут и ORM и ссылка на HTTP-сессию и объект пользователя User и локаль поьзователя и логгер… В общем, все что нужно лично вам внутри любой точки в приложении — можно хранить в сессии. У меня она доступна везде, даже и других бандлов приложения.
Ну а дальше, реализуем наш абстрактный EntryPoint:
/**
* Точка входа в приложение с авторизованным доступом.
* Если сессия пользователя не имеет авторизации - то выполняется
* юзкейс авторизации в публичной области.
*/
public class MainApplicationEntryPointFactory extends ApplicationEntryPointFactory {
public MainApplicationEntryPointFactory() {
super();
}
@Override
public EntryPoint create() {
MainApplicationEntryPoint mainApp =
new MainApplicationEntryPoint(getUsecaseRegister());
return mainApp;
}
}
/**
* Точка входа в основное приложение, содержит объект контроллера приложения.
* Этот класс является началом приложеиня с точки зрения пользователя и также
* привязан к единственному URI, по которому он доступен.
*/
public class MainApplicationEntryPoint extends ApplicationEntryPoint {
private Shell mShell;
private CommonController mLoginController;
private ApplicationController mCtrl;
public MainApplicationEntryPoint() {
super();
}
@Override
public int createUI() {
Display display = new Display();
mShell = createMainShell( display ); // Создаем основной шелл
try {
if (getSession().getUser() != null) {
// Создаем контроллер приложения с композитом
// главного окна приложения.
mCtrl = new MainApplicationController(getSession(), mShell);
mCtrl.init();
} else {
mCtrl = new PublicAreaController(getSession(), mShell);
mCtrl.init();
} catch (Exception ex) {
MessageDialog.openError(mShell, "Ошибка!", ex.getMessage());
}
// Открываем шелл, контроллер уже создал композит при инициализации,
// так что он просто будет показан.
mShell.open(); // Открываем шелл
while( !mShell.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
display.dispose();
return 0;
}
}
Я уже писал, что у меня MVC, поэтому все что внутри главного окна создается уже посредством соответствующих контроллеров. Для публичной области и для приложения — свой контроллер для каждой. Но это — то, что работает уже внутри приложения.
В общем и в целом, Shell это такой же Composite и им можно пользоваться упаковывая в него контролы и другие композиты. Если в нем ничего не создавать, то мы получим пустой шелл. Принцип работы с ним можно подчерпнуть из литературы по SWT. Но, что важно, это будет приложение доступное по определенному URI.
Что еще важнее, мы теперь не привязаны к той архитектуре, которую дает RWT workbench. Однако, для того, чтобы воспользоваться ресурсами (например картинками или внешними JS) нужно изменить контекст приложения на свой. Я приведу еще свой пример активатора бандла приложения:
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
/*
* Регистрируем само приложение
*/
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("contextName", "app");
context.registerService(ApplicationConfiguration.class.getName(),
new ApplicationConfig(), props);
}
public void stop(BundleContext context) throws Exception {
}
}
Таким образом приложение будет доступно через контекст, который будет участвовать в URI: /app/public или /app/main.
PS. Код, который я приводил, является примерным, концептуальным. Тупо скопировать его и ожидать, что оно заработает — не получится. Однако, если бы у меня был такой мануал в свое время, можно было бы сэкономить кучу времени.
Автор: futurelink