Vaadin

Join Vaadin Log In

Creating a Modular Vaadin Application with OSGi

by Petter Holmström

Introduction

One of the strengths of rich client platforms such as Eclipse or NetBeans is their modular design. Additional features can be added just by installing a plugin and sometimes you do not even have to restart the application. Wouldn't it be cool to have something like this for Vaadin applications as well? With GlassFish 3, Sun's latest JEE application server, it is possible.

In this article, we are going to create a modular Vaadin application, where UI modules can be added and removed on the fly without having to redeploy or even restart the application. We are going to do this by utilizing OSGi and Declarative Services, which are supported by GlassFish 3. When finished, the application will look something like this:

It may not look very fancy – the main window consists of a tab sheet only – but every tab is in fact loaded from a separate module. As new modules are deployed to the server, more tabs show up in the window. Likewise, when a module is undeployed, the tab disappears. And all of this without having to redeploy or even restart the main application.

Prerequisites

In order to fully understand this article, you should be familiar with JEE and Vaadin development. Some basic knowledge of OSGi is also recommended. It might also be a good idea to read throuh Hello GlassFish 3 before continuing with this article.

If you want to try out the code in this article you should get the latest version of GlassFish 3 (build 67 was used for this article) and Apache Ant 1.7. You also need to download the source code(info). Note, that you have to edit the build.xml file to point to the correct location of the GlassFish installation directory before you can use it!

The Application Architecture

The architecture of the application is the following:

The most important part of the application is the module service. The module service runs in the OSGi container and is responsible for keeping track of the installed modules. Whenever a new module is added or an existing one removed, the module service will get notified and take appropriate action.

On the front end, we have a Vaadin application running in an ordinary JEE web container, completely unaware of the existence of OSGi. It is, however, aware of the module service and is notified every time a module is added or removed. Thus, it can add or remove tabs accordingly.

The modules are OSGi bundles that register themselves with the module service when they are started. Likewise, they unregister themselves when they are stopped.

The Module Service

We are going to begin with the module service. As it is going to be exported as a declarative OSGi service, it needs to have an interface:

#!java
public interface ModuleService {

	public void registerModule(Module module);
	
	public void unregisterModule(Module module);
	
	public List<Module> getModules();
	
	public void addListener(ModuleServiceListener listener);
	
	public void removeListener(ModuleServiceListener listener);	
}

We also need an interface that all the modules are to implement:

#!java
public interface Module {

	public String getName();
	
	public Component getComponent();
}

In this simple example, a module is only expected to provide a name and a Vaadin component that is to be added to the tab sheet in the front-end application.

Finally, we need an interface that the module service listeners should implement:

#!java
public interface ModuleServiceListener {

	public void moduleRegistered(ModuleService source, Module module);
	
	public void moduleUnregistered(ModuleService source, Module module);
}

Next, we move on to the implementation of the ModuleService interface:

#!java
public class ModuleServiceImpl implements ModuleService {

	private ArrayList<Module> modules = new ArrayList<Module>();
	
	private ArrayList<ModuleServiceListener> listeners = new ArrayList<ModuleServiceListener>();

	@SuppressWarnings("unchecked")
	public synchronized void registerModule(Module module) {
		modules.add(module);
		for (ModuleServiceListener listener : (ArrayList<ModuleServiceListener>) listeners.clone()) {
			listener.moduleRegistered(this, module);
		}
	}
	
	@SuppressWarnings("unchecked")
	public synchronized void unregisterModule(Module module) {
		modules.remove(module);
		for (ModuleServiceListener listener : (ArrayList<ModuleServiceListener>) listeners.clone()) {
			listener.moduleUnregistered(this, module);
		}
	}
	
	public List<Module> getModules() {
		return Collections.unmodifiableList(modules);
	}
	
	public synchronized void addListener(ModuleServiceListener listener) {
		listeners.add(listener);
	}
	
	public synchronized void removeListener(ModuleServiceListener listener) {
		listeners.remove(listener);
	}
}

The implementation should be pretty easy to understand. When a module is registered it is added to the modules list and the listeners are notified. Likewise, when a module is unregistered it is removed from the list and the listeners are notified. The reason why the methods iterate over a clone of the listeners list instead of the list itself is that it prevents strange things from happening if addListener(..) or removeListener(..) is called by one of the iterated listeners.

This is all the code that goes into the module service bundle. However, before we are done, we have to create two configuration files. Firstly, we need to create an OSGi service definition file:

#!xml
<?xml version="1.0"?>
<component name="vaadin-moduleService" immediate="true">
	<implementation class="moduledemo.services.impl.ModuleServiceImpl"/>
	<service>
		<provide interface="moduledemo.services.ModuleService"/>
	</service>
</component>

In short, this service definition tells the OSGi container that the bundle exports a service named vaadin-moduleService and that it provides an implementation of the moduledemo.services.ModuleService interface in the class moduledemo.services.impl.ModuleServiceImpl.

Secondly, we have to create the OSGi manifest:

Bundle-ManifestVersion: 2
Bundle-Name: Module Demo module manager
Bundle-Vendor: IT Mill Ltd
Bundle-Version: 1.0.0
Bundle-SymbolicName: moduledemo.services
Export-Package: moduledemo.services
Service-Component: OSGI-INF/moduleservice.xml
Import-Package: com.vaadin.ui

In short, this manifest tells the OSGi container that the bundle exports the package moduledemo.services, requires the package com.vaadin.ui and provides a service definition in the file OSGI-INF/moduleservice.xml.

The Front-end Application

Now, we move on to the front-end application. As mentioned previously, the application will contain a tab sheet, where each tab corresponds to a module. The code looks like this:

#!java
public class ModuleDemoApp extends Application implements ModuleServiceListener {
	
	private ModuleService moduleService;
	
	public ModuleDemoApp(ModuleService moduleService) {
		this.moduleService = moduleService;
	}
	
	private TabSheet tabs;
	
	@Override
	public void init() {
		tabs = new TabSheet();
		tabs.setSizeFull();		
		for (Module module : moduleService.getModules()) {
			tabs.addTab(module.getComponent(), module.getName(), null);
		}		
		setMainWindow(new Window("Module Demo Application", tabs));
		moduleService.addListener(this);
	}
	
	@Override
	public void close() {
		moduleService.removeListener(this);
		super.close();
	}
	
	public void moduleRegistered(ModuleService source, Module module) {
		tabs.addTab(module.getComponent(), module.getName(), null);
	}
	
	public void moduleUnregistered(ModuleService source, Module module) {
		tabs.removeComponent(module.getComponent());
	}

}

The main application class ModuleDemoApp implements the ModuleServiceListener. Upon startup, it registers itself with the module service instance injected as a constructor parameter, and upon shutdown it unregisters itself. Every time a module is registered, it is added to the tab sheet and when a module is unregistered, it is removed.

If you have developed Vaadin applications before, you are probably used to configuring the ApplicationServlet class in the web.xml file. However, in this example we are going create a custom servlet and use the new Servlet 3.0 @WebServlet annotation for configuring it. Thanks to this, we no longer need a web.xml descriptor file.

The servlet class can be added as an inner class of the main application class:

#!java
@WebServlet(urlPatterns="/*")
public static class Servlet extends AbstractApplicationServlet {

	@Resource(mappedName="vaadin-moduleService")
	ModuleService moduleService;

	@Override
	protected Class<? extends Application> getApplicationClass() {
		return ModuleDemoApp.class;
	}

	@Override
	protected Application getNewApplication(HttpServletRequest request)	throws ServletException {
		return new ModuleDemoApp(moduleService);
	}
}

Note, that the servlet has a moduleService field annotated with the @Resource annotation. One of the interesting features of GlassFish 3 is that it is possible to inject references to OSGi services into all container managed Java beans, even though they are not actually running in the OSGi container themselves. Thus, in this case, GlassFish will find the module service we define in the previous section and inject it.

We have now written all the code needed for our new modular Vaadin UI platform. Let's create a few modules to try it out!

The Modules

For this example, we are going to create two very simple modules, module 1 and module 2. Their implementations are almost equal – the only difference is the numbers in their names. Therefore, we are only going to look at module 1 (module 2 is included in the source code archive):

#!java
public class Module1 extends CustomComponent implements Module {

	public Module1() {
		setCompositionRoot(new Label("Hello, this is Module 1"));
	}

	public String getName() {
		return "Module 1";
	}
	
	public Component getComponent() {
		return this;
	}
	
	public void setModuleService(ModuleService service) {
		service.registerModule(this);
	}
	
	public void unsetModuleService(ModuleService service) {
		service.unregisterModule(this);
	}
}

The module class is itself a custom Vaadin component consisting of a single label. The setModuleService(..) method is used by the OSGi container to inject the module service instance when the module starts. Likewise, unsetModuleService(..) is called by the OSGi container when the module is stopped. However, to get this to work, we need to make the OSGi container aware of the presence of these methods. We do this by creating another service definition file:

#!xml
<?xml version="1.0"?>
<component name="vaadin-module1">
	<implementation class="moduledemo.module1.Module1"/>
	<reference name="vaadin-moduleService"
		interface="moduledemo.services.ModuleService"
		bind="setModuleService"
		unbind="unsetModuleService"
		cardinality="1..1"
		policy="dynamic"/>
</component>

In short, this service definition tells the OSGi container to instantiate the moduledemo.module1.Module1 class, inject the "vaadin-moduleService" using the setModuleService method when the bundle is started and "eject" the service using the unsetModuleService when the bundle is stopped. Note, that in this case we do not provide any services – we only reference an existing service.

Finally, we have to create the manifest:

Bundle-ManifestVersion: 2
Bundle-Name: Module Demo module 1
Bundle-Vendor: IT Mill Ltd
Bundle-Version: 1.0.0
Bundle-SymbolicName: moduledemo.module1
Export-Package: moduledemo.module1
Service-Component: OSGI-INF/module1.xml
Import-Package: com.vaadin.ui, moduledemo.services

In short, this manifest tells the OSGi container that the bundle exports the package moduledemo.module1, requires the packages com.vaadin.ui and moduledemo.services, and provides a service definition in the file OSGI-INF/module1.xml.

Deployment

Before we can deploy the application, we have to package it. The final application will consist of three OSGi bundles (the module service and both modules) and an ordinary WAR (the front-end application). To package the application, we use the Ant build script that comes in the source code archive:

$ ant all

This will create the files moduledemo.services.jar, moduledemo.war, moduledemo.module1.jar and moduledemo.module2.jar in the moduledemo/dist directory.

Before deploying the application bundles, we have to deploy the Vaadin library. Currently, Vaadin does not have OSGi support, although support is planned for version 6.2 (as of October 21, 2009 the nightly builds have OSGi support). However, the Vaadin library that comes in the source code archive has an OSGi manifest and can thus be deployed by copying it to the {glassfish-installation-dir}/glassfish/domains/domain1/autodeploy/bundles directory.

After this, we can deploy moduledemo.services.jar and moduledemo.war, e.g. by copying them to {glassfish-installation-dir}/glassfish/domains/domain1/autodeploy/bundles and {glassfish-installation-dir}/glassfish/domains/domain1/autodeploy, respectively. We should now be able to try out the application by navigating to http://localhost:8080/moduledemo:

The window is empty, as we have not installed any modules yet. Next, we deploy moduledemo.module1.jar and moduledemo.module2.jar in the same way as the Vaadin library or the module service. After clicking the refresh button (which is not the same as restarting the application), the window should look something like this:

We can now bring up the OSGi console and verify that our modules are in fact deployed as OSGi bundles:

$ telnet localhost 6666

The ps command lists all installed bundles. At the end of the list, you should see our application bundles:

Next, we stop module 1 by issuing the following command (the numerical bundle ID may be different on your system):

-> stop 227

The browser window now looks something like this (after a refresh):

We also note that the status of the Module demo module 1 bundle has changed from Active to Resolved:

We can bring module 1 back on line again by issuing the following command (again, the numerical bundle ID may be different on your system):

-> start 227

Refresh the window, and both tabs should be visible again.

Summary

In this article, we have created a simple, modular UI platform for Vaadin applications. Features can be added to the platform on the fly simply by deploying OSGi bundles. Thanks to declarative services and annotations, our application has a clear design without any unnecessary boilerplate code.

7 Attachments 7 Attachments
1242 Views

Average (3 Votes)