Setting Up JAX-RS for Equinox OSGI
Developing microservices and RESTful applications in Java? See how JAX-RS, OSGI, and the Equinox framework can help you master them.
Join the DZone community and get the full member experience.
Join For FreeAfter a somewhat busy period, I finally had some time to scratch a few things from my TO DO list, and one of them was to get acquainted with microservices, and most notably REST — in OSGI — with Equinox as my framework of choice. So far, I have made microservice-ish modules 'by hand' using servlets, but it is time to move on, and submit myself to the wonders of JAX-RS! As it turned out, it cost me about one-and-a-half days, in which most of my time was spent trying to figure out the best way to approach this. There are quite a few options:
After a few hours of experimenting, I came to the conclusion that the first two options were a bit too convoluted for my modest requirements, so I opted for the third. The promise that the connector would scan bundles for JAX-RS annotated classes seemed enticing, but at the end of the day, I couldn't get the HelloWorld REST Service up and running, and nothing seemed to get registered. The connector did not seem to be actively maintained, so I feared that it was back to the drawing board for me. This was frustrating, as every online tutorial basically told me that all I needed was to simply register a dedicated servlet from a JAX-RS provider (e.g GlassFish) to an OSGI HTTP Service. How difficult can it be? In absence of any log messages, it was a matter of using my head.
It eventually dawned on me that my problems were probably classpath related, and with this, the big 'aha' I had been waiting for finally came: Equinox! The examples were (probably) all made with Apache Karaf, which do not suffer from the strict sandboxing of Equinox OSGI. I absolutely love this aspect of Equinox, but it does come with occasional headaches. Luckily, I didn't have to suffer a JAX-RS-OSGI migraine attack this time, mostly owing to this post in Stack Overflow. The answer from Balazs Zsoldos especially helped me get on the right track. This is probably the reason why the JAX-RS-OSGI connector didn't work as well. So, for those who want a straightforward approach to getting JAX-RS up and running in Equinox OSGI, I offer my solution.
A Recipe
So what did I do?
- Create an OSGI bundle with a REST resource (the ubiquitous HelloWorldService).
- Get it to work with the GlassFish implementation of Jersey 2.x.
Sounds easy enough... and it is, once you have figured out what needs to be done.
Get the required resources from the GlassFish Jersey website. We will need to add the following bundles to our target:
hk2-locator-2.5.0-b42.jar
javax.annotation-api-1.2.jar
javax.inject-1.jar
javax.inject-2.5.0-b42.jar
javax.ws.rs-api-2.1-m09.jar
jersey-client.jar
jersey-common.jar
jersey-container-servlet.jar
jersey-container-servlet-core.jar
jersey-hk2.jar
jersey-server.jar
jsr250-api-1.0.jar
osgi-resource-locator-1.0.1.jar
Create a new bundle in your workspace (assuming you are developing with the Eclipse IDE), and include your REST Resource (for instance, at paragraph 6.3 of Vogella's tutorial on the same subject) .
Add the following dependencies to your MANIFEST.MF file:
javax.servlet,
javax.ws.rs,
javax.ws.rs.core,
org.condast.commons.http,
org.condast.wph.core.definition,
org.glassfish.jersey,
org.glassfish.jersey.inject.hk2,
org.glassfish.jersey.server,
org.glassfish.jersey.servlet,
org.osgi.framework,
org.osgi.service.component.annotations
Create a ServletWrapper around the GlassFish ServletContainer. This ensures that your bundle will be added to CLASSPATH of the Jersey server:
import javax.servlet.Servlet;
import javax.ws.rs.ApplicationPath;
import org.foo.commons.http.AbstractServletWrapper;
import org.condast.wph.rest.resources.ControlResource;
import org.condast.wph.rest.resources.HelloResource;
import org.condast.wph.rest.resources.TerminalResource;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
public class RestServlet extends AbstractServletWrapper {
public static final String S_CONTEXT_PATH = "rest";
public RestServlet() {
super(S_CONTEXT_PATH);
}
@Override
protected Servlet onCreateServlet(String contextPath) {
RestApplication resourceConfig = new RestApplication();
return new ServletContainer(resourceConfig);
}
@ApplicationPath(S_CONTEXT_PATH)
private class RestApplication extends ResourceConfig {
//Loading classes is the safest way...
//in equinox the scanning of packages may not work
private RestApplication() {
register(HelloResource.class);
register(TerminalResource.class);
register(ControlResource.class);
}
}
}
The AbstractServletWrapper
is a trivial implementation of the javax.servlet.Servlet
interface and wraps the org.glassfish.jersey.servlet.ServletContainer
that is created in
the onCreateServlet
method.
- Register the classes in the ResourceConfig (lines 25-35). All other options will fail because the Jersey server will not be able to scan them! Many tutorials will, for instance, demonstrate how the package name can be included, but this will not work. In Equinox, the Jersey server needs access to the actual classes!
- Register the ServletWrapper as an HttpService. I opted to do this by means of the plugin extension framework (extension org.eclipse.eqiunox.http.registry), but this can of course also be done declaratively or programmatically. Remember to have the correct service up and running when launching your application!
- Launch your application with at least the following bundles (if you are using Jetty):
javax.annotation-api;
javax.servlet;
javax.transaction;
javax.ws.rs-api;
org.apache.felix.gogo.command;
org.apache.felix.gogo.runtime;
org.apache.felix.gogo.shell;
org.eclipse.equinox.console;
org.eclipse.equinox.ds@1;
org.eclipse.equinox.util;
org.eclipse.osgi.services;
org.eclipse.osgi;
org.glassfish.hk2.external.javax.inject;
org.glassfish.hk2.osgi-resource-locator;
org.eclipse.equinox.http.jetty;
org.eclipse.jetty.continuation;
org.eclipse.jetty.http;
org.eclipse.jetty.io;
org.eclipse.jetty.security;
org.eclipse.jetty.server;
org.eclipse.jetty.servlet;
org.eclipse.jetty.util;
Note: Jetty starts your application on a default port, usually 8080. You can change this by passing the VM argument:
-Dorg.osgi.service.http.port=8080
Now you should be able to see your REST service on localhost:<jetty port>/rest/hello
Conclusion
I absolutely love the strict modularization of Equinox OSGI, but you tend to forget that other frameworks are often less restrictive. The online tutorials may, therefore, not get you what you are looking for and, even worse, you may get very few clues on what is going wrong. Luckily, in this particular case, I managed to circumvent this well-known pitfall. Hopefully, it may help other as well!
Opinions expressed by DZone contributors are their own.
Comments