Now, that early builds of Jersey 2.0 are integrated into GlassFish application server main trunk (which Jakub is still planning to blog about) more people have to deal with the reality that Jersey 2.0 is not compatible with Jersey 1.x. All the internal Jersey API’s underwent a significant refactoring in Jersey 2.0. And people started asking if there is any way of designing the web application (WAR-based) so that it can run on GlassFish 3.x (which bundles Jersey 1.x) while also being able to run with GlassFish main trunk (that now bundles Jersey 2.0 milestone builds). So, I decided to spend my last week’s “weekend coding” on creating a simple jar that one can bundle in their war to enable this. As a result, I added a new maven module under jersey/ext (in Jersey 2.0 workspace) called servlet-portability. You can build it and add it to your application as a dependency. Now, here is what it does and what it doesn’t do:
- It provides PortableServletContainer class you can use in web.xml instead of the Jersey version-specific ServletContainer class to register a Jersey servlet.
- It enables your application to bundle Jersey 2.x-specific code along with Jersey 1.x-specific code and helps to ensure the right code gets loaded depending on which version of the Jersey runtime is on the runtime classpath.
- It does not provide a compatibility layer – in other words, it does not enable you to use API’s from Jersey 1.x with Jersey 2.x runtime – i.e. in your web app you have to write the Jersey version-specific code twice – one class using Jersey 1.x API’s and another one using Jersey 2.x API’s.
Example Application
Here is how you can use it. I will build a simple application with the following requirements:
- Use package-scanning for discovering all the resources and providers that are using pure JAX-RS API (i.e. are not using any version-specific Jersey API’s)
- Expose one resource that will require different implementation depending on Jersey version
Getting Jersey 2.0 Source Code and Building It
To try this out let’s first get Jersey 2.0 source code. You can do it in one of the following ways:
- Download the sources zip file, or
- Clone Jersey git repository on java.net by executing the following in the terminal:
git clone ssh://<your_java_net_userid>@git.java.net/jersey~code jersey
, or
- Clone Jersey git repository on github.com by executing the following in the terminal:
git clone git://github.com/jersey/jersey.git
Now, to build the code execute the following in the source tree root:
mvn clean install -Dmaven.test.skip=true
Creating a New Web Application Project
We will use Jersey 2.0 web application maven archetype to quickly create a skeleton of a new Jersey web application. To do that, switch to the directory where you want your project to reside and execute the following command in that directory:
mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example -DarchetypeVersion=2.0-SNAPSHOT
You should see a new simple-service subdirectory got created that contains the project files. Now, open this project in your favorite IDE. You should see it is a simple project containing a single resource called MyResource (in com.example package) and registering Jersey servlet using web.xml descriptor.
Updating the pom.xml
Let’s update the pom.xml of the project to add a dependency on the portability module, change the scope of Jersey 2.0 servlet dependency to “provided” (so that the Jersey jars are not bundled in our war) and add Jersey 1.x servlet dependency. The resulting pom.xml should look as follows:
4.0.0 com.example simple-service <packaging>war</packaging>1.0-SNAPSHOT simple-service simple-service <plugins> <plugin>org.apache.maven.plugins maven-compiler-plugin true </plugin> </plugins> 1.6 <properties> org.glassfish.jersey.ext jersey-servlet-portability ${jersey.version} org.glassfish.jersey.containers jersey-container-servlet-core ${jersey.version} provided com.sun.jersey jersey-servlet 1.12 provided 2.0-SNAPSHOT <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
Adding Jersey Version-Specific Resource
Create two new sub-packages – com.example.jersey1 and com.example.jersey2. We will use these for version-specific classes. Let’s create a new resource in com.example.jersey2 package and name it InfoResource. Just for illustration, it will inject ResourceConfig class from Jersey 2 API, retrieve the value of “example.text” property and return it back to the client. Here is how the resource class looks like:
package com.example.jersey2; import javax.ws.rs.GET; import javax.ws.rs.core.Context; import org.glassfish.jersey.server.ResourceConfig; public class InfoResource { private @Context ResourceConfig rc; @GET public String get() { return (String) rc.getProperty("example.text"); } }
As you can see on line 5 it uses Jersey version-specific class. Let’s clone this into com.example.jersey1 package, changing just the import statement to import ResourceConfig from Jersey 1.x package. Here is how the InfoResource in com.example.jersey1 should look like:
package com.example.jersey1; import javax.ws.rs.GET; import javax.ws.rs.core.Context; import com.sun.jersey.api.core.ResourceConfig; public class InfoResource { private @Context ResourceConfig rc; @GET public String get() { return (String) rc.getProperty("example.text"); } }
One important thing to note here is I haven’t annotated InfoResource using the @Path annotation. This is to make sure it does not get picked up by the package scanning (otherwise it would result in Jersey 2-specific resource being loaded when only Jersey 1.x runtime is present or vice-versa). We will use the “explicit root resources” feature from Jersey 1.x and resource builder API from Jersey 2.0 to register this unannotated resource into the Jersey runtime. We need to implement a custom ResourceConfig class for that.
Implementing ResourceConfig
Add ExampleResourceConfig class to com.example.jersey2 package and configure it to scan “com.example” package for resources and providers and map InfoResource to “info” path using the resource builder API. Here is the source code:
package com.example.jersey2; import java.util.ArrayList; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.model.Resource; import org.glassfish.jersey.server.model.ResourceModelIssue; public class ExampleResourceConfig extends ResourceConfig { public ExampleResourceConfig() { // look for resources in com.example package and its subpackages this.packages("com.example"); // create a resource builder from the InfoResource class // (sending in dummy issues list - we don't expect introspection issues) Resource.Builder rb = Resource.builder( InfoResource.class, new ArrayList<ResourceModelIssue>() ); // bind it to "info" path rb.path("info"); // add it to the resource config this.addResources(rb.build()); } }
Add the Jersey 1.x version of the same class into the com.example.jersey1 package. It should look as follows:
package com.example.jersey1; import com.sun.jersey.api.core.PackagesResourceConfig; // extend PackagesResourceConfig to get the package scanning functionality public class ExampleResourceConfig extends PackagesResourceConfig { public ExampleResourceConfig() { // pass name of the package to be scanned super("com.example"); // add InfoResource as an explicit resource bound to "info" path this.getExplicitRootResources().put("info", InfoResource.class); } }
Adding Link to InfoResource to the index.jsp
To be able to conveniently access the InfoResource, let’s add a link to the index.jsp page. Here is how the updated index.jsp looks like:
<html> <body> <h2>Jersey RESTful Web Application!</h2> <p>Jersey resource <p>InfoResource <p>Visit Project Jersey website for more information on Jersey! </body> </html>
Updating web.xml – Putting It All Together
And finally we will use the PortableServletContainer to put this all together. Open web.xml and replace the use of ServletContainer with the use of PortableServletContainer. It will have two sets of init-parameters – one Jersey 1.x specific – prefixed with jersey1#, the other one Jersey 2.0 specific prefixed with jersey2#. The servlet will figure out automatically what version of Jersey runtime is present and use the right init-params subset. Btw, obviously you don’t have to add jersey1/2# prefix to the parameter name if that parameter should be present and have the same value regardless of Jersey version being used. Here is how the updated web.xml should look like:
Jersey Web Application org.glassfish.jersey.servlet.portability.PortableServletContainer jersey1#javax.ws.rs.Application com.example.jersey1.ExampleResourceConfig jersey1#example.text Using Jersey 1.x jersey2#javax.ws.rs.Application com.example.jersey2.ExampleResourceConfig jersey2#example.text Using Jersey 2.0 1 Jersey Web Application /webapi/*
Trying It Out
Now you can build the application and deploy the war to GlassFish 3.1.2 which bundles Jersey 1. Assuming the GlassFish is running at locahost:8080, you should be able to access the application at http://localhost:8080/simple-service/. You can see that clicking on Jersey resource works (invokes MyResource.getIt() method returning “Got it!”) and clicking on InfoResource shows “Using Jersey 1.x”.
If you deploy the same thing on the latest GlassFish 4.0 promoted build that bundles Jersey 2, you should see that after clicking on InfoResource, “Using Jersey 2.0” is displayed.
Resources
You can download a zip file with this example application at: http://java.net/projects/jersey/downloads/download/portability-example.zip
Another example can be seen in in Jersey 2.0 integration tests workspace: https://github.com/jersey/jersey/tree/master/tests/integration/portability-jersey-1