RESTful Java with JAX-RS 2.0 (2013)
Part II. JAX-RS Workbook
Chapter 21. Examples for Chapter 6
In Chapter 3, you saw a quick overview on how to write a simple JAX-RS service. You might have noticed that we needed a lot of code to process incoming and outgoing XML data. In Chapter 6, you learned that all this handcoded marshalling code is unnecessary. JAX-RS has a number of built-in content handlers that can do the processing for you. You also learned that if these prepackaged providers do not meet your requirements, you can write your own content handler.
There are two examples in this chapter. The first rewrites the ex03_1 example to use JAXB instead of handcoded XML marshalling. The second example implements a custom content handler that can send serialized Java objects over HTTP.
Example ex06_1: Using JAXB
This example shows how easy it is to use JAXB and JAX-RS to exchange XML documents over HTTP. The com.restfully.shop.domain.Customer
class is the first interesting piece of the example.
src/main/java/com/restfully/shop/domain/Customer.java
@XmlRootElement(name="customer")
public
class
Customer
{
private
int
id;
private
String
firstName;
private
String
lastName;
private
String
street;
private
String
city;
private
String
state;
private
String
zip;
private
String
country;
@XmlAttribute
public
int
getId()
{
return
id;
}
public
void
setId(int
id)
{
this.id
=
id;
}
@XmlElement(name="first-name")
public
String
getFirstName()
{
return
firstName;
}
public
void
setFirstName(String
firstName)
{
this.firstName
=
firstName;
}
@XmlElement(name="last-name")
public
String
getLastName()
{
return
lastName;
}
public
void
setLastName(String
lastName)
{
this.lastName
=
lastName;
}
@XmlElement
public
String
getStreet()
{
return
street;
}
...
}
The JAXB annotations provide a mapping between the Customer
class and XML.
You don’t need to write a lot of code to implement the JAX-RS service because JAX-RS already knows how to handle JAXB annotated classes:
src/main/java/com/restfully/shop/services/CustomerResource.java
@Path("/customers")
public
class
CustomerResource
{
private
Map<Integer,
Customer>
customerDB
=
new
ConcurrentHashMap<Integer,
Customer>();
private
AtomicInteger
idCounter
=
new
AtomicInteger();
public
CustomerResource()
{
}
@POST
@Consumes("application/xml")
public
Response
createCustomer(Customer
customer)
{
customer.setId(idCounter.incrementAndGet());
customerDB.put(customer.getId(),
customer);
System.out.println("Created customer "
+
customer.getId());
return
Response.created(URI.create("/customers/"
+
customer.getId())).build();
}
@GET
@Path("{id}")
@Produces("application/xml")
public
Customer
getCustomer(@PathParam("id")
int
id)
{
Customer
customer
=
customerDB.get(id);
if
(customer
==
null)
{
throw
new
WebApplicationException(Response.Status.NOT_FOUND);
}
return
customer;
}
@PUT
@Path("{id}")
@Consumes("application/xml")
public
void
updateCustomer(@PathParam("id")
int
id,
Customer
update)
{
Customer
current
=
customerDB.get(id);
if
(current
==
null)
throw
new
WebApplicationException(Response.Status.NOT_FOUND);
current.setFirstName(update.getFirstName());
current.setLastName(update.getLastName());
current.setStreet(update.getStreet());
current.setState(update.getState());
current.setZip(update.getZip());
current.setCountry(update.getCountry());
}
}
If you compare this with the CustomerResource
class in ex03_1, you’ll see that the code in this example is much more compact. There is no handcoded marshalling code, and our methods are dealing with Customer
objects directly instead of raw strings.
The Client Code
The client code can also now take advantage of automatic JAXB marshalling. All JAX-RS 2.0 client implementations must support JAXB as a mechanism to transmit XML on the client side. Let’s take a look at how the client code has changed from ex03_1:
@Test
public
void
testCustomerResource()
throws
Exception
{
System.out.println("*** Create a new Customer ***");
Customer
newCustomer
=
new
Customer();
newCustomer.setFirstName("Bill");
newCustomer.setLastName("Burke");
newCustomer.setStreet("256 Clarendon Street");
newCustomer.setCity("Boston");
newCustomer.setState("MA");
newCustomer.setZip("02115");
newCustomer.setCountry("USA");
We start off by allocating and initializing a Customer
object with the values of the new customer we want to create.
Response
response
=
client.target("http://localhost:8080/services/customers")
.request().post(Entity.xml(newCustomer));
if
(response.getStatus()
!=
201)
throw
new
RuntimeException("Failed to create");
The code for posting the new customer looks exactly the same as ex03_1 except that instead of initializing a javax.ws.rs.client.Entity
with an XML string, we are using the Customer
object. The JAX-RS client will automatically marshal this object into an XML string and send it across the wire.
Changes to pom.xml
JBoss RESTEasy is broken up into a bunch of smaller JARs so that you can pick and choose which features of RESTEasy to use. Because of this, the core RESTEasy JAR file does not have the JAXB content handlers. Therefore, we need to add a new dependency to our pom.xml file:
<dependency>
<groupId>
org.jboss.resteasy</groupId>
<artifactId>
resteasy-jaxb-provider</artifactId>
<version>
1.2</version>
</dependency>
Adding this dependency will add the JAXB provider to your project. It will also pull in any third-party dependency RESTEasy needs to process XML. If you are deploying on a Java EE application server like Wildfly or JBoss, you will not need this dependency; JAXB support is preinstalled.
Build and Run the Example Program
Perform the following steps:
1. Open a command prompt or shell terminal and change to the ex06_1 directory of the workbook example code.
2. Make sure your PATH is set up to include both the JDK and Maven, as described in Chapter 17.
3. Perform the build and run the example by typing maven install
.
Example ex06_2: Creating a Content Handler
For this example, we’re going to create something entirely new. The Chapter 6 example of a content handler is a reimplementation of JAXB support. It is suitable for that chapter because it illustrates both the writing of a MessageBodyReader
and a MessageBodyWriter
and demonstrates how the ContextResolver
is used. For ex06_2, though, we’re going to keep things simple.
In ex06_2, we’re going to rewrite ex06_1 to exchange Java objects between the client and server instead of XML. Java objects, you ask? Isn’t this REST? Well, there’s no reason a Java object can’t be a valid representation of a resource! If you’re exchanging Java objects, you can still realize a lot of the advantages of REST and HTTP. You still can do content negotiation (described in Chapter 9) and HTTP caching (described in Chapter 11).
The Content Handler Code
For our Java object content handler, we’re going to write one class that is both a MessageBodyReader
and a MessageBodyWriter
:
src/main/java/com/restfully/shop/services/JavaMarshaller.java
@Provider
@Produces("application/example-java")
@Consumes("application/x-java-serialized-object")
public
class
JavaMarshaller
implements
MessageBodyReader,
MessageBodyWriter
{
The JavaMarshaller
class is annotated with @Provider
, @Produces
, and @Consumes
, as required by the specification. The media type used by the example to represent a Java object is application/example-java
:[22]
public
boolean
isReadable(Class
type,
Type
genericType,
Annotation[]
annotations,
MediaType
mediaType)
{
return
Serializable.class.isAssignableFrom(type);
}
public
boolean
isWriteable(Class
type,
Type
genericType,
Annotation[]
annotations,
MediaType
mediaType)
{
return
Serializable.class.isAssignableFrom(type);
}
For the isReadable()
and isWriteable()
methods, we just check to see if our Java type implements the java.io.Serializable
interface:
public
Object
readFrom(Class
type,
Type
genericType,
Annotation[]
annotations,
MediaType
mediaType,
MultivaluedMap
httpHeaders,
InputStream
is)
throws
IOException,
WebApplicationException
{
ObjectInputStream
ois
=
new
ObjectInputStream(is);
try
{
return
ois.readObject();
}
catch
(ClassNotFoundException
e)
{
throw
new
RuntimeException(e);
}
}
The readFrom()
method uses basic Java serialization to read a Java object from the HTTP input stream:
public
long
getSize(Object
o,
Class
type,
Type
genericType,
Annotation[]
annotations,
MediaType
mediaType)
{
return
−1;
}
The getSize()
method returns –1. It is impossible to figure out the exact length of our marshalled Java object without serializing it into a byte buffer and counting the number of bytes. We’re better off letting the servlet container figure this out for us. The getSize()
method has actually been deprecated in JAX-RS 2.0.
public
void
writeTo(Object
o,
Class
type,
Type
genericType,
Annotation[]
annotations,
MediaType
mediaType,
MultivaluedMap
httpHeaders,
OutputStream
os)
throws
IOException,
WebApplicationException
{
ObjectOutputStream
oos
=
new
ObjectOutputStream(os);
oos.writeObject(o);
}
Like the readFrom()
method, basic Java serialization is used to marshal our Java object into the HTTP response body.
The Resource Class
The CustomerResource
class doesn’t change much from ex06_2:
src/main/java/com/restfully/shop/services/CustomerResource.java
@Path("/customers")
public
class
CustomerResource
{
...
@POST
@Consumes("application/example-java")
public Response createCustomer(Customer customer)
{
customer.setId(idCounter.incrementAndGet());
customerDB.put(customer.getId(), customer);
System.out.println("Created
customer
" + customer.getId());
return Response.created(URI.create("/customers/"
+
customer.getId())).build();
}
...
}
The code is actually exactly the same as that used in ex06_1, except that the @Produces
and @Consumes
annotations use the application/example-java
media type.
The Application Class
The ShoppingApplication
class needs to change a tiny bit from the previous examples:
src/main/java/com/restfully/shop/services/ShoppingApplication.java
public
class
ShoppingApplication
extends
Application
{
private
Set<Object>
singletons
=
new
HashSet<Object>();
private
Set<Class<?>>
classes
=
new
HashSet<Class<?>>();
public
ShoppingApplication()
{
singletons.add(new
CustomerResource());
classes.add(JavaMarshaller.class);
}
@Override
public
Set<Class<?>>
getClasses()
{
return
classes;
}
@Override
public
Set<Object>
getSingletons()
{
return
singletons;
}
}
For our Application
class, we need to register the JavaMarshaller
class. If we don’t, the JAX-RS runtime won’t know how to handle the application/example-java
media type.
The Client Code
The client code isn’t much different from ex06_1. We just need to modify the javax.ws.rs.client.Entity
construction to use the application/example-java
media type. For example, here’s what customer creation looks like:
src/test/java/com/restfully/shop/test/CustomerResourceTest.java
public
class
CustomerResourceTest
{
@Test
public
void
testCustomerResource()
throws
Exception
{
...
Response
response
=
client.target
("http://localhost:8080/services/customers")
.request().post(Entity.entity
(newCustomer,
"application/example-java"));
The static Entity.entity()
method is called, passing in the plain Customer
Java object along with our custom media type.
Build and Run the Example Program
Perform the following steps:
1. Open a command prompt or shell terminal and change to the ex06_2 directory of the workbook example code.
2. Make sure your PATH is set up to include both the JDK and Maven, as described in Chapter 17.
3. Perform the build and run the example by typing maven install
.
[22] The media type application/x-java-serialized-object
should actually be used, but as of the second revision of this book, RESTEasy now has this type built in.