Thursday 26 June 2014

RESTful Service with RESTEasy - Building a Sample Application (Tabernus)

Introduction


REST is an architectural style enabling networked applications to communicate with one another, typically over HTTP. It allows applications to create, retrieve, update and delete data on a server using simple HTTP requests. This is achieved using HTTP verbs such as GET, PUT, POST, DELETE and a URI describing the location of the resource.

For example, using REST we can:


Create a new Resource by invoking a POST request to
    • http://localhost:8080/RESTfulBookstore/bookstoreService/customer/
Update an existing Resource by invoking a PUT request to
    • http://localhost:8080/RESTfulBookstore/bookstoreService/customer/242
where 242 is the id of the customer whose details need to be updated.

Retrieve an existing Resource by invoking a GET request to
    • http://localhost:8080/RESTfulBookstore/bookstoreService/customer/51
where 51 is the id of the customer whose details need to be retrieved.

Delete an existing Resource by invoking a DELETE request to
    • http://localhost:8080/RESTfulBookstore/bookstoreService/customer/42
where 42 is the id of the customer whose details need to be deleted


The objective of this post is to provide an introduction to REST. To help achieve this, I developed a sample application that will be discussed below. The source for this sample application is available at here

Developing a sample application: Tabernus
My goal was to build something very simple which would help me become familiar with key concepts of the REST api and to this end I've knocked up a simple app. The scenario I'm going to model revolves around purchasing items from an online store (Tabernus). This store only sells 3 types of items: Books, Music CDs, and software.

The following diagram shows our domain entities.




A Customer can place many Orders. An Order comprises of a number of OrderItems each of which relate to a single item and specify the quantity required for that item. The API exposed by our RESTful service should enable us to perform essential CRUD tasks such as:
  • Creating a new Customer
  • Retrieving details for an existing Customer
  • Updating the details for an existing Customer
  • Deleting an existing Customer

Pre-requisites

The application was developed using java 6, Ant 1.8.4 and deployed in JBOSS AS 7.3. The following jars were also required




I also found it useful to install the WebServiceTester plugin for eclipse. This allowed me to test out urls and i could view the response that was sent back by the server.

Developing a Sample Application : Tabernus

Developing a RESTful application turned out to be quite simple and required very little code. It involved 3 main steps, which are described below.

Step 1 - Bootstrapping

A key step in implementing a RESTful application is to inform the underlying RESTful framework which class is to be used to deliver the service. Furthermore it needs to be told how that service should be deployed. The service class can be configured in one of two ways, namely as a singleton or on a per-request basis. In the former case, all HTTP requests are handled by one instance of our Service class. In a Per-request approach, a new instance of our service is instantiated to process the incoming request and is discarded at the end of that request. It should be noted that the latter implies statelessness as no service state is held between requests. This is in accordance with the HTTP protocol which is inherently stateless.

To configure our Service using either approach, we need to implement a class that extends Application. In this instance, I have opted for the per-request approach and the bootstrapping class (ApplicationConfig) is shown below,

@ApplicationPath("/")
public class ApplicationConfig extends Application {
    @SuppressWarnings("unchecked")
    public <Set<?>> getClasses() {
        return new HashSet<Class<?>>(Arrays.asList(BookstoreService.class));
   }
}

In this case, all incoming requests will be handled by the class BookstoreService.

The @ApplicationPath provides the relative base URL for our JAX-RS service.

The latest versions of resteasy don't require any configuration in the web.xml. For the most part it can be left empty, as follows:
   
  RESTfulBookstore


Step 2 - Annotation of Domain classes

A very useful feature of JAX-RS is that it provides support for (un)marshalling JAXB annotated classes.

Given below is an example of the Customer domain object.

@XmlRootElement(name="customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer implements Serializable {

    private static final long serialVersionUID = -5757865852221123124L;

    @XmlAttribute(name="id")
    private int userId;

    @XmlElement
    private String forename;
 
    @XmlElement
    private String lastname;
 
    @XmlElement
    private String email;
 
    @XmlElement
    private List<Order> orders;

    ...................
    ...................

The @XmlRootElement annotation is used to identify the class as the main XML element, <customer>. The @XmlAttribute annotation is placed above the userId field. It will be used by JAXB to insert an attribute named 'id' (rather than userId) into the <customer> tag. Note that the name() attribute can be used with the various annotations to tell JAXB, how that java property is to be named in the XML document. If left unspecified, JAXB will use the default value which is essentially the name of the annotated field. Finally the @XmlElement annotation is used to decorate the various properties of our Java class. This instructs JAXB to map this property to a subelement of the XML element representing our Customer entity.

It's worth noting that the very last property which is a collection of Order instances is also decorated in the same way.


Step 3 - Expose the Service API

Next up, I had to write the service class (BookStoreService) which exposes an API to perform CRUD operations on Customer instances. The class is declared in the following way.

@Path("/bookstoreService")

public class BookstoreService {

private static final BackendService backendService = new BackendServiceImpl();

       ....................

       ....................



The @Path annotation is used to specify a URI matching pattern for incoming HTTP requests. Typically this would be relative to the context root of your application. It is required here at the class level to enable the BookstoreService to receive HTTP requests.

Finally note that this class references BackendService which is effectively a dummy service used here to simulate a real back-end service such as an EJB service tier. In the following sections, I'll provide the method implementations for performing basic CRUD operations on Customer instances.

Retrieve Single Customer

The First operation I looked at was to retrieve an existing Customer. The implementation is listed below,

 @GET
 @Path("/customer/{id}")
 @Produces("application/*+xml")
 public Customer getCustomer(@PathParam("id") int id) throws WebApplicationException {
  
  final Customer customer = backendService.getCustomer(id);
  
  if(customer == null) {
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  }
  
  return customer;
 }


The annotation @GET marks this out to be accessible via a HTTP GET operation. The @Path annotation specifies the url pattern required to invoke this method. Note that the data-binding is made possible by using curly-bracket notation to wrap the id, i.e. {id}. A combination of these two annotations is sufficient to enable RESTeasy to map an incoming request to the Service method that needs to be invoked. The @Produces annotation is required to specify the response type. Finally the method argument is prefixed with the @PathParam annotation which indicates that this is a request parameter and needs to be passed as an argument to the method.

 The method itself is quite simple. It makes a request to the backend service to return the customer for the given customer id. It subsequently returns this as a response. Note that there is no requirement for us to transform this in anyway and all serialization is taken care of under the covers.

To test this, I wrote a simple JUnit Test. The RESTful service was deployed under JBOSS AS 7. The test is listed below:

 private Client client;

 @Before
 public void setUp() throws Exception {
  client = ClientBuilder.newClient();
 }

 @After
 public void tearDown() throws Exception {
  client = null;
 }

 /**
  * Tests that we can retrieve a specific customer using customer id
  */
 @Test
 public void testGetSpecificCustomer() {

  final int specificCustomerId = 2;

  WebTarget target = client.target("http://localhost:8080/RESTfulBookstore/bookstoreService/customer/"+String.valueOf(specificCustomerId));

  Response response = target.request().get();

  Customer customer = response.readEntity(Customer.class);

  response.close(); 

  assertEquals(200, response.getStatus());

 }


Accessing a REST resource is actually quite straightforward and is made possible using the client interface exposed by JAX-RS. The process is as follows


  1. Obtain an instance of the jax.ws.rs.client.Client interface by invoking the newClient() method on the class ClientBuilder
  2. Configure the Client with the target url. This will instruct the Client class to fire a http request at this address.
  3. Create a HTTP Request using the .getRequest() method
  4. Finally invoke the request. This will also specify which HTTP method (e.g. GET/PUT) will be executed.


This will return a response. The payload accompanying the response can be accessed by using the readEntity() method and passing as an argument the type of the Java class that is to be returned. Finally we finish up by ensuring that the response is closed.

Retrieve Collection of Customers (GET)

In a similar fashion, to retrieve a Collection of Customers we do the following

 
 @GET
 @Path("/customers")
 @Produces("application/*+xml")
 public List getAllCustomers() {  
    final List customers = backendService.getAllCustomers();
    return customers;
 }

Note that in this case, we want to retrieve all Customer instances and therefore don't need to specify a userId.

The Client code executes the request in the following way:

    
 WebTarget target = client.target("http://localhost:8080/RESTfulBookstore/bookstoreService/customers");

 Response response = target.request().get();

 List <Customer> customers = response.readEntity(new GenericType<list<Customer>>() {});

Note that in order to unmarshall returned content, the object type needs to be known. However generics information is not available at runtime, and therefore it becomes tricky to unmarshall the content. To get round this we need to declare a GenericType.

Create a New Customer (POST)

To create a new Resource on the Server, i.e. a new Customer instance, we need to use the HTTP POST operation.
 @POST
 @Path("/customer")
 @Consumes("application/*+xml")
 public Response createCustomer (Customer customer) throws URISyntaxException{    
  final int customerId = backendService.createCustomer(customer);
  return Response.status(201).contentLocation(new URI("/customer/"+customerId)).build();
 }


In this example we pass an instance of Customer as an argument to the method. This instance is subsequently passed onto the backend. To finish a response is returned with a HTTP code of 201 (indicates resource has been created).  Additionally the response will contain an a URI address which will enable the Client to access the newly created Customer instance.

The Client code executes the request in the following way:
        
 WebTarget target = client.target("http://localhost:8080/RESTfulBookstore/bookstoreService/customer/");

 final Customer newCustomer = new Customer("Fred", "Bloggs", "fbloggs@internet.com", "07851 444 555", "fbloggs");

 Response response = target.request().post(Entity.entity(newCustomer, "application/*+xml"));

Note that here we invoke the post() method instead of get() as before.

Update Existing Customer (PUT)

To edit an existing resource, i.e. update the Customer details we need to use the HTTP PUT operation.

 @PUT
 @Path("/customer/{id}")
 @Consumes("application/*+xml")
 public Response updateCustomer (@PathParam("id") int id, Customer newCustomer) throws URISyntaxException{

  final Customer existingCustomer = backendService.getCustomer(id);
  
  if(existingCustomer == null) {
   logger.warn("SERVER updateCustomer NO CUSTOMER FOUND: " + existingCustomer);   
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  }
  
  
  existingCustomer.setEmail(newCustomer.getEmail());
  existingCustomer.setMobileNumber(newCustomer.getMobileNumber());
  existingCustomer.setPassword(newCustomer.getPassword());
  
  return Response.status(201).contentLocation(new URI("/customer/"+ newCustomer.getUserId())).build();
 }
 
The method uses the Customer id to perform a lookup. If it manages to retrieve the matching Customer instance, it will overwrite existing properties. To finish, it will return a 201 status and provide the URI location of the updated Customer instance.

The Client code executes the request in the following way:

 
 WebTarget target2 = client.target("http://localhost:8080/RESTfulBookstore/bookstoreService/customer/"+String.valueOf(customerId));
 Response response2 = target2.request().put(Entity.entity(customer, "application/*+xml"));

Note that in this case, we invoke the put() method on the Client interface.

Delete Existing Customer (DELETE)

The final example focusses on the HTTP DELETE operation.

 @DELETE
 @Path("/customer/{id}")
 public Response deleteCustomer (@PathParam("id") int id) throws URISyntaxException{

  final Customer existingCustomer = backendService.getCustomer(id);
  
  if(existingCustomer == null) {
   logger.warn("SERVER deleteCustomer NO CUSTOMER FOUND: " + existingCustomer);   
   throw new WebApplicationException(Response.Status.NOT_FOUND);
  }
  

  backendService.deleteCustomer(id);

  return Response.status(200).build();
 }


The method takes the customer id and invokes a method on back-end code to delete the corresponding Customer instance. The response returned contains a 200 status indicating to the client that the request was processed correctly without errors.

The Client code executes the request in the following way:

 WebTarget target2 = client.target("http://localhost:8080/RESTfulBookstore/bookstoreService/customer/"+String.valueOf(customerId));
 Response response2 = target2.request().delete();


Again note that we tell the Client class that we want fire off a HTTP DELETE request by invoking the delete() method.

The following shows output from firing of a get() for a customer with id of 2.




This completes the tutorial on implementing a RESTful service using resteasy. Any comments relating to corrections, omissions, etc are welcome.

No comments:

Post a Comment