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.
For
more on HTTP methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
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
For more on annotations http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-67000A
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.
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.
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 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
No comments:
Post a Comment