« Adding authentication to a Jetty 7 deployment through jetty-web.xml | Main | A fond farewell to Purple Scout »
Wednesday
Jan262011

Deploying a servlet in Jetty 7 using OSGi-DS 

I recently had to deploy a servlet to an instance of a Jetty 7 (7.2.2 to be precise) server embedded in Equinox. To bootstrap Jetty I used the org.eclipse.jetty.osgi.boot bundle as described on the Eclipse Wiki. This bundle is not shipped with 7.2.2 but can be built from source easily.

As it stands, all services in our system runs using OSGi-DS, and I figured it would be neat if I were able to deploy the servlets in a similar fashion.

The servlets themselves are constructed in a non-trivial manner, and had some dependencies on other OSGi-DS-exposed services, so programmatic construction of the servlet instances is preferable for now.

Turns out that's not completely simple or free of hacks, but also not extremely hard.

The org.eclipse.jetty.osgi.boot bundle registers a service tracker looking for services extending org.eclipse.jetty.server.handler.ContextHandler, which basically means that if I just add a <provide interface="org.eclipse.jetty.server.handler.ContextHandler"/> tag to my OSGi service component description I can have Jetty pick up on that automagically.

Yay! Well, almost. This was my first try at a component:

package test.webthing;

import java.util.Map;

import javax.servlet.Servlet;

import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.osgi.service.component.ComponentContext;

public class WebThing extends ServletContextHandler {

    public WebThing() {
        setContextPath("/hello");
    }

    private void addServlet(final Servlet servlet) {
        final ServletHolder holder =
            getServletHandler().newServletHolder();
        holder.setServlet(servlet);
        addServlet(holder, "/");
    }

    public void activate(Map<String, Object> properties)
        throws Exception {
        addServlet(new HelloServlet());
    }
}

That didn't quite work, however; I got this beauty in the logs:

2011-01-26 23:04:24,302 DEBUG org.eclipse.jetty.osgi.boot FrameworkEvent ERROR
java.lang.IllegalArgumentException: the property contextFilePath is required
    at org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker.serviceChanged(JettyContextHandlerServiceTracker.java:231)
    at org.eclipse.osgi.internal.serviceregistry.FilteredServiceListener.serviceChanged(FilteredServiceListener.java:104)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.dispatchEvent(BundleContextImpl.java:933)
    at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:227)
    at org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:149)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEventPrivileged(ServiceRegistry.java:756)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.publishServiceEvent(ServiceRegistry.java:711)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistrationImpl.register(ServiceRegistrationImpl.java:130)
    at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.registerService(ServiceRegistry.java:206)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.registerService(BundleContextImpl.java:507)
    at org.eclipse.equinox.internal.ds.InstanceProcess.registerService(InstanceProcess.java:504)
    at org.eclipse.equinox.internal.ds.InstanceProcess.buildComponents(InstanceProcess.java:212)
    at org.eclipse.equinox.internal.ds.Resolver.buildNewlySatisfied(Resolver.java:441)
    at org.eclipse.equinox.internal.ds.Resolver.enableComponents(Resolver.java:213)
    at org.eclipse.equinox.internal.ds.SCRManager.performWork(SCRManager.java:800)
    at org.eclipse.equinox.internal.ds.SCRManager$QueuedJob.dispatch(SCRManager.java:767)
    at org.eclipse.equinox.internal.ds.WorkThread.run(WorkThread.java:89)
    at org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor.run(Executor.java:70)

Ok, so, I set my breakpoints to stun and started debugging. What happened was that in JettyContextHandlerServiceTracker#serviceChanged() there was a check that required the property "contextFilePath" of the service to be set to something other than null. Digging further revealed that not only did it have to be set to something non-null, it also needed to be a valid jetty-web.xml-file. "But Why!?", I exclaimed, but added the property to my component properties and created a dummy jetty-web.xml file:

<?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC
  "-//Jetty//Configure//EN"
  "http://www.eclipse.org/jetty/configure.dtd">

<Configure class="test.webthing.WebThing" />

Aha! Progress in the logs!

2011-01-26 22:36:35,763 INFO  org.eclipse.jetty.util.log Deployable added: webthing-1.0.0.qualifier/jetty-web.xml

And indeed, the servlet works!

$ curl http://localhost:8080/hello/
HELLO
$

So I modified my component slightly to look like this:

package test.webthing;

import java.util.Map;

import javax.servlet.Servlet;

import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class WebThing extends ServletContextHandler {
    private void addServlet(final Servlet servlet) {
        final ServletHolder holder =
            getServletHandler().newServletHolder();
        holder.setServlet(servlet);
        addServlet(holder, "/");
    }

    public void activate(Map<String, Object> properties)
        throws Exception {
        setContextPath(
            (String) properties.get(
                OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH));
        addServlet(new HelloServlet());
    }
}

and added the contextPath property to my component description (to make it consistent), which now looked like this:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
  activate="activate" configuration-policy="ignore" enabled="true"
  immediate="true" name="test.webthing">
   <implementation class="test.webthing.WebThing"/>
   <service>
      <provide interface="org.eclipse.jetty.server.handler.ContextHandler"/>
   </service>
   <property name="contextFilePath" type="String"
     value="/jetty-web.xml"/>
   <property name="contextPath" type="String" value="/hello"/>
</scr:component>

Hopefully the requirement for a jetty.xml-file will go away in the future, but for now, I can live with it like this, but if anyone has an idea of how to make it better, let me know!

The "webthing" bundle can be downloaded here.

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.
  • Response
    Fnordology - Journal - Deploying a servlet in Jetty 7 using OSGi-DS

Reader Comments (7)

HI ,
I am using jetty 7.2.2 embedded in one of the application . I tried your example without the use of jetty.xml. But it got failed the brower doesn't shows up anything . Yes the jetty server was down . How did you started your jetty server ??? I am using below snippet which programmtically starts the jetty server.

Server server = new Server(50091);
try {
server.start();
server.join();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
The start of the jetty server is kept in bundle A while registering the servlet which you have mentioned is kept in bundle B . Now when i start the application the bundle A starts the server on port 50091 and bundle B also starts but the servlet registration doesn't happens :( ....basically when i did the debug on bundle B the piece of code was never called .

Could you please help me on this as what went wrong

Best Regards,
mitul

February 16, 2011 | Unregistered CommenterMitul

Also to add below are the bundles which are used in my application ...
1) jetty-continuation-7.2.2.v20101205
2) jetty-deploy-7.2.2.v20101205
3) jetty-http-7.2.2.v20101205
4) jetty-io-7.2.2.v20101205
5) jetty-osgi-boot-7.2.2.v20101205
6)jetty-security-7.2.2.v20101205
7) jetty-server-7.2.2.v20101205
8) jetty-servlet-7.2.2.v20101205
9) jetty-util-7.2.2.v20101205
10) jetty-webapp-7.2.2.v20101205
11) jetty-xml-7.2.2.v20101205

Best Regards,
mitu

February 16, 2011 | Unregistered CommenterMitul

You need to ensure you have the jetty-osgi-boot bundle started and that you have a proper configuration for it. jetty-osgi-boot is what loads the config, starts the server and starts looking for the exposed services.

February 16, 2011 | Registered CommenterHenrik Gustafsson

I posted a followup here, with some details.

I just typed it down off the top of my head and did some copy-pasting, so it might have a few bugs or whatnot. Let me know about any issues you find :)

February 16, 2011 | Registered CommenterHenrik Gustafsson

I also start my Jetty server programatically like Mitul.
I use GWT so I have it point to the location of the compiled war


WebAppContext altHandler = new WebAppContext();
altHandler.setResourceBase("./war");
altHandler.setDescriptor("./war/WEB-INF/web.xml");
altHandler.setContextPath("/");
altHandler.setParentLoaderPriority(true);

// Add it to the server
server.setHandler(altHandler);

This works great and my servlets are available.

But I am stumped as to where to get a reference to an OSGi Tracker.
How does one access the OSGi framework from a Servlet inside the Jetty Container

Kris

January 13, 2012 | Unregistered CommenterKris

I haven't done that actually, but I think I might have an idea. I'm a bit busy, but I'll get back soon

January 13, 2012 | Registered CommenterHenrik Gustafsson

(A few months too late, but anyway)

ServletContext.getAttribute("osgi-bundlecontext") will give you the bundle context, from there you can create ServiceTrackers etc.

August 1, 2012 | Unregistered CommenterCaspar MacRae (earcam)

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>