Stand-alone RAP application

Welcome all! This is my first article, so please attribute it with understanding.

Since the first time I saw RAP and OSGi, a year has passed, but at first glance I just fell in love with these technologies. Unfortunately, even on the web there is very little documentation on RAP that allows you to write something cool (except hello world) from scratch. First, of course, you need to know what OSGi is. On this topic, information on the network is enough - you can google it. Since the article is about RAP, it is understood that the reader already knows how to create OSGi bundles, how to install and run them.

That is, the task is set as follows: "make a custom interface for the site using RAP." How to create an OSGi bundle project in Eclipse, the reader, I suppose, knows.

So. Almost all examples of using RAP are based, apparently, on one principle - no one will ever use anything other than a workbench. No doubt, the workbench is great in business applications, when you need to operate on tabular data and the interface does not have to be very intuitive and flexible. However, when the task sounds like “make a site using RAP”, the workbench is no longer suitable.

First you need to make an entry point to the application. And without using Declarative Services. In this case, and specifically in this case, DS is evil. I must say right away that in my application there is still MVC, where Application is View, there is also a usecase concept ... Such a “disgrace” cannot be done with DS at all. But we will go along a simplified path, for starters.

We create an abstract class of the entry point to the application, so that we can later inherit our application from it:

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();
			}
		}		
	}
}


Here is such a simplified entry point class. We are interested in two methods: createMainShell - which Shell creates - in terms of SWT, this is the main application window, and for us, this is the page in the browser. And clearShell - which simply deletes everything on the page, that is, in the main application window. If you need to show other content, we simply destroy everything that was in the shell and fill it with new data.

Next, create an 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 properties = new HashMap();
		properties.put( WebClient.FAVICON, "/images/16/help2.png" );
		properties.put( WebClient.PAGE_TITLE, "Public area" );	
		application.addEntryPoint("/public", 
				new PublicAreaEntryPointFactory(), 
				properties);
		properties = new HashMap();
		properties.put( WebClient.PAGE_TITLE, "Main area" );
		application.addEntryPoint("/main", 
				new MainApplicationEntryPointFactory(), 
				properties);
	}  
}


This implies that you can configure several areas that are accessible by different URIs: in this case / public and / main. Each area can have its own favicon and page title. The application in RWT is created through the factory, which we will do now.

abstract public class ApplicationEntryPointFactory implements EntryPointFactory {
	public ApplicationEntryPointFactory() {}	
	public EntryPoint create() {
		return null;
	}
}


This is an abstract factory, you never know, come in handy. Than then redraw the entire application - do it right away. I’ll say that it came in handy.

We almost forgot about the application session class, which is used through and through, throughout the application:

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");
		}
	}
}


As you can see, in the session we store everything that is possible. There is ORM and a link to the HTTP session and the User object and the user locale and logger ... In general, all that you personally need inside any point in the application can be stored in the session. I have it available everywhere, even other application bundles.

Well, then, we implement our abstract 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;
	}
}


I already wrote that I have MVC, therefore everything that is created inside the main window is created using the appropriate controllers. For the public domain and for the application - its own controller for each. But this is what works already inside the application.

In general, Shell is the same Composite, and you can use it by packing controls and other composites into it. If you do not create anything in it, then we will get an empty shell. The principle of working with it can be drawn from the literature on SWT. But, importantly, this will be an application available at a specific URI.

More importantly, we are no longer tied to the architecture that the RWT workbench provides. However, in order to use resources (for example, pictures or external JS), you need to change the application context to your own. I will give my example of an application bundle activator:

public class Activator implements BundleActivator {
	public void start(BundleContext context) throws Exception {
		/*
		 *  Регистрируем само приложение
		 */
		Dictionary props = new Hashtable();
		props.put("contextName", "app");		
		context.registerService(ApplicationConfiguration.class.getName(), 
			new ApplicationConfig(), props);		
	}
	public void stop(BundleContext context) throws Exception {
	}
}


Thus, the application will be accessible through the context that will participate in the URI: / app / public or / app / main.

PS. The code that I cited is approximate, conceptual. Stupidly copy it and expect that it will work - will not work. However, if I had such a manual in due time, it would be possible to save a lot of time.

PPS For comrades who want to get to know each other better. RAP is the former Rich Ajax Platform, now the Remote Application Platform. RAP is a development of the Eclipse Foundation and is a Java toolkit for creating a thin browser client. Information on the usefulness and features of this project is available at eclipse.org/rap. One of the main features is the open JSON-based protocol for exchanging data between the client (browser) and server. Second, RWT redefines SWT interfaces. That is, almost everything that is true for SWT can be ported, and this is a very big opportunity for effective code-reusage. Theoretically, you can run the same application by attaching different classpaths to the classpath as a service, with a client as a browser or as a desktop Java application. And this is with minimal code changes. GWT performs almost similar functions, though not so fundamentally defining the architecture for building the interface.

Also popular now: