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:
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.