Examples for Chapter 6 - JAX-RS Workbook - RESTful Java with JAX-RS 2.0 (2013)

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.