Follow my blog with Bloglovin

Thursday, May 3, 2012

Easy REST with JAX-RS and RESTEasy

REST with JAX-RS
What is REST?

REST stands for Representational State Transfer. It’s an architectural style for building lightweight network software (client/server) over a stateless, cacheable, client-server communication protocol. As HTTP is most widely used protocol on the web it is most widely used (almost all implementation use this only). HTTP describes methods like GET, PUT, POST etc. and work on URI. This makes it suitable for defining, locating resource and defining operations on them. HTTP is simple and well established protocol for communicating on web.

Software architecture is defined by a configuration of architectural elements-components, connectors, and data--constrained in their relationships in order to achieve a desired set of architectural properties. [Roy Fielding]

Resources are located by URI and represented in any format [XML, JSON, CSV or any MIME type]. Operation and communication are defined by HTTP methods like GET, POST, PUT, DELETE, HEAD etc.

There is no W3C recommendation for REST like there is for Web Services (WSDL). A contract for published service should be defined and documented for the client to discover what resources and operations are available. There is a submission made by Sun Microsystems called WADL for defining resources in web applications.


Java standardizes this in JSR 311 called Java API for XML – RESTful Services.

Why REST?
  • It’s a light weight and simple web service then complex RPC mechanism like CORBA, XML-RPC and Web Services.
  • Like web services it is platform independent, language independent and standard based as it runs on top of HTTP.
  • Even an AJAX (Java Script) client can call a REST service. No need of stubs or complex request format (SOAP request).
  • Can handle complex request/response and any MIME type.  Can use any format for representation of resource in request/response. XML provide type safety and a predefine contract.
  • REST based systems are scalable and simple.
  • Authentication/Authorization and security handled as in HTTP. Something like OAuth or simpler basic or form based authentication or other mechanism can be used.
How to implement in Java?

There are many implementations available for JAX-RS. Jersey is the production quality reference implementation, other implementation like Restlet, RESTEasy are available which provide complete implementation of JAX-RS and also provide rich support for client side and other functionality like more lifecycle support for Resource class like Singleton or PerSession. JAX-RS only specifies per request scope lifecycle.

JAX-RS JSR-311

This is the JEE specification for creating RESTful services in Java. A brief summary of this specification for is presented in this topic.
It consists of Resources and/or Providers and resource methods to operate on resource. 

Resource: A web resource class represents a resource published on an URI. This path is specified with @Path annotation at class level or method level. The path is a URL which is used to access the resource.    JAX-RS provides a way to parameterize the URL.
A resource class must have a public constructor. Parameters of the constructor must be annotated with @Context, @HeaderParam, @CookieParam, @MatrixParam, @QueryParam or @PathParam annotation, as these are the only data available for JAX-RS runtime.  If more than one constructor is provided then the constructor with matching most of parameters will be used. As while instantiating resource classes the JAX-RS runtime will have only information from environment, HTTP headers, Cookies, request URL, so they can pass parameters only from these sources. For more information on these annotations http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-220003.2

Fields (class level variables) can also be annotated and will be injected by JAX-RS runtime after construction of object of resource class.

This class must have one or more resource methods annotated with HttpMethod like GET PUT, POST to operate on resources.

This class can define other annotations like @Produces, @Consumes or @ApplicationPath. If defined at class level will be common to all methods. Methods can override them individually. Produces and Consumes describes what media types resource can accept and provides. A resource can provide/accept more than one media type. By means of content negotiation clients specify request and response format they want. JAX-RS runtimes provides content negotiation by means HTTP headers (Content-Type and Accept) and URL suffix.

Lifecycle of a Resource
A resource class is instantiated for each request and after serving the request it is dereference for garbage collection. Implementers of the specifications can provide other strategy for lifecycle management. Jersey  and RESTEasy provides Singleton and PerSession strategy. A IoC (like Spring) based lifecycle management implementation like Apache Wink can support all lifecycle strategy supported by container.
Resource methods are public methods in resource classes with designated HttpMethod. They can override @Path annotation at class level.

Facts about resource methods:
  • They must be public.
  • All the parameters except one must be annotated with @Context, @HeaderParam, @CookieParam, @MatrixParam, @QueryParam or @PathParam. Only one parameter which is not annotated will be mapped to message body.
  • Parameters can be annotated with @DefaultValue to supply default value and @Encoded to get value as it was requested by client without decoding.
  • The type of parameters must have a no argument constructor or a single argument of type String or a static method named valueOf or fromString accepting a String parameter.
Providers are means of extending JAX-RS. Entity providers provide capability to handle different media types or handling media types in custom ways.  They map representation of resources (may be MIME types) and Java types to and fro. There are two types of providers: MessageBodyReader (for representation to Java type) and MessageBodyWriter (for Java types to representation). They can declare what media types they can handle.

Other providers are Context providers and Exception mapping provider.

Lifecycle of a Provider
Providers are singleton in web application. JAX-RS runtime create single instance of provider class.

JAX-RS Annotations

Configuration and deployment of application
Configuration needs a subclass of Application which provides a Set of classes which are Resource.
This can use runtime class scan or static Set of classes. This configuration subclass can be supplied depends on type of deployment.
In standalone application deployment RuntimeDelegate.createEndpoint method accepts the Application subclass and type of resource and returns Resource(s).

Servlet deployment will be described in example.

Example implementation with RESTEasy

In this example we will describe how to create a resource class. This class will allow creation and retrieval of resource. Update and delete are similar to create (just change the annotation). We will also create provider to map Java type to resource representation and vice versa using XStream.

In this tutorial we will do following:
  • Publish a REST service.
  • Make the resource support XML and JSON and use content negotiation using HTTP headers to get and post representation of required and supported type.
  • Create a custom provider to support mapping between Java types and supported representations.
  • Test using Mozilla REST client.
  • Write clients for published service.
Softwares used:

Eclipse Galileo for JEE
JDK 1.6+
RESTEasy 2.3
XStream 1.4.2

Step  #1 Create a dynamic web project in eclipse named REST, or any other name. If any other name is used then make modification throughout the tutorial. Rename WebContent directory to JAXRS [not required just for convenience].

Step #2 Extract RESTEasy and XStream and copy jars from these distributions (lib directory) to project and add to projects build path. To do so follow these steps:



Copy jars specifies in screenshot into JAXRS [renamed from WebContent]/lib directory.
After that right click on project and go to properties. In Build Bath select on Add Jars and browse through the project folder and select the jars.

Step #3 Create a class named ERecource and copy following content into it.
EResource.java

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;

/**
 * This is the resource class for demonstration.
 * It will be published at path /resource as specified in @Path annotation. This can be overridden
 * for sub resource, the path specified in sub resource will be computed relative to the class level @Path
 * All the methods will accept and produce data in XML and JSON format as specifies in @Produces and
 * @Consumes annotation. These annotations can be overridden in resource methods if required.
 *
 *
 */
@Path("resource")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class EResource {

                private static int count = 0;
                                
                @POST
                public int createOrder(Order order){
                                return count++;
                }
                

                @GET
                public Order getOrder(){
                                Order order = new Order();
                                order.setId(count++);
                                order.setName("name");
                                return order;
                }
               
                @GET
                @Path("/{id}")
                public Order getOrder(@PathParam("id") int id){
                                Order order = new Order();
                                order.setId(id);
                                order.setName("name");
                                return order;
                }
               
                /**
                 * This method describes how to access headers, cookies and context information.
                 * @param headers : access all headers.
                 * @param info : access all URI components
                 * @param id : access a path param by name and assign it to a variable.
                 * @param accept : access a http header by name.
                 * @return
                 */
                @GET
                @Produces(MediaType.TEXT_PLAIN)
                public String extractNReturnRequestData(@Context HttpHeaders headers, @Context UriInfo info, @DefaultValue("2")@PathParam("id") String id, @HeaderParam(HttpHeaders.ACCEPT) String accept){
                             
           }
}

Explanation:

    @POST
    public int createOrder(Order order) will be called on POST request from client to URL http://<host>:<port>/REST/resource. This method expects an instance of Order. This instance is provided by JAX-RS runtime. This requires JSON or XML representation depends on Content-Type header specified by client.

  @GET
   public Order getOrder() will be called on GET request from client to URL http://<host>:<port>/REST/resource without any parameter.

  @GET
  @Path("/{id}")
   public Order getOrder(@PathParam("id") int id) will be called on GET request from client to URL http://<host>:<port>/REST/resource/<id> with a parameter of type integer. This describes how to specify path parameters by URL template and accessing them in method.
         
@GET
@Produces(MediaType.TEXT_PLAIN)
 public String extractNReturnRequestData(@Context HttpHeaders headers, @Context UriInfo info, @DefaultValue("2")@PathParam("id") String id, @HeaderParam(HttpHeaders.ACCEPT) String accept) will be called on GET request from client to URL http://<host>:<port>/REST/resource with accept header as "text/pain".

The resource methods can expect (as parameter)/and provide (as response) any media type. see MediaType class for detail. Just mention the media type in @Consumes/@Produces annotations. Also Response and ResponseBuilder classes can be used to return responses with additional headers or more detail. To return Image or PDF InputStream can be used.

Content negotiation:
This class produces and consumes XML and JSON representation. For specifying what content type is sent and expected by client Accept and Content-Type headers are used. Resource publishes this with annotations. RESTEasy also provides URL based content negotiation. In this way content type is determined by ending suffix of URL. To do so following changes are required in web.xml:
<context-param>
        <param-name>resteasy.media.type.mappings</param-name>
        <param-value>html : text/html, json : application/json, xml : application/xml</param-value>
</context-param>
This is not covered in this tutorial. For more details look at http://docs.jboss.org/resteasy/2.0.0.GA/userguide/html/JAX-RS_Content_Negotiation.html


Exception handling:
As HTTP defines error and success codes for response, any exception in resource methods mapped to WebApplicationException and sent to client with appropriate code. Any unexpected exception will be mapped to HTTP error code 500 (internal server error).

Step #4 Create a class Order (a DAO to transfer content) and paste the content into it.
Order.java

public class Order {

      int id;
      String name;
      public int getId() {
            return id;
      }
      public void setId(int id) {
            this.id = id;
      }
      public String getName() {
            return name;
      }
      public void setName(String name) {
            this.name = name;
      }
     
}

We will exchange XML/JSON representation of this class in our REST service the EResource class.

Step #5 Create a class XStreamProvider to implement provider and paste following content into it.
XStreamProvider.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * This class is an provider which provide mapping of Java types and representations (XML and JSON).
 * This provider accepts and produces XML and JSON using XStream.
 */
@Provider
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class XStreamProvider implements MessageBodyReader<Object>, MessageBodyWriter<Object> {

                @Override
                public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2,
                                                MediaType arg3) {
//isCompatible should be used instead of equals if parameters like encoding or locale does not matter.
                                if(arg3.isCompatible(MediaType.APPLICATION_JSON_TYPE) || arg3.isCompatible(MediaType.APPLICATION_XML_TYPE))
                                                return true;
                                else
                                                return false;
                }

                @Override
                public Object readFrom(Class<Object> arg0, Type arg1, Annotation[] arg2,
                                                MediaType arg3, MultivaluedMap<String, String> arg4,
                                                InputStream arg5) throws IOException, WebApplicationException {
                                if(arg3.equals(MediaType.APPLICATION_JSON_TYPE)){
                                                XStream stream = new XStream(new JsonHierarchicalStreamDriver());
                                                return stream.fromXML(arg5);
                                }else if(arg3.isCompatible(MediaType.APPLICATION_XML_TYPE)){
                                                XStream stream = new XStream(new DomDriver());
                                                return stream.fromXML(arg5); 
                                }else {
                                                throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE);
                                }
                }

                @Override
                public long getSize(Object arg0, Class<?> arg1, Type arg2,
                                                Annotation[] arg3, MediaType arg4) {
                                XStream stream = null;
                                if(arg4.equals(MediaType.APPLICATION_JSON_TYPE)){
                                                stream = new XStream(new JsonHierarchicalStreamDriver());
                                }else{
                                                stream = new XStream(new DomDriver());
                                }
                                long l = stream.toXML(arg0).length();
                                System.out.println(l);
                                return l;
                }

                @Override
                public boolean isWriteable(Class<?> arg0, Type arg1, Annotation[] arg2,
                                                MediaType arg3) {
                                if(arg3.equals(MediaType.APPLICATION_JSON_TYPE) || arg3.equals(MediaType.APPLICATION_XML_TYPE))
                                                return true;
                                else
                                                return false;
                }

                @Override
                public void writeTo(Object arg0, Class<?> arg1, Type arg2,
                                                Annotation[] arg3, MediaType arg4,
                                                MultivaluedMap<String, Object> arg5, OutputStream arg6)
                                                throws IOException, WebApplicationException {
                                if(arg4.equals(MediaType.APPLICATION_JSON_TYPE)){
                                                XStream stream = new XStream(new JsonHierarchicalStreamDriver());
                                                arg6.write(stream.toXML(arg0).getBytes());
                                                arg6.flush();
                                }else if(arg4.equals(MediaType.APPLICATION_XML_TYPE)){
                                                XStream stream = new XStream(new DomDriver());
                                                arg6.write(stream.toXML(arg0).getBytes());
                                                arg6.flush();
                                }else {
                                                System.out.println(arg0);
                                                throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE);
                                }
                }
}

This provider provides mapping between any Java type and XML/JSON representation. By default RESTEasy or any other JAX-RS runtime provide this mapping. It is not required to write provider. This is just to describe how to write Providers. Its about specifying what media types it can support and implement two interfaces.

Step #6 Make following change in web.xml:

<?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_2_5.xsd"
      id="WebApp_ID" version="2.5">
      <display-name>REST</display-name>
      <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>

      <context-param>
            <param-name>resteasy.scan</param-name>
            <param-value>true</param-value>
      </context-param>
</web-app>

Register listener ResteasyBootstrap to scan resource and provider classes and context-param  resteasy.scan with value true. Also add HttpServletDispatcher to handle request and response. JAX-RS runtime is designed around this servlet.

Step #7 Deploy the service in servlet container. This can be done using either registering Tomcat (or any other container) with eclipse or deploying it to tomcat manually. Before that copy compiled class files from build directory to JAXRS/WEB-INF/classes (create classes folder).

Approach #1 Register servlet container into eclipse.
Register the container into eclipse and right click on project and click on Run As … -> Run on Server.
In this approach the resource will be published with REST as context root.

Approach #2 Deploy manually:
Copy JAXRS directory into tomcat’s webapps directory and start tomcat.
In this approach the resource will be published with JAXRS as context root.

Make adjustment to URL in accessing the resource as per context root. 

All the manual tasks like copying class files can be automated using Ant script.

Step #8 Test it with Mozilla RESTClient (any other tool like SoapUI can be used)

Test 1: GET request without id with no accept header.
 As no header specified for content negotiation, the first appearing in list of media types specified in Produces/Consumes annotation will be used. So response is sent in XML representation.

Test 2: GET request with id with accept header.
As id (23) is specified in path param and accept HTTP header is specified as "application/json". So the method expecting a integer param will be called and response will be sent in JSON representation. If this accept media type is not supported by resource an HTTP 406 (unacceptable) will be sent as error.

Test 3: POST request
 Here we are sending POST request with XML representation of Order, which will be saved and identifier will sent back to client.

Step #9 Write clients for these resource methods.

Apache HTTPClient: For this copy httpclient-*.jar and httpcore-*.jar from RESTEasy distribution's lib to project and add to project build path. Then, add a class Client with following content.

Client.java

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;



public class Client {

    public static void main(String[] args) throws Exception {
        HttpClient client = new DefaultHttpClient();
        HttpGet get = new HttpGet("http://localhost:8080/REST/resource/12");
        System.out.println(getResult(client, get));
       
        get.addHeader("Accept", "application/json");
        System.out.println(getResult(client, get));
       
        HttpPost post = new HttpPost("http://localhost:8080/REST/resource");
        post.addHeader("Content-Type", "application/xml");
        post.setEntity(new StringEntity("<Order><id>12</id><name>name</name></Order>"));
        System.out.println(getResult(client, post));
    }
   
    private static String getResult(HttpClient client, HttpRequestBase method) throws Exception, IOException{
        HttpResponse response = client.execute(method);
        if(response.getStatusLine().getStatusCode()==200){
            return EntityUtils.toString(response.getEntity());
        }else{
            throw new Exception(response.getStatusLine().toString());
        }
    }
}

For more on RESTEasy client see
http://docs.jboss.org/resteasy/2.0.0.GA/userguide/html/RESTEasy_Client_Framework.html
http://docs.jboss.org/resteasy/2.0.0.GA/userguide/html/AJAX_Client.html


RESTEasy has support for client side for Java and Javascript both. A Javascript client can be generated.

Refrences:
JSR-311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html

Popular Posts