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.
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
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
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.
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 :)
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
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
(A few months too late, but anyway)
ServletContext.getAttribute("osgi-bundlecontext") will give you the bundle context, from there you can create ServiceTrackers etc.