Deploying a servlet in Jetty 7 using OSGi-DS 
Wednesday, January 26, 2011 at 22:48
Henrik Gustafsson in Hacking

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.

Article originally appeared on Fnordology (http://blog.fnord.se/).
See website for complete article licensing information.