OSGi has a service spec called http.service (see Service Compendium document of the OSGi Alliance). Felix has an implementation for it that is based around jetty 4.x. Since we are at jetty 6.1.7 at the moment I thought about trying to create an implementation of my own. Not that it is really necessary, you can expose resources without complying to the spec (see my other post).
But the spec is there for a reason, so let’s try to adhere to it first. The specification is up to the following two interfaces and one exception.
- HttpContext – Enables bundles to use provided information about a servlet or resource during registration.
- HttpService – Enables other bundles to dynamically register sevlets or resources into the Http Service URI namespace.
- NamespaceException – Thrown when a problem arises during registration of a servlet or resource into the Http Service UIR namespace.
At first I tried to create my own implementation for these interfaces. It did not look to hard, but in the end I found the following patch which made my life a lot easier.
jetty6 patch
To be able to understand the implementation of an Http Service using jetty, you should understand the basics with respect to Jetty. In a forthcoming post I’ll talk more about these details. Within this post I’ll concentrate on the using of the service. I do need to make a few remarks. What if you do not want to apply the patch, change the pom, etc. Well you can download the one I have created from here : org.apache.felix.http.jetty-0.9.0-GRIDSHORE.jar. Another thing I would like to stress is the current state of the bundle. There are a lot of TODO’d in there. It looks like security is not implemented as it should. There is a completely different implementation at the ops4j website. One disadvantage is the level of control, need to look into that thing again. It is much more complicated, uses other bundles of the pax project. It does look interesting, but for me it is not easy enough. Another implementation is available at the sling website. But again a lot to graps before you can start. So for now I have chosen to continue with the slightly limited implementation, adhere to the spec and my bundles should work with the others as well.
Read more to learn about the sample using http.service and maven to build the bundle.
The sample
The goal was to play around with servlets and resources. I want to get a better idea about what OSGi can do. So I used the runner I introduced in my previous post (starting-with-osgi-using-apache-felix-step-1). Of course there are some other bundles and there is an extra property to configure the jetty intance:
configMap.put("org.osgi.service.http.port","9081"); // set the port to listen to for jetty configMap.put(AutoActivator.AUTO_START_PROP + ".1", "file:" + BUNDLE_ROOT + "training-service/1.0-SNAPSHOT/training-service-1.0-SNAPSHOT.jar " + "file:" + BUNDLE_ROOT + "http-servlets/1.0-SNAPSHOT/http-servlets-1.0-SNAPSHOT.jar " + "file:" + JETTY_ROOT + "jetty/6.1.7/jetty-6.1.7.jar " + "file:" + JETTY_ROOT + "jetty-util/6.1.7/jetty-util-6.1.7.jar " + "file:" + JETTY_ROOT + "servlet-api-2.5/6.1.7/servlet-api-2.5-6.1.7.jar " + "file:bundle/org.osgi.compendium-1.0.0.jar " + "file:bundle/slf4j-api-1.4.3.jar " + "file:bundle/slf4j-simple-1.4.3.jar " + "file:bundle/org.apache.felix.http.jetty-0.9.0-GRIDSHORE.jar " + // use the created patched http.service "file:bundle/org.apache.felix.shell-1.0.0.jar " + "file:bundle/org.apache.felix.shell.tui-1.0.0.jar ");
The training service is a very easy service with one method returning a list of trainings. The more interesting bundle is the http-servlets bundle. This bundle contains the servlet that uses the training-service and some static resources like html pages, style sheets, etc. Let’s start with a look at the internal structure of the bundle. Please remember that we use the maven-bundle-plugin plugin. Therefore we adhere to basic maven project structure. The java files are in the src/main/java folder. The static resources are in the src/main/resources folder. The plugin than creates the right MANIFEST.MF file.
Manifest-Version: 1.0 Built-By: jettro Created-By: Apache Maven Bundle Plugin Bundle-Activator: nl.gridshore.samples.bundles.httpservlets.impl.Activ ator Import-Package: javax.servlet;version="2.5",javax.servlet.http;version ="2.5",nl.gridshore.samples.bundles.trainingservice.api,org.osgi.fram ework;version="1.3",org.osgi.service.http;version="1.2",org.slf4j;ver sion="1.4" Bnd-LastModified: 1204315796232 Bundle-Version: 1.0.0.SNAPSHOT Bundle-Name: http-servlets Build-Jdk: 1.5.0_13 Private-Package: htmls,htmls.css,htmls.images,nl.gridshore.samples.bun dles.httpservlets.impl Bundle-ManifestVersion: 2 Bundle-SymbolicName: http-servlets Tool: Bnd-0.0.227
Take good notice of the Private-Packge, here the directories are listed that are in the resources folder. So by default these resources are private. We need the http service to expose these resources. The next step is to have a look at the Activator of the http-servlets bundle. Let’s step through the separate methods that are important.
public void start(BundleContext bundleContext) throws Exception { this.bundleContext = bundleContext; doRegister(); synchronized (this) { bundleContext.addServiceListener(this, "(|(objectClass=" + TrainingService.class.getName() + ")" + "(objectClass=" + HttpService.class.getName() + "))"); } }
Two important things happen in the start method, we first try to register the servlet (more on that later). After that we add a service listener that listens to events related to the TrainingService class and the HttpService class. This means that if one of these instances are started, stopped or updated we are notified. This notification is important to unregister the servlet if the training service disappears. Of course it is also important to register the servlet if the http service becomes available. Let’s move on to the register methods.
private void register() throws InvalidSyntaxException, ServletException, NamespaceException { ServiceReference[] trainingReferences = bundleContext.getServiceReferences(TrainingService.class.getName(), null); TrainingService trainingService = null; if (trainingReferences != null) { trainingService = (TrainingService) bundleContext.getService(trainingReferences[0]); } else { logger.info("No training service available"); } ServiceReference[] httpReferences = bundleContext.getServiceReferences(HttpService.class.getName(), null); HttpService httpService = null; if (httpReferences != null) { httpService = (HttpService) bundleContext.getService(httpReferences[0]); } else { logger.info("No http service available"); } if ((trainingService != null) && (httpService != null)) { logger.info("training servlet will be registered."); httpService.registerServlet("/trainings", new TrainingsServlet(trainingService), null, null); httpService.registerResources("/","/htmls",null); } else { logger.info("No servlet to register, problem with training service or http service"); } } private void doRegister() { try { register(); } catch (InvalidSyntaxException e) { logger.error("Could not register servlet based on an Invalid Syntax",e); } catch (ServletException e) { logger.error("Could not register servlet based on an Servlet exception",e); } catch (NamespaceException e) { logger.error("Could not register servlet based on an Namespace exception",e); } }
Concentrate on the register method. First we check for available training service references. If a reference is available, we get the actual service. Then the same is done for the http service. If both of them are availble, we use the http service to register a new servlet that accepts the training service to obtain trainings from. As you can see we first register the servlet, the two null parameters represent the servlet config parameters and the special HttpContext. By providing null a default context is created. We also register a folder with static resources. The first string is the path or alias where the user should access the resources. The second string is the path to the resources within the bundle. The final parameter is the same as for the servlet, the HttpContext, again null so the default value. There are to more methods I want to show, first for unregistering and second for handling change events.
private void unregister() throws InvalidSyntaxException { logger.info("Unregister a servlet"); ServiceReference[] httpReferences = bundleContext.getServiceReferences(HttpService.class.getName(), null); if (httpReferences != null) { HttpService httpService = (HttpService) bundleContext.getService(httpReferences[0]); httpService.unregister("/trainings"); httpService.unregister("/"); } } private void doUnregister() { try { unregister(); } catch (InvalidSyntaxException e) { logger.error("Could not unregister servlet",e); } } public void serviceChanged(ServiceEvent event) { String objectClass = ((String[]) event.getServiceReference().getProperty("objectClass"))[0]; logger.info("Service change event occurred for : {}", objectClass ); if (event.getType() == ServiceEvent.REGISTERED) { doRegister(); } else if (event.getType() == ServiceEvent.UNREGISTERING) { doUnregister(); } else if (event.getType() == ServiceEvent.MODIFIED) { doUnregister(); doRegister(); } }
The unregister function is pretty obvious. So let’s have a better look at the serviceChanged method. I took the easy way out here. Just check the type of event. In case of a new registration, register the servlet and resources. In case of an unregistration, we unregister the servlet and in case of a change we just do an unregister and a register. This works oke for my situation. Using the following screendumps I’ll show what happens when we start all bundles, stop de training-service bundle, and in the end start it again. First the output from the console, then the screens.
Welcome to Felix. ================= 1 [FelixStartLevel] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - No http service available 1 [FelixStartLevel] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - No servlet to register, problem with training service or http service org.mortbay.log:Logging to org.mortbay.log via org.apache.felix.http.jetty.LogServiceLog org.mortbay.log:started org.mortbay.jetty.servlet.HashSessionIdManager@2b2057 org.mortbay.log:started org.mortbay.jetty.servlet.HashSessionManager@85c0de org.mortbay.log:starting OsgiServletHandler@395aaf org.mortbay.log:started OsgiServletHandler@395aaf org.mortbay.log:starting SessionHandler@70b819 org.mortbay.log:started SessionHandler@70b819 org.mortbay.log:starting org.mortbay.jetty.servlet.Context@1b50a1{/,null} org.mortbay.log:starting ErrorHandler@46ad8b org.mortbay.log:started ErrorHandler@46ad8b org.mortbay.log:started org.mortbay.jetty.servlet.Context@1b50a1{/,null} org.mortbay.log:jetty-6.1.x org.mortbay.log:started Realm[OSGi HTTP Service Realm]==[] org.mortbay.log:started org.mortbay.thread.BoundedThreadPool@ebaf12 org.mortbay.log:starting Server@32e910 org.mortbay.log:started org.mortbay.jetty.nio.SelectChannelConnector$1@19549e org.mortbay.log:Started SelectChannelConnector@0.0.0.0:9081 org.mortbay.log:started SelectChannelConnector@0.0.0.0:9081 org.mortbay.log:started Server@32e910 191 [FelixStartLevel] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - Service change event occurred for : org.osgi.service.http.HttpService 191 [FelixStartLevel] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - training servlet will be registered. org.mortbay.log:started /trainings/* org.mortbay.log:started /htmls -> stop 1 36499 [Felix Shell TUI] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - Service change event occurred for : nl.gridshore.samples.bundles.trainingservice.api.TrainingService 36499 [Felix Shell TUI] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - Unregister a servlet -> start 1 org.mortbay.log:started /trainings/* org.mortbay.log:started /htmls 43500 [Felix Shell TUI] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - Service change event occurred for : nl.gridshore.samples.bundles.trainingservice.api.TrainingService 43500 [Felix Shell TUI] INFO nl.gridshore.samples.bundles.httpservlets.impl.Activator - training servlet will be registered. ->
Try to follow the steps, you can see the bundle containing the servlet starts before the training-service of the http-service. Then the change event is received and the servlet is registered. Next we stop the training service and the servlet is unregistered. And the servlet is registered again after a start of the training-service bundle (number 1).
This is the initial screen. Notice the url, this is a static resource called index.html that loads a stylesheet and an image from the bundle.
Now we have stopped the training service bundle, which has resulted in a unregister of the servlet, therefore the page is not available anymore.
I have started the training service bundle again, so the servlet gets registered and now I have clicked the link for trainings. As you can see we have two very interesting trainings.
So that is about it, it turned out to become a pretty long post. Hope you liked it. Please leave a comment if you did, and of course if you did not. All your ideas and suggestions are welcome. If you want to have a look at the source, you can find everyting in the FelixTryout project at my google code website.