If you are looking forward to run Vaadin with an OSGi environment you would have found the "Lunifera addon" at Vaadin directory. It is a comfortable way to get some benefits of OSGi in your Vaadin application. But maybe you want to implement your Vaadin bridge by your own. Therefore I have prepared a simple example licensed under the Apache License V2 that introduces you how to implement things properly.
Requirements
To run Vaadin in an OSGi environment we need to have following parts:
- an OSGi runtime
- the OSGi-HttpService to register VaadinServlet
- a class that handles the loading of static resources
- a bundle that wires things together
As an OSGi runtime I am using equinox in my example. But you are free to use a different one. There are no dependencies to equinox so far. HttpService - Thats an OSGi-Service defined in OSGi compendium specification. It will be provided by a bundle (equinox.servlets) and you can think about it like an abstraction above a servlet context. It's API allows you to register servlets and resources to the underlying servlet context. But you do not get in touch with any underlying implementations. Static resources - Thats all the web resources like css-files, js-files, themes,... that are contained in the Vaadin bundles. In pure JEE you may use the classloader to find all the resources in your big classpath. If you are dealing with OSGi there isn't a big classpath anymore. Each bundle has it's own classloader and classpath. So we need to use the classloader of the Vaadin bundles to load the static resources. Seems to be very complex. But it isn't. You will see. At least we have to get an instance of HttpService and we need to register the VaadinServlet at. Thats all we have to do and your Vaadin application will run properly in an OSGi environment.
Setup example
To follow my explanation
- download Vaadin_Simple_OSGi.zip from http://lun.lunifera.org/downloads/examples/vaadin/
- unzip to your favorite location
or
- clone https://github.com/lunifera/lunifera-vaadin-examples.git (contains a bundle org.lunifera.example.vaadin.osgi.bootstrap.simple)
- use Import -> "Existing Maven Projects" in eclipse IDE (Make sure that m2e is installed)
- expand org.lunifera.example.vaadin.osgi.bootstrap.simple/config and set target.target
- open target.target
- wait until resolved
- if error, then select all repositories in target and press update button on the right side
- wait until resolved
- press "set as target platform"
- Now there should be no problems. (Except pom.xml -> Just delete it)
You will find a bundle called "org.lunifera.example.vaadin.osgi.bootstrap.simple". All the necessary code is located there.
VaadinServlet
We are going to create a custom VaadinServlet.
2 public class SimpleVaadinServlet extends VaadinServlet {
3 // nothing to do here
4 }
The implementation is very simple. It just uses the VaadinServletConfiguration-annotation to define the used UI-class and the production mode. And thats all we have to do...
SimpleUI
Now we are going to prepare a simple UI class. There is no different to non OSGi environments.
2 public class SimpleUI extends UI {
3 @Override
4 protected void init(VaadinRequest request) {
5 HorizontalLayout layout = new HorizontalLayout();
6 setContent(layout);
7 layout.setStyleName(Reindeer.LAYOUT_BLUE);
8 layout.setSizeFull();
9
10 Label label = new Label();
11 label.setValue(\"Simple OSGi integration\");
12 label.setContentMode(ContentMode.HTML);
13 layout.addComponent(label);
14 }
15 }
Instances for that UI-class will be created by Vaadin since we defined it in the VaadinServlet annotation.
Wire things together
Now we have to access the HttpService. This blog uses an activator to do so. Activators are called by the OSGi-environment if a bundle is started and implements two methods - start(BundleContext) and stop(BundleContext).
Activator
For now our activator looks like this. A simple activator doing nothing.
2
3 private static BundleContext context;
4
5 static BundleContext getContext() {
6 return context;
7 }
8
9 public void start(BundleContext bundleContext) throws Exception {
10 Activator.context = bundleContext;
11
12 }
13
14 public void stop(BundleContext bundleContext) throws Exception {
15 Activator.context = null;
16 }
17
18 }
HttpService
The next step is to get an instance of HttpService. Different ways are possible, but in this post I will use a ServiceTracker. A ServiceTracker adds as an observer to the OSGi-service-registry and tracks "coming and going" services.
2 ServiceTrackerCustomizer
3
4 private static BundleContext context;
5
6 static BundleContext getContext() {
7 return context;
8 }
9
10 // used to track the HttpService
11 private ServiceTracker
12 // used to register servlets
13 private HttpService httpService;
14
15 //
16 // Helper methods to get an instance of the http service
17 //
18 @Override
19 public HttpService addingService(ServiceReference reference) {
20 httpService = context.getService(reference);
21
22 try {
23 // register the servlet at the http service
24 httpService.registerServlet(\ "/\", new SimpleVaadinServlet(), null,
25 null);
26 } catch (ServletException e) {
27 e.printStackTrace();
28 } catch (NamespaceException e) {
29 e.printStackTrace();
30 }
31
32 return httpService;
33 }
34
35 @Override
36 public void removedService(ServiceReference reference,
37 HttpService service) {
38 // unregister the servlet from the http service
39 httpService.unregister(\ "/\");
40 }
41
42 public void start(BundleContext bundleContext) throws Exception {
43 Activator.context = bundleContext;
44
45 // Start a HttpService-Tracker to get an instance of HttpService
46 tracker = new ServiceTracker<>(bundleContext, HttpService. class, this);
47 tracker.open();
48 }
49
50 public void stop(BundleContext bundleContext) throws Exception {
51 // close the HttpService-tracker
52 tracker.close();
53 tracker = null;
54
55 Activator.context = null;
56 }
Line 46 and 47 starting the service tracker. If a http service was added the method #addinService() will be invoked. And #removedService() is called if the service was removed again. At line 20 the http service is accessed using the passed service reference. At line 24 and 25 the VaadinServlet is registered at the http service and may become accessed afterwards. Line 39 unregisters the VaadinServlet if the service was removed again. Line 52 stops the ServiceTracker if the bundle is stopped. The service tracker will call #removedService() for each added service. So the servlets become unregistered before the bundle is stopped.
HttpContext - Resource provider
Now you can try to run the Vaadin application, but you will see that it does not work! The problem is that no resources like js-files, css-files,... can become accessed. So we have to ensure that the HttpService gets access to all required resources. There is a concept called HttpContext which is a parameter for the servlet registration at HttpService. The HttpService is used to access resources, handle security,... The way we will implement things is to find all bundles that may contain resources and to delegate resource requests to the bundle. I assume that bundles starting with "com.vaadin" are potential owners of resources.
Find all "com.vaadin" bundles
To find all Vaadin bundles we are using a BundleListener. It is registered at the bundle context and tells us about the lifecycle of bundles.
2 ServiceTrackerCustomizer
3
4 private ResourceProvider resourceProvider;
5
6 public void start(BundleContext bundleContext) throws Exception {
7 Activator.context = bundleContext;
8
9 resourceProvider = new ResourceProvider();
10
11 // register this instance as a bundle listener
12 bundleContext.addBundleListener(this);
13
14 // Start a HttpService-Tracker to get an instance of HttpService
15 tracker = new ServiceTracker<>(bundleContext, HttpService.class, this);
16 tracker.open();
17 }
18
19 public void stop(BundleContext bundleContext) throws Exception {
20 // close the HttpService-tracker
21 tracker.close();
22 tracker = null;
23
24 resourceProvider = null;
25
26 bundleContext.removeBundleListener(this);
27
28 Activator.context = null;
29 }
30
31 @Override
32 public void bundleChanged(BundleEvent event) {
33 // vaadin bundle it will be added to the resource provider for lookups.
34 String name = event.getBundle().getSymbolicName();
35 if (name.startsWith(\"com.vaadin\")) {
36 if (event.getType() == BundleEvent.STARTED) {
37 resourceProvider.add(event.getBundle());
38 } else if (event.getType() == BundleEvent.STOPPED) {
39 resourceProvider.remove(event.getBundle());
40 }
41 }
42 }
43 }
Line 2 adds BundleListener interface to the Activator. Line 12 registers the activator as a bundle listener. Line 26 removes the activator as a bundle listener if the bundle becomes stopped. Line 32 becomes invoked if the state of a bundle was changed. We are checking whether the symbolic name starts with "com.vaadin". If it does so, we are adding or removing the bundle to or from the resource provider. The resource provider is not implemented yet, but we will do immediatelly.
Create the ResourceProvider
The resource provider is really simple. It implements the HttpContext interface and delegates the getResource(String) call to the registered "com.vaadin" bundles.
2
3 private List
4
5 @Override
6 public URL getResource(String uri) {
7 URL resource = null;
8 // iterate over the vaadin bundles and try
9 // to find the requested resource
10 for (Bundle bundle : resources) {
11 resource = bundle.getResource(uri);
12 if (resource != null) {
13 break;
14 }
15 }
16 return resource;
17 }
18
19 /**
20 * Adds a bundle that may potentially contain
21 * a requested resource.
22 * @param bundle
23 */
24 public void add(Bundle bundle) {
25 resources.add(bundle);
26 }
27
28 /**
29 * Removes a bundle that may potentially contain
30 * a requested resource.
31 * @param bundle
32 */
33 public void remove(Bundle bundle) {
34 resources.remove(bundle);
35 }
36 }
Using the bundle.getResource(String uri) method the resource is searched in the bundle. If found, it will be returned.
Use the ResourceProvider
The very last step in coding is to use the resource provider. We have to create and instance in start() and drop it (set to null) in the stop() method. And we have to ensure, that the resource provider is used during servlet registration.
2 public HttpService addingService(ServiceReference reference) {
3 httpService = context.getService(reference);
4
5 try {
6 // register the servlet at the http service
7 httpService.registerServlet(\"/\", new SimpleVaadinServlet(), null,
8 resourceProvider);
9 } catch (ServletException e) {
10 e.printStackTrace();
11 } catch (NamespaceException e) {
12 e.printStackTrace();
13 }
14
15 return httpService;
16 }
17 }
Run example
To run the example, we need to prepare an OSGi-launch-configuration. And we have to set the start level of our bundle carefully. The bundle "org.lunifera.example.vaadin.osgi.bootstrap.simple" needs to track all starting Vaadin bundles, and so we have to ensure that the bundle is started before the "com.vaadin" bundles do so. So lets set the start level of our bootstrap bundle to 3 (default is 4). And we have to set "autostart" since the activator should be invoked eager.
bundle | start level | autostart |
---|---|---|
org.lunifera.example.vaadin.osgi.bootstrap.simple | 3 | true |
com.vaadin.client-compiled | default | false |
com.vaadin.server | default | false |
com.vaadin.shared | default | false |
com.vaadin.shared.deps | default | false |
com.vaadin.themes | default | false |
javax.annotation | default | false |
javax.servlet | default | false |
org.apache.felix.gogo.command | default | false |
org.apache.felix.gogo.runtime | default | false |
org.apache.felix.gogo.shell | default | false |
org.eclipse.equinox.console | default | false |
org.eclipse.equinox.http.jetty | default | false |
org.eclipse.equinox.http.servlet | default | false |
org.eclipse.jetty.continuation | default | false |
org.eclipse.jetty.http | default | false |
org.eclipse.jetty.io | default | false |
org.eclipse.jetty.security | default | false |
org.eclipse.jetty.server | default | false |
org.eclipse.jetty.servlet | default | false |
org.eclipse.jetty.util | default | false |
org.eclipse.osgi | default | false |
org.eclipse.osgi.services | default | false |
org.json | default | false |
org.jsoup | default | false |
To start a jetty server on a proper port, use the VM argument: "-Dorg.osgi.service.http.port=8082" in your launch configuration. Now you can access the Vaadin page under "http://localhost:8082".
By Florian Pirchner - based on lunifera.org - OSGi components for business applications