Приветствую всех! Это моя первая статья, потому просьба отнестить с пониманием.
С тех пор как я впервые увидел RAP и OSGi — прошел год, но с первого взгляда я просто влюбился в эти технологии. К сожалению даже в сети очень мало документации по RAP, позволяющей написать что-то крутое (кроме hello world) с нуля. Для начала, конечно, нужно знать что такое OSGi. По этой теме инфы в сети достаточно — можно загуглить. Так как статья о RAP, подразумевается, что читатель уже знает как создавать OSGi бандлы, как их устанавливать и запускать.
То есть задача ставится так: «сделать кастомный интерфейс сайта заюзав RAP». Как создать в Eclipse проект OSGi бандла, читатель, я предполагаю, знает.
Итак. Практически все примеры использования RAP базируются, видимо, на одном приципе — никто никогда не будет использовать ничего кроме workbench. Спору нет, воркбенч замечателен в бизнес-приложениях, когда нужно оперировать табличными данными и интерфейс не должен быть шибко интуитивным и гибким. Однако, когда задача звучит как «сделать сайт с использованием RAP» — воркбенч уже не подходит.
Для начала нужно сделать точку вход в приложение. Причем без использования Declarative Services. В этом деле и конкретно в этом случае DS — зло. Сразу скажу, что в моем приложении еще есть MVC, где Application является View, есть еще и usecase концепция… Такое «безобразие» вообще с помощью DS сделать нельзя. Но мы пойдем по упрощенному пути, для начала.
Создаем абстрактный класс точки входа в приложение, чтобы потом унаследовать от него наше приложение:
Вот такой упрощенный класс точки входа. Нам интересны два метода: createMainShell — который создает Shell — в терминах SWT это главное окно приложения, а у нас — это страница в броузере. И clearShell — воторый просто удаляет все, что есть на странице, то есть в главном окне приложения. Если нужно показать другое содержимое — просто уничтожаем все, что было в шелле и наполняем новыми данными.
Далее создаем ApplicationConfig:
Здесь подразумевается, что можно сконфигурировать несколько областей, досупных по разным URI: в данном случае /public и /main. У каждой области можно задать свой favicon и заголовок страницы. Приложение в RWT создается посредством фабрики, которую мы сейчас и сделаем.
Это абстрактная фабрика, мало ли что, пригодится. Чем потом перекраивать все приложение — сделаем сразу. Скажу, что мне — пригодилась.
Чуть не забыли про класс сессии приложения, который как раз испоьзуется насквозь, во всем приложении:
Как видно, в сессии мы храним все что только можно. Тут и ORM и ссылка на HTTP-сессию и объект пользователя User и локаль поьзователя и логгер… В общем, все что нужно лично вам внутри любой точки в приложении — можно хранить в сессии. У меня она доступна везде, даже и других бандлов приложения.
Ну а дальше, реализуем наш абстрактный EntryPoint:
Я уже писал, что у меня MVC, поэтому все что внутри главного окна создается уже посредством соответствующих контроллеров. Для публичной области и для приложения — свой контроллер для каждой. Но это — то, что работает уже внутри приложения.
В общем и в целом, Shell это такой же Composite и им можно пользоваться упаковывая в него контролы и другие композиты. Если в нем ничего не создавать, то мы получим пустой шелл. Принцип работы с ним можно подчерпнуть из литературы по SWT. Но, что важно, это будет приложение доступное по определенному URI.
Что еще важнее, мы теперь не привязаны к той архитектуре, которую дает RWT workbench. Однако, для того, чтобы воспользоваться ресурсами (например картинками или внешними JS) нужно изменить контекст приложения на свой. Я приведу еще свой пример активатора бандла приложения:
Таким образом приложение будет доступно через контекст, который будет участвовать в URI: /app/public или /app/main.
PS. Код, который я приводил, является примерным, концептуальным. Тупо скопировать его и ожидать, что оно заработает — не получится. Однако, если бы у меня был такой мануал в свое время, можно было бы сэкономить кучу времени.
PPS. Для товарищей, которые хотят познакомиться поближе. RAP — бывшая Rich Ajax Platform, ныне Remote Application Platform. RAP является разработкой Eclipse Foundation и представляет собой Java тулкит для создания тонкого броузерного клиента. Информация о полезности и фичах этого проекта доступно изложена на eclipse.org/rap. Одной из главных особенностей является открытый JSON-based протокол обмена данными между клиентом (броузером) и сервером. Второй — RWT переопределяет интерфейсы SWT. То есть почти все, что верно для SWT — можно портировать, а это очень большая возмжность для эффективного code-reusage. Теоретически, можно одно и то же приложение, прилагая в classpath разные либы, запускать как сервис, с клиентом в виде броузера или как десктопное приложение Java. И это с минимальными изменениями кода. Почти аналогичные функции, правда не столь фундаментально задающие архитектуру построения интерфейса, выполняет GWT.
С тех пор как я впервые увидел 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. Код, который я приводил, является примерным, концептуальным. Тупо скопировать его и ожидать, что оно заработает — не получится. Однако, если бы у меня был такой мануал в свое время, можно было бы сэкономить кучу времени.
PPS. Для товарищей, которые хотят познакомиться поближе. RAP — бывшая Rich Ajax Platform, ныне Remote Application Platform. RAP является разработкой Eclipse Foundation и представляет собой Java тулкит для создания тонкого броузерного клиента. Информация о полезности и фичах этого проекта доступно изложена на eclipse.org/rap. Одной из главных особенностей является открытый JSON-based протокол обмена данными между клиентом (броузером) и сервером. Второй — RWT переопределяет интерфейсы SWT. То есть почти все, что верно для SWT — можно портировать, а это очень большая возмжность для эффективного code-reusage. Теоретически, можно одно и то же приложение, прилагая в classpath разные либы, запускать как сервис, с клиентом в виде броузера или как десктопное приложение Java. И это с минимальными изменениями кода. Почти аналогичные функции, правда не столь фундаментально задающие архитектуру построения интерфейса, выполняет GWT.
