Stand-alone приложение RAP

в 22:50, , рубрики: ajax, eclipse, equinox, java, osgi, rap, Песочница, метки: , , ,

Приветствую всех! Это моя первая статья, потому просьба отнестить с пониманием.

С тех пор как я впервые увидел 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

Источник

Поделиться

* - обязательные к заполнению поля