gembin

OSGi, Eclipse Equinox, ECF, Virgo, Gemini, Apache Felix, Karaf, Aires, Camel, Eclipse RCP

HBase, Hadoop, ZooKeeper, Cassandra

Flex4, AS3, Swiz framework, GraniteDS, BlazeDS etc.

There is nothing that software can't fix. Unfortunately, there is also nothing that software can't completely fuck up. That gap is called talent.

About Me

 

Putting Java to REST

from http://java.dzone.com/articles/putting-java-rest

Last month I gave you an Introduction to REST. It was 100% theory, so now its time to see a little bit of REST in action. Since I am primarily a Java programmer, Part II of this series will focus on writing RESTFul Web services using the Java language. REST does not require a specific client or server-side framework in order to write your Web services. All you need is a client or server that supports the HTTP protocol. In Java land, servlets are a fine instrument for building your distributed apps, but can be a bit cumbersome and require a bunch of glue-code and XML configuration to get things going. So, about a year and a half ago, JSR-311, JAX-RS, was started at the JCP to provide an annotation-based framework to help you be more productive in writing RESTFul Web services. In this article, we'll implement various simple Web services using the JAX-RS specification.   

A Simple JAX-RS Service

JAX-RS uses Java annotations to map an incoming HTTP request to a Java method. This is not an RPC mechanism, but rather a way to easily access the parts of the HTTP request you are interested in without a lot of boilerplate code you'd have to write if you were using raw servlets. To use JAX-RS you annotate your class with the @Path annotation to indicate the relative URI path you are interested in, and then annotate one or more of your class's methods with @GET, @POST, @PUT, @DELETE, or @HEAD to indicate which HTTP method you want dispatched to a particular method.

1.@Path("/orders")
2.public class OrderEntryService {
3.   @GET
4.   public String getOrders() {...}
5.}

If we pointed our browser at http://somewhere.com/orders, JAX-RS would dispatch the HTTP request to the getOrders() method and we would get back whatever content the getOrders() method returned.

JAX-RS has a very simple default component model. When you deploy a JAX-RS annotated class to the JAX-RS runtime, it will allocate OrderEntryService object to service one particular HTTP request, and throw away this object instance at the end of the HTTP response. This per-request model is very similar to stateless EJBs. Most JAX-RS implementations support Spring, EJB, and even JBoss Seam integration. I recommend you use one of those component models rather than the JAX-RS default model as it is very limited and you will quickly find yourself wishing you had used Spring, EJB, or Seam to build your services.

Accessing Query Parameters

One problem with the getOrders() method of our OrderEntryService class is that this method could possibly return thousands of orders in our system. It would be nice to be able to limit the size of the result set returned from the system. For this, the client could send a URI query parameter to specify how many results it wanted returned in the HTTP response, i.e. http://somewhere.com/orders?size=50. To extract this information from the HTTP request, JAX-RS has a @QueryParam annotation:

01.@Path("/orders")
02.public class OrderEntryService {
03.   @GET
04.   public String getOrders(@QueryParam("size")
05.                           @DefaultValue("50") int size)
06.   {
07.     ... method body ...
08.   }
09.}

The @QueryParam will automatically try and pull the "size" query parameter from the incoming URL and convert it to an integer. @QueryParam allows you to inject URL query parameters into any primitive type as well as any class that has a public static valueOf(String) method or a constructor that has one String parameter. The @DefaultValue annotation is an optional piece of metadata. What it does is tell the JAX-RS runtime that if the "size" URI query parameter is not provided by the client, inject the default value of "50".

Other Parameter Annotations

There are other parameter annotations like @HeaderParam, @CookieParam, and @FormParam that allow you to extract additional information from the HTTP request to inject into parameters of your Java method. While @HeaderParam and @CookieParam are pretty self explanatory, @FormParam allows you to pull in parameters from an application/x-www-formurlencoded request body (an HTML form). I'm not going to spend much time on them in this article as the behave pretty much in the same way as @QueryParam does.

Path Parameters and @PathParam

Our current OrderEntryService has limited usefulness. While getting access to all orders in our system is useful, many times we will have web service client that wants access to one particular order. We could write a new getOrder() method that used @QueryParam to specify which order we were interested in. This is not good RESTFul design as we are putting resource identity within a query parameter when it really belongs as part of the URI path itself. The JAX-RS specification provides the ability to define named path expressions with the @Path annotation and access those matched expressions using the @PathParam annotation. For example, let's implement a getOrder() method using this technique.

1.@Path("/orders")
2.public class OrderEntryService {
3.   @GET
4.   @Path("/{id}")
5.   public String getOrder(@PathParam("id") int orderId) {
6.     ... method body ...
7.   }
8.}

The {id} string represents our path expression. What it initially means to JAX-RS is to match an incoming's request URI to any character other than '/'. For example http://somewhere.com/orders/3333 would dispatch to the getOrder() method, but http://somewhere.com/orders/3333/entries would not. The "id" string names the path expression so that it can be referenced and injected as a method parameter. This is exactly what we are doing in our getOrder() method. The @PathParam annotation will pull in the information from the incoming URI and inject it into the orderId parameter. For example, if our request is http://somewhere.com/orders/111, orderId would get the 111 value injected into it.

More complex path expressions are also supported. For example, what if we wanted to make sure that id was an integer? We can use Java regular expressions in our path expression as follows:

1.@Path("{id: \\d+}")

Notice that a ':' character follows "id". This tells JAX-RS there is a Java regular expression that should be matched as part of the dispatching process.

Content-Type

Our getOrder() example, while functional, is incomplete. The String passed back from getOrder() could be any mime type: plain text, HTML, XML, JSON, YAML. Since we're exchanging HTTP messages, JAX-RS will set the response Content-Type to be the preferred mime type asked for by the client (for browsers its usually XML or HTML), and dump the raw bytes of the String to the response output stream.

You can specify which mime type the method return type provides with the @Produces annotation. For example, let's say our getOrders() method actually returns an XML string.:

01.@Path("/orders")
02.public class OrderEntryService {
03.   @GET
04.   @Path("{id}")
05.   @Produces("application/xml")
06.   public String getOrder(@PathParm("id") int orderId)
07.   {
08.      ...
09.   }
10.}

Using the @Produces annotation in this way would cause the Content-Type of the response to be set to "application/xml".

Content Negotiation

HTTP clients use the HTTP Accept header to specify a list of mime types they would prefer the server to return to them. For example, my Firefox browser sends this Accept header with every request:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

The server should interpret this string as that the client prefers html or xhtml, but would accept raw XML second, and any other content type third. The mime type parameter "q" in our example identifies that "application/xml" is our 2nd choice and "*/*" (anything) is our third choice (1.0 is the default "q" value if "q" is not specified).

JAX-RS understands the Accept header and will use it when dispatching to JAX-RS annotated methods. For example, let's add two new getOrder() methods that return HTML and JSON.

01.@Path("/orders")
02.public class OrderEntryService {
03.   @GET
04.   @Path("{id}")
05.   @Produces("application/xml")
06.   public String getOrder(@PathParm("id") int orderId)  {...}
07. 
08.   @GET
09.   @Path("{id}")
10.   @Produces("text/html")
11.   public String getOrderHtml(@PathParm("id") int orderId) {...}
12. 
13.   @GET
14.   @Path("{id}")
15.   @Produces("application/json")
16.   public String getOrderJson(@PathParm("id") int orderId) {...}
17.}

If we pointed our browser at our OrderEntryService, it would dispatch to the getOrderHtml() method as the browser prefers HTML. The Accept header and each method's @Produces annotation is used to determine which Java method to dispatch to.

Content Marshalling

Our getOrder() method is still incomplete. It still will have a bit of boilerplate code to convert a list of orders to an XML string that the client can consume. Luckily, JAX-RS allows you to write HTTP message body readers and writers that know how to marshall a specific Java type to and from a specific mime type. The JAX-RS specification has some required built-in marshallers. For instance, vendors are required to provide support for marshalling JAXB annotated classes (JBoss RESTEasy also has providers for JSON, YAML, and other mime types). Let's expand our example:

01.@XmlRootElement(name="order")
02.public class Order {
03.   @XmlElement(name="id")
04.   int id;
05. 
06.   @XmlElement(name="customer-id")
07.   int customerId;
08. 
09.   @XmlElement("order-entries")
10. 
11.   List<OrderEntry> entries;
12....
13.}
14. 
15.@Path("/orders")
16.public class OrderEntryService {
17.   @GET
18.   @Path("{id}")
19.   @Produces("application/xml"
20.   public Order getOrder(@PathParm("id") int orderId) {...}
21.}

JAX-RS will see that your Content-Type is application/xml and that the Order class has a JAXB annotation and will automatically using JAXB to write the Order object to the HTTP output stream. You can plug in and write your own marshallers using the MessageBodyReader and Writer interfaces, but we will not cover how to do this in this article.

Response Codes and Custom Responses

The HTTP specification defines what HTTP response codes should be on a successful request. For example, GET should return 200, OK and PUT should return 201, CREATED. You can expect JAX-RS to return the same default response codes.

Sometimes, however, you need to specify your own response codes, or simply to add specific headers or cookies to your HTTP response. JAX-RS provides a Response class for this.

01.@Path("/orders")
02.public class OrderEntryService {
03.   @GET
04.   @Path("{id}")
05.   public Response getOrder(@PathParm("id") int orderId)
06.   {
07.      Order order = ...;
08.      ResponseBuilder builder = Response.ok(order);
09.      builder.expires(...some date in the future);
10.      return builder.build();
11.   }
12.}

In this example, we still want to return a JAXB object with a 200 status code, but we want to add an Expires header to the response. You use the ResponseBuilder class to build up the response, and ResponseBuilder.build() to create the final Response instance.

Exception Handling

JAX-RS has a RuntimeException class, WebApplicationException, that allows you to abort your JAX-RS service method. It can take an HTTP status code or even a Response object as one of its constructor parameters. For example

01.@Path("/orders")
02.public class OrderEntryService {
03.   @GET
04.   @Path("{id}")
05.   @Produces("application/xml")
06.   public Order getOrder(@PathParm("id") int orderId) {
07.      Order order = ...;
08.      if (order == null) {
09.         ResponseBuilder builder = Response.status(Status.NOT_FOUND);
10.         builder.type("text/html");
11.         builder.entity("<h3>Order Not Found</h3>");
12.         throw new WebApplicationException(builder.build();
13.      }
14.      return order;
15.   }
16.}

In this example, if the order is null, send a HTTP response code of NOT FOUND with a HTML encoded error message.

Beyond WebApplicationException, you can map non-JAXRS exceptions that might be thrown by your application to a Response object by registering implementations of the ExceptionMapper class:

1.public interface ExceptionMapper<E extends Throwable>
2.{
3.   Response toResponse(E exception);
4.}

For example, lets say we were using JPA to locate our Order objects. We could map javax.persistence.EntityNotFoundException to return a NOT FOUND status code.

01.@Provider
02.public class EntityNotFoundMapper
03.    implements ExceptionMapper<EntityNotFoundException>
04.{
05.   Response toResponse(EntityNotFoundException exception)
06.   {
07.      return Response.status(Status.NOT_FOUND);
08.   }
09.}

You register the ExceptionMapper using the Application deployment method that I'll show you in the next section.

Deploying a JAX-RS Application

Although the specification may expand on this before Java EE 6 goes final, it provides only one simple way of deploying your JAX-RS applications into a Java EE environment. First you must implement a javax.ws.rs.core.Application class.

1.public abstract class Application
2.{
3.   public abstract Set<Class<?>> getClasses();
4.   public Set<Object>getSingletons()
5.}

The getClasses() method returns a list of classes you want to deploy into the JAX-RS environment. They can be @Path annotated classes, in which case, you are specifying that you want to use the default per-request component model. These classes could also be a MessageBodyReader or Writer (which I didn't go into a lot of detail), or an ExceptionMapper. The getSingletons() method returns actual instances that you create yourself within the implementation of your Application class. You use this method when you want to have control over instance creation of your resource classes and providers. For example, maybe you are using Spring to instantiate your JAX-RS objects, or you want to register an EJB that uses JAX-RS annotations.

You tell the JAX-RS runtime to use the class via a <context-param> within your WAR's web.xml file

1.<context-param>
2.      <param-name>javax.ws.rs.core.Application</param-name>
3.      <param-value>com.smoke.MyApplicationConfig</param-value>
4.</context-param>

Other JAX-RS Features

There's a bunch of JAX-RS features I didn't go into detail with or mention. There's a few helper classes for URI building and variant matching as well as classes for encapsulating HTTP specification concepts like Cache-Control. Download the specification and take a look at the Javadocs for more information on this stuff.

Test Drive JAX-RS

You can test drive JAX-RS through JBoss's JAX-RS implementation, RESTEasy available at http://jboss.org/resteasy.

About the Author

Bill Burke is an engineer and Fellow at JBoss, a division of Red Hat. He is JBoss's representative on JSR-311, JAX-RS and lead on JBoss's RESTEasy implementation.



posted on 2009-07-10 17:38 gembin 阅读(1104) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(6)

随笔分类(440)

随笔档案(378)

文章档案(6)

新闻档案(1)

相册

收藏夹(9)

Adobe

Android

AS3

Blog-Links

Build

Design Pattern

Eclipse

Favorite Links

Flickr

Game Dev

HBase

Identity Management

IT resources

JEE

Language

OpenID

OSGi

SOA

Version Control

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜

free counters