Saturday, December 17, 2011

REST Client

Accessing the RESTful service requires a "client".  Jersey is the open source JAX-RS (JSR 311) Reference Implementation for building RESTful Web services.  We use it below to create a class that has all the infrastructure needed to connect to an http RESTful service and to translate POJOs from and to  XML or JSON required by the services.

The Example:
PartEntityRESTClient

package com.sample.service;

import com.sample.service.other.Part;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;

/**
 * RESTClient for accessing the RESTful web service at http://localhost:8080/JSFDemoApp/resources/Part
 *  USAGE:<pre>
 *        PartEntityRESTClient client = new PartEntityRESTClient();
 *        Object response = client.XXX(...);
 *        // do whatever with response
 *        client.close();
 *  </pre>
 * @author Thomas Dias
 */
public class PartEntityRESTClient {

    private WebResource webResource;
    private Client client;
    private static final String BASE_URI = "http://localhost:8080/JSFDemoApp/resources";

    /**
     * Default constructor - initializes client to reference appropriate web service.
     */
    public PartEntityRESTClient() {
        com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig();
        client = Client.create(config);
        webResource = client.resource(BASE_URI).path("Part");
    }

    /**
     * CRUD operation to delete a record from the database
     * @param id of part to be removed
     * @throws UniformInterfaceException if web service throws an exception. i.e. remove failed.
     */
    public void remove(String id) throws UniformInterfaceException {
        webResource.path(java.text.MessageFormat.format("{0}", new Object[]{id})).delete();
    }

    /**
     * Operation to retrieve a record by supplying its name.
     * @param name of part to find.
     * @return the part found.  Part will have an id of 0l if no part was found.
     * @throws UniformInterfaceException if web service throws an exception
     */
    public Part findByName(String name) throws UniformInterfaceException {
        WebResource resource = webResource;
        resource = resource.path(java.text.MessageFormat.format("findByName/{0}", new Object[]{name}));
        return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(Part.class);
    }

    /**
     * creates a part and persists it in the customers database via an http POST method.
     * creates an xml file from the part to be supplied with the POST method
     * @param part to be created
     * @throws UniformInterfaceException if web service throws an exception. i.e. part is not unique.
     */
    public void create_XML(Part part) throws UniformInterfaceException {
        webResource.type(javax.ws.rs.core.MediaType.APPLICATION_XML).post(part);
    }

    /**
     * creates a part and persists it in the customers database via an http GET method.
     * supplies the pertinent parts of the part to be created in the URL
     * @param parent name of the part that is the parent of this part.  Supply an empty string if their is no parent.
     * @param name of the part to be created.
     * @return the part created by the web service.
     * @throws UniformInterfaceException if web service throws and exception. 
     */
    public Part createByGet(String parent, String name) throws UniformInterfaceException {
        WebResource resource = webResource;
        resource = resource.path(java.text.MessageFormat.format("createByGet/{0}/{1}", new Object[]{parent, name}));
        return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(Part.class);
    }

    /**
     * release resources associated with this client.
     */
    public void close() {
        client.destroy();
    }
}


Discussion

Walking through the client we com across:     private static final String BASE_URI = "http://localhost:8080/JSFDemoApp/resources";


Why do we need this when we have it defined in our REST service?  Well, this client knows nothing of our service.  This client is connecting to some vendor's service in some far off land.  The fact that it is running on with our application / service is simply for convenience of this tutorial, but the example still holds if we were connecting to Amazon.com's REST service.  Of course, the BASE_URI and methods would change.

The client is based on the specification given by the vendor you are connecting to.  Unlike SOAP, there is no WSDL for REST.  They can use it, and define their service with it, but it is not part of the REST definition.  The vendor would inform you how to obtain the specification for whatever service you are planning to connect to.

If we look at the find method's definition:
    public Part findByName(String name) throws UniformInterfaceException {
We notice that it is returning a Part.  Assuming we did not have the REST service running on our environment, we would have to declare a POJO called Part and annotate it such that it would be translated by based on the definition given to us from the vendor.


Now all the methods are throwing a UniformInterfaceException.  That is because we are communicating over http and we may get an http exception.

Reviewing how the client method findByName connects to the service, we have: 
        com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig();
        client = Client.create(config);
        webResource = client.resource(BASE_URI).path("Part");
        WebResource resource = webResource;
        resource = resource.path(java.text.MessageFormat.format("findByName/{0}", new Object[]{name}));
        return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(Part.class);
 
So, the webResource is told  where to go with the webResource.  Then we create the final path by calling resource.path with the sub path.  This is generated by MessageFormat by passing in the string with the path and parameters, and an array of objects that will be put in where the parameters are.  Finally we will call resource.accept that will retrieve the xml file from the service method we are calling and call get to translate that xml to a Part.  We tell accept that we are sending an XML file.

All four service methods call the service differently.

Delete is passing the id to delete via the path.  Therefor it modifies the path.  It receives nothing in return, so there is no accept.  Delete is connecting via the DELETE method, so webService.delete() is called.

create_XML calls the service to create a part with a POST and receives nothing in return.  There is no modification to the path.  So,  it calls webService.post(part) and passes the part as XML which it was told to do by setting the type to XML.

createByGet receives an XML, calls the GET method with parameters marking the name and parentName and so you see the path modified, the accept with an xml type, and get(Part.class) to translate the received xml to a part.

If my explanation isn't quiet clear, review the code above, I believe it to be quite self explanatory.

Note one last method.   Close.  There are resources that get opened that should be closed when you are done with the client.
 

No comments:

Post a Comment