Blog

OSGi and Vaadin - Part 1

By  
Florian Pirchner
·
On Aug 5, 2013 9:11:00 AM
·

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

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.

@VaadinServletConfiguration(ui=SimpleUI.class, productionMode=false)
public class SimpleVaadinServlet extends VaadinServlet {
// 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.

@Theme(Reindeer.THEME_NAME)
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.

public class Activator implements BundleActivator {
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.

public class Activator implements BundleActivator,
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  tracker;
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. classthis);
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.

public class Activator implements BundleActivator,
2        ServiceTrackerCustomizer "\">, BundleListener {
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.classthis);
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.

public class ResourceProvider implements HttpContext {
2
3    private List  resources =  new ArrayList ();
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.

@Override
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

 

 

Florian Pirchner
You haven't yet written a blog author profile for yourself. Go to My Account page to write a short description of yourself.
Other posts by Florian Pirchner