Web Applications with OSGi – Working with Jersey

In this previous article, I explained how to deploy standard web applications into an OSGi container. In this article, I will talk about how to deploy a web application that exposes REST services with Jersey. Once again, the complete sources are available on GitHub.

I have tried many things.
And actually, only one really worked. Let’s start with it. For the record, I used Jersey 1.x. This is because the application I plan to migrate to OSGi uses this version of Jersey. As in the previous article, I give explanations for Apache Karaf.

Explanations

First, you need to install Jersey bundles in your OSGi container.
If you use version 1.18, you MUST switch to version 1.18.1. Version 1.18 relies on an old version of ASM (version 3.x), while Karaf uses ASM 4.x. Both versions of ASM are incompatible. Using version 1.18 generally result in…

java.lang.IncompatibleClassChangeError: Implementing class

So, deploy jersey-core, jersey-server and jersey-servlet.

bundle:install –start mvn:com.sun.jersey/jersey-core/1.18.1
bundle:install –start mvn:com.sun.jersey/jersey-server/1.18.1
bundle:install –start mvn:com.sun.jersey/jersey-servlet/1.18.1

These bundles export packages your web application will import.
Here is what your POM should look like for your REST bundle.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>net.vzurczak</groupId>
	<artifactId>rest-with-jersey-as-a-bundle</artifactId>
	<name>OSGi Sample :: REST :: Jersey as a Bundle</name>
	<packaging>bundle</packaging>
	
	<properties>
		<jersey.stack.version>1.18.1</jersey.stack.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-core</artifactId>
			<version>${jersey.stack.version}</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.felix</groupId>
				<artifactId>maven-bundle-plugin</artifactId>
				<configuration>
					<instructions>
					 	<Import-Package>
							javax.ws.rs.*,
							com.sun.jersey.api.core,
							com.sun.jersey.spi.container.servlet
						</Import-Package>
						<Web-ContextPath>rest-bundle</Web-ContextPath>
						<Webapp-Context>rest-bundle</Webapp-Context>
						<_wab>src/main/webapp</_wab>
					</instructions>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

As you can see, this is a WAB (Web Archive Bundle).
Unlike what I did in my first examples, this is not a WAR. This is a JAR file, with OSGi metadata in its MANIFEST, and the structure of a WAR. Why I chose the JAR extension is because when somebody see a WAR, the first reflex is to deploy it in a web application server (such as Tomcat). But in this case, we use OSGi mechanisms and this will NOT work within Tomcat. The JAR prevents any ambiguity.

So, you bundle must import the JAX-RS annotations, as well as classes related to the servlet specification. Notice the _wab instruction for the maven-bundle-plugin. It indicates where the web resources are located. The plug-in will generate the right metadata (mainly the class path).

Here is my JAX-RS resource…

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path( "/get-data" )
public class MyRestResource {

	@GET
	public Response getDataToDisplay() {
		return Response.ok().entity( "Yes, it works." ).build();
	}
}

… and the web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

	<display-name>Welcome Page</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>

	<servlet>
		<servlet-name>MyServlet</servlet-name>
		<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
		
		<init-param>
			<param-name>com.sun.jersey.config.property.packages</param-name>
			<param-value>net.vzurczak.sample.rest.internal</param-value>
		</init-param>
		
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>MyServlet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>
</web-app>

The web context being rest-bundle (check the POM), the REST services will found under rest-bundle/rest/. For the deployment, make sure the WAR feature is installed and started. Then, build your project and deploy the resulting JAR in Karaf’s deploy directory.

You should find your REST service under http://localhost:8181/rest-bundle/rest/get-data.

Complementary Information

I guess things must work the same way with JAX-RS 2.x.
The main difference will probably be about the name of the packages to import. In fact, for Jersey 2.x, you can try the osgi-jax-rs-connector.

I also said the WAR features had to be installed and started before deploying the web application. This can be a problem if you restart the OSGi container. Indeed, if you web application starts at the same time or before the web server, your web bundle will not be registered by the HTTP service. I have not really investigated a solution about that. You may have to play with the bundle start level to solve it.

Eventually, I tried to embed a specific version of Jersey (let’s say 0.18) inside my bundle. This would fit situations where a specific bundle needs another version of Jersey. For this, I used 2 instructions of the maven-bundle-plugin.

<instructions>
	< Embed-Dependency>*;scope=compile|runtime;inline=true</Embed-Dependency>
	< Embed-Transitive>true</Embed-Transitive>
</instructions>

Roughly, it repackages the Maven dependencies inside the bundle.
Unfortunately, no matter what I tried, there was always an error during the deployment. Either there were no provider classes, or there was a missing package (javax.ejb), or no WebApplication provider was present. Resource scanning does not work, and setting the application in the web.xml does not solve it either. This is a problem if you want to expose REST services (server side). I do not think it is a problem for clients.

Anyway, for the server side, you can only rely on the first mechanism I described here. One good practice, if several versions of a same package are present in your OSGi container, is to hard-code the package version in your imports. You can specify a given version or version ranges in your imports…

<plugin>
	<groupId>org.apache.felix</groupId>
	<artifactId>maven-bundle-plugin</artifactId>
	<configuration>
		<instructions>
		 	<Import-Package>
				javax.ws.rs.*,
				com.sun.jersey.api.core;version="[1.0,2.0)",
				com.sun.jersey.spi.container.servlet;version="[1.18.1,1.19)"
			</Import-Package>
			<Web-ContextPath>rest-bundle</Web-ContextPath>
			<Webapp-Context>rest-bundle</Webapp-Context>
			<_wab>src/main/webapp</_wab>
		</instructions>
	</configuration>
</plugin>

See comments below about OSGi version ranges.
You can also take alook at this post on stackoverflow and in the OSGi Core Specification.

10 thoughts on “Web Applications with OSGi – Working with Jersey

  1. I found a small issue in your post. If you do an Import-Package with version=”1.18.1″ this means version>=1.18.1. If you want to really pin down the version you need version=”[1.18.1,1.18.1]”. A useful thing is also to limit to a certain minor version only version=”[1.18,1.19)” so you get bug fixes but no bigger changes.

    1. I thought putting the exact version meant use that version and no other one. After checking it on the web, you are right. Thanks for notifying me.

      version=”[1.18,1.19)” looks good indeed, but assuming you declare a lower version with ${project.version}, how do you inject the upper version using a POM? Apart declaring the next version as a Maven property, I don’t see any other option.

      Anyway, I will update this article.
      Again, thanks! 🙂

      1. Well, I would like to reply to myself.
        In fact, we do not need to have a custom Maven property. The Maven Bundle plugin is based on BND. Looking at this page, I first tried the provide directive. Putting it on the Import-Package works, but with some side effects.

        The solution was close. In fact, just put ;version="${range;[==,=+)}" instead of ;version="${project.version}". And it works. Here, it means the second digit is incremented and excluded from the range.

  2. Hi Vincent,

    I copied the rest-with-jersey-as-a-bundle-1.0-SNAPSHOT.jar in the deploy folder, and whenever i hit the url I am getting the below error

    2016-03-31 16:43:21,881 | WARN | pool-46-thread-1 | t-with-jersey-as-a-bundle – 100} | 80 – org.eclipse.jetty.util – 9.2.14.v20151106 | unavailable
    com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.

    1. Hi. Did you specify the right package in the web.xml file?
      This is where Jersey will search for resource classes. If you changed the package name, make sure you propagated this change into the web.xml file.

    2. OK. For the record, another person met the same problem.
      After investigating it, we found out this was due to the fact the Jersey bundles were installed but not started. I had forgotten to mention this part in my article. I updated the blog post in consequence (bundle install –start…).

  3. Hi Vincent,

    This is really great article, it helped me to remove lots of confusion.
    I have some issue of deployment of this bundle on the eclipse vergo, how is that possible?

  4. Hello Vincent,

    This post helped me a lot to get clear way for the jersey-osgi wab.
    I want to deploy it on virgo jetty server, how can I do it?

    I’m struggling on that since many days. I have also tried with osgi-jax-rs-connector connector way but didn’t get success.

    Thanks for this post.

    1. Hi!

      I have not used Virgo for quite a long time now. And I cannot experiment anything right now as I am (supposed to be) on hollidays. Anything in the logs? How about bundle statuses? Are they all started?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s