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.


About this entry