Posts Tagged ‘portability’

Writing Web Applications That Can Run With Jersey 1.x As Well As Jersey 2.0

June 26th, 2012

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:

  1. 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)
  2. 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
                
                    1.6
                    1.6
                
            </plugin>
        </plugins>
    
    
        
            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
        
    
    <properties>
        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