Thursday, June 10, 2010

Response objects and the use of GenericEntity class with RESTEasy

Recently during the implementation of a REST API, I wanted to return a complex response containing a list of objects (Patients). The issue was that the RESTEasy build-in JAXB MessageBodyWriter could not directly handle lists of JAXB objects (Java has trouble obtaining generic type information at runtime).

I was recently in a situation where I had to create a complex response to a HTTP POST for my REST API. I am using JAXB /JSON support from RESTEasy.

I found some element of answer in the book "RESTFul Java with JAX-RS" from Bill Burke (pp 102). However the code snippet had a couple of errors:

  • the GenericEntity object cannot be passed to the Response.ok() method directly (a ResponseBuilder is required).

  • references to GenericEntity needs to be parameterized.

My use case is a little more complex than in the book. I am receiving a user-name and password from a POST (e.g. a form submit). I then perform the authentication and returns a list of Patient objects in a JSON/GZIP compressed format (instead a list of Customer objects) together with an authentication token.





The resulting code looks like this:


   @POST
   @Path("/token")
   @Consumes("application/x-www-form-urlencoded")
   @Produces("application/json")
   @GZIP
   public Response getPatientsWithToken(@FormParam("username") String username, @FormParam("password") String password) {
  
        Login login = new Login(username, password);
        // ... perform authentication here ....
    
        // Build the returning patient list
        List<Patient> returnList = new ArrayList<Patient>();
        returnList.addAll(patients.values());
        Collections.sort(returnList);
      
        GenericEntity<List<Patient>> entity = new GenericEntity<List<Patient>>(returnList){};
      
        // Create the response
        ResponseBuilder builder = Response.ok(entity);
        return builder.build();
   }



Of course you will have to import the following classes as well:


import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

Wednesday, June 9, 2010

Open APIs: State of the Market, May 2010

Today, I was looking at the presentation from John Musser related to Open APIs (see below). Even though these statistics comes mainly from mashup and consumer applications, I was surprised by the fact that REST APIs are gaining market shares over SOAP APIs so rapidly.

In B2B and in the enterprise world in general SOAP is often the top choice. The advantages for SOAP often mentioned are:
  • Type checking (via the WSDL files)
  • Availability of development tools
On the other hand, REST offers the following:
  • Lightweight and easy to build
  • Human Readable Results
  • Extensibility
  • Scalability

In Health Care, SOAP is still widespread and prevalent. However there are some interesting projects such as NHIN Direct Health Information Exchange where the relevance of REST vs other API protocols are discussed.

It will be interesting to see what will be the outcome of such discussions.

Tuesday, June 1, 2010

JAXB-JSON Rest API using RESTEasy on JBoss EAP

In this second part of my evaluation of JBoss RESTEasy, I focus on adapting the JAXB-JSON samples provided by RESTEasy for JBoss Enterprise Application Platform 5.0.1.

Earlier I find myself to adapt the maven POM file to have the proper dependencies for the Twitter RESTEasy client.

Initially this simple JAXB-JSON sample had been designed to run on Jetty Web Server which run fine out-of-the box. However I had to make some modifications to the original project structure to have the code running as a simple eclipse project that can be deploy on JBoss EAP 5.0.X from RedHat (this will also work on Jboss community edition).

The new project (eclipse) structure looks as below:





Notice that I have also moved the code for both packages:  org.jboss.resteasy.annotations.providers.jaxb.json
org.jboss.resteasy.plugins.providers.jaxb.json
at the root of my project, since I am not using the remaining code of the example.

I also made some additional adaptations for JBoss to some of the project files including:
  • pom.xml file
  • web.xml
I also added a small test suite to test the REST API operations using JUnit which will work after the first deployment (I run JBoss locally on http://localhost:8080).

By the way, make sure you have src/main/resources/META-INF/services/javax.ws.rs.ext.Providers included in your project.









Here is the content of my new pom.xml file for JBoss:

<?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>org.jboss.resteasy.examples</groupId>
    <artifactId>jaxb-json</artifactId>
    <version>0.1.0</version>
    <packaging>war</packaging>
    <name/>
    <description/>

    <repositories>
        <repository>
            <id>java.net</id>
            <url>http://download.java.net/maven/1</url>
            <layout>legacy</layout>
        </repository>
        <repository>
            <id>maven repo</id>
            <name>maven repo</name>
            <url>http://repo1.maven.org/maven2/</url>
        </repository>
        <!-- For resteasy -->
        <repository>
            <id>jboss</id>
            <name>jboss repo</name>
            <url>http://repository.jboss.org/maven2</url>
        </repository>
    </repositories>
    <dependencies>
    
        <!-- core library -->
        
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>1.2.1.GA</version>
            <!-- filter out unwanted jars -->
            <exclusions>
                <exclusion>
                    <groupId>commons-httpclient</groupId>
                    <artifactId>commons-httpclient</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>tjws</groupId>
                    <artifactId>webserver</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <!-- optional modules -->
        
      <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jettison-provider</artifactId>
            <version>1.2.1.GA</version>
        </dependency>
        
        <!-- modules already provided by Java 6.0 -->
         
   <dependency>
       <groupId>javax.xml.bind</groupId>  
       <artifactId>jaxb-api</artifactId>
       <version>2.1</version>
       <scope>provided</scope>
   </dependency>
   
    <dependency>
         <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.1</version>
                 <scope>test</scope>
             </dependency>
     
    </dependencies>

    <build>
        <finalName>jaxb-json</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jboss-maven-plugin</artifactId>
                <version>1.4</version>
                <configuration>
                    <jbossHome>C:\JBoss\EnterprisePlatform-5.0.0.GA\jboss-as</jbossHome>
             <contextPath>/</contextPath>
             <serverName>default</serverName>
             <fileName>target/jaxb-json.war</fileName>
          </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


I modified the web.xml for the URL looks more simple by removing the mapping to reasteasy:

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
   <display-name>Archetype Created Web Application</display-name>

   <context-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>org.jboss.resteasy.examples.service.LibraryApplication</param-value>
   </context-param>

   <context-param>
      <param-name>resteasy.servlet.mapping.prefix</param-name>
      <param-value>/</param-value>
   </context-param>

   <listener>
      <listener-class>
         org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
      </listener-class>
   </listener>

   <servlet>
      <servlet-name>Resteasy</servlet-name>
      <servlet-class>
         org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
      </servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>Resteasy</servlet-name>
      <url-pattern>/</url-pattern>
   </servlet-mapping>

</web-app>

The JUnit code looks like this:

package org.jboss.resteasy.examples.test;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
  
public class LibraryTest extends TestCase
{
    /**
     * Create the test case
     *
     * @param testName name of the test case
     */
    public LibraryTest( String testName )
    {
        super( testName );
    }

    /**
     * @return the suite of tests being tested
     */
    public static Test suite()
    {
        return new TestSuite( LibraryTest.class );
    }

      
    /**
     * Testing the Library REST API
     */
    
    public void testGetMapped()  
    {
     validateRESTCall("GET", "http://localhost:8080/jaxb-json/library/books/mapped");
     assertTrue( true );
    }
    

    public void testGetBadger()  
    {
     validateRESTCall("GET", "http://localhost:8080/jaxb-json/library/books/badger");
     assertTrue( true );
    }
    
    private void validateRESTCall(String method, String url) {
     
     try {
         System.out.println("*** "+method);
         URL resURL = new URL(url);
         System.out.println("URL: " + url.toString());
         HttpURLConnection connection = (HttpURLConnection) resURL.openConnection(); 
         connection.setRequestMethod(method);
         System.out.println("Content-Type: " + connection.getContentType());
         
         BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
 
         String line = reader.readLine();
         while (line != null)
         {
            System.out.println(line);
            line = reader.readLine();
         }
         Assert.assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
         connection.disconnect();
     } catch (Exception err) { 
         System.out.print("Error in VHRResourceTest.validateRESTCall : " + err); 
        };
    }
}

To build I am using Maven (I recommend to install the maven eclipse plugin) with the goals mvn clean install compile package. Make sure also that before that you do a mvn eclipse:eclipse to update the dependencies in your project.

To deploy jaxb-json.war file from eclipse (so I don't have to manually copy the war file from the target folder), I have installed the JBoss eclipse plugin. As a result I can make it deployable (accessible by a right-click) and it appear in the Eclipse JBoss server view:


The REST API JSON Library resources are then accessible directly on a browser via http://localhost:8080/jaxb-json/library/books/mapped or http://localhost:8080/jaxb-json/library/books/badger.

If you want to compress your response, RESTEasy provides GZIP Compression/Decompression support using a very simple @GZIP annotation:

   @GET
   @Path("books/mapped")
   @Produces("application/json")
   @GZIP
   public BookListing getBooksMapped()
   {
      return getListing();
   }

Just import the following class:

import org.jboss.resteasy.annotations.GZIP;

Overall the adaptation from Jetty to JBoss was easy and the documentation very clear.

Additional discussions, recommendations and information can be found on the JBoss Community.

For an example of using REST architecture for Mobile Applications (HealthCare) see this post.