RESTful Web Services Developer's Guide

The Storage-Service Application

The Storage-Service sample application demonstrates a simple, in-memory, web storage service and test code using the Jersey client API. The web storage service enables clients to create and delete containers. Containers are used to create, read, update, and delete items of arbitrary content, and to search for items containing certain content. A container can be thought of as a hash map of items. The key for the item is specified in the request URI. There are three web resources that are shown below.

The web storage service ensures that content can be cached (by browsers and proxies) by supporting the HTTP features Last-Modified and ETag. The sample consists of three web resources implemented by the classes described in the following paragraphs.


Note –

Two of the resource classes have similar names. ContainersResource (plural) and ContainerResource (singular).


Understanding the Web Resource Classes for the Storage-Service Example

The first resource class, com.sun.jersey.samples.storageservice.resources.ContainersResource, provides metadata information on the containers. This resource references the ContainerResource resource using the @Path annotation declared on the ContainersResource.getContainerResource method. The following code is extracted from ContainersResource.java:

@Path("/containers")
@Produces("application/xml")
public class ContainersResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    
    @Path("{container}")
    public ContainerResource getContainerResource(@PathParam("container")
        String container) {
        return new ContainerResource(uriInfo, request, container);
    }
    
    @GET
    public Containers getContainers() {
        System.out.println("GET CONTAINERS");
        
        return MemoryStore.MS.getContainers();
    }    
}

Another resource class , com.sun.jersey.samples.storageservice.resources.ContainerResource, provides for reading, creating, and deleting of containers. You can search for items in the container using a URI query parameter. The resource dynamically references the ItemResource resource class using the getItemResource method that is annotated with @Path. The following code is extracted from ContainerResource.java:

@Produces("application/xml")
public class ContainerResource {
    @Context UriInfo uriInfo;
    @Context Request request;
    String container;
    
    ContainerResource(UriInfo uriInfo, Request request, String container) {
        this.uriInfo = uriInfo;
        this.request = request;
        this.container = container;
    }
    
    @GET
    public Container getContainer(@QueryParam("search") String search) {
        System.out.println("GET CONTAINER " + container + ", search = " + search);

        Container c = MemoryStore.MS.getContainer(container);
        if (c == null)
            throw new NotFoundException("Container not found");
        
        
        if (search != null) {
            c = c.clone();
            Iterator<Item> i = c.getItem().iterator();
            byte[] searchBytes = search.getBytes();
            while (i.hasNext()) {
                if (!match(searchBytes, container, i.next().getName()))
                    i.remove();
            }
        }
        
        return c;
    }    

    @PUT
    public Response putContainer() {
        System.out.println("PUT CONTAINER " + container);
        
        URI uri =  uriInfo.getAbsolutePath();
        Container c = new Container(container, uri.toString());
        
        Response r;
        if (!MemoryStore.MS.hasContainer(c)) {
            r = Response.created(uri).build();
        } else {
            r = Response.noContent().build();
        }
        
        MemoryStore.MS.createContainer(c);
        return r;
    }
    
    @DELETE
    public void deleteContainer() {
        System.out.println("DELETE CONTAINER " + container);
        
        Container c = MemoryStore.MS.deleteContainer(container);
        if (c == null)
            throw new NotFoundException("Container not found");
    } 
    
    
    @Path("{item: .+}")
    public ItemResource getItemResource(@PathParam("item") String item) {
        return new ItemResource(uriInfo, request, container, item);
    }
    
    private boolean match(byte[] search, String container, String item) {
        byte[] b = MemoryStore.MS.getItemData(container, item);
        
        OUTER: for (int i = 0; i < b.length - search.length; i++) {
             for (int j = 0; j < search.length; j++) {
                if (b[i + j] != search[j])
                    continue OUTER;
            }
            
            return true;
        }
        
        return false;
    }
}

The last resource class discussed in this sample, com.sun.jersey.samples.storageservice.resources.ItemResource, provides for reading, creating, updating, and deleting of an item. The last modified time and entity tag are supported in that it conditionally returns content only if the browser or proxy has an older version of the content. The following code is extracted from ItemResource.java:

public class ItemResource {
    UriInfo uriInfo;
    Request request;
    String container;
    String item;
    
    public ItemResource(UriInfo uriInfo, Request request,
            String container, String item) {
        this.uriInfo = uriInfo;
        this.request = request;
        this.container = container;
        this.item = item;
    }
    
    @GET
    public Response getItem() {
        System.out.println("GET ITEM " + container + " " + item);
        
        Item i = MemoryStore.MS.getItem(container, item);
        if (i == null)
            throw new NotFoundException("Item not found");
        Date lastModified = i.getLastModified().getTime();
        EntityTag et = new EntityTag(i.getDigest());
        ResponseBuilder rb = request.evaluatePreconditions(lastModified, et);
        if (rb != null)
            return rb.build();
            
        byte[] b = MemoryStore.MS.getItemData(container, item);
        return Response.ok(b, i.getMimeType()).
                lastModified(lastModified).tag(et).build();
    }    
    
    @PUT
    public Response putItem(
            @Context HttpHeaders headers,
            byte[] data) {
        System.out.println("PUT ITEM " + container + " " + item);
        
        URI uri = uriInfo.getAbsolutePath();
        MediaType mimeType = headers.getMediaType();
        GregorianCalendar gc = new GregorianCalendar();
        gc.set(GregorianCalendar.MILLISECOND, 0);
        Item i = new Item(item, uri.toString(), mimeType.toString(), gc);
        String digest = computeDigest(data);
        i.setDigest(digest);
        
        Response r;
        if (!MemoryStore.MS.hasItem(container, item)) {
            r = Response.created(uri).build();
        } else {
            r = Response.noContent().build();
        }
        
        Item ii = MemoryStore.MS.createOrUpdateItem(container, i, data);
        if (ii == null) {
            // Create the container if one has not been created
            URI containerUri = uriInfo.getAbsolutePathBuilder().path("..").
                    build().normalize();
            Container c = new Container(container, containerUri.toString());
            MemoryStore.MS.createContainer(c);
            i = MemoryStore.MS.createOrUpdateItem(container, i, data);
            if (i == null)
                throw new NotFoundException("Container not found");
        }
        
        return r;
    }    
    
    @DELETE
    public void deleteItem() {
        System.out.println("DELETE ITEM " + container + " " + item);
        
        Item i = MemoryStore.MS.deleteItem(container, item);
        if (i == null) {
            throw new NotFoundException("Item not found");
        }
    }
    
    
    private String computeDigest(byte[] content) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA");
            byte[] digest = md.digest(content);
            BigInteger bi = new BigInteger(digest);
            return bi.toString(16);
        } catch (Exception e) {
            return "";
        }
    }
}

The mapping of the URI path space is shown in the following table.

Table 5–1 URI Path Space for Storage-Service Example

URI Path 

Resource Class 

HTTP Methods 

/containers 

ContainersResource

GET 

/containers/{container} 

ContainerResource

GET, PUT, DELETE 

/containers/{container}/{item} 

ItemResource

GET, PUT, DELETE 

ProcedureBuilding and Running the Storage-Service Application from the Command Line

  1. Make sure that Maven is installed and configured, as described in Running the Jersey Examples.

  2. Open a terminal prompt and navigate to jersey.home/samples/storage-service.

  3. To run the sample, enter mvn clean compile exec:java and press Enter.

    This will build, package, and deploy the web storage service using Grizzly, an HTTP web server.


    Tip –

    To run the application on GlassFish, copy the classes from the example into sources of the web application. Then, create a web.xml file that uses the Jersey servlet. The Java classes are not dependent on a particular container.


  4. To view the Web Application Description Language file (the WADL), open a web browser and navigate to:


    http://localhost:9998/storage/application.wadl

    The WADL description in also shown in this document at Example: The Storage-Service WADL

  5. Leave the terminal window open, and open a new terminal window to continue with exploring this sample.

ProcedureExploring the Storage-Service Example

Once the service is deployed, it is a good idea to see how it works by looking at the contents of the containers, creating a container, adding content to the container, looking at the date and time stamp for the items in the container, searching for content in the containers, modifying content of an item in the containers, deleting an item from the container, and deleting the container. To accomplish these tasks, use the cURL command line tool, as shown in the following steps. cURL is a command line tool for transferring files with URL syntax.

  1. To show which containers currently exist, open a new terminal prompt and enter the following command. This command will henceforth be referred to as the GET command.

    curl http://127.0.0.1:9998/storage/containers

    The response is in XML format. Because there are no containers present, the response is an empty containers element.

  2. To create a container, enter the following at the terminal prompt:

    curl -X PUT http://127.0.0.1:9998/storage/containers/quotes

    This step creates a container called quotes. If you run the GET command from the previous step again, it will return information in XML format showing that a container named quotes exists at the URI http://127.0.0.1:9998/storage/containers/quotes.

  3. Create some content in the quotes container. The following example shows how to do this from the terminal prompt:

    curl -X PUT -HContent-type:text/plain --data 
    			"Something is rotten in the state of Denmark"  
    			http://127.0.0.1:9998/storage/containers/quotes/1
    curl -X PUT -HContent-type:text/plain --data 
    			"I could be bounded in a nutshell" 
    			http://127.0.0.1:9998/storage/containers/quotes/2
    curl -X PUT -HContent-type:text/plain --data 
    			"catch the conscience of the king" 
    			http://127.0.0.1:9998/storage/containers/quotes/3 
    curl -X PUT -HContent-type:text/plain --data 
    			"Get thee to a nunnery" 
    			http://127.0.0.1:9998/storage/containers/quotes/4 

    If you run the GET command again with /quotes at the end (curl http://127.0.0.1:9998/storage/containers/quotes), it will return information in XML format that show that the quotes container contains 4 items. For each item, the following information is displayed: digest, date last modified, MIME type, item name (also referred to as its key), and URI address. For example, the listing for the first item looks like this:

        <item>
            <digest>7a54c57975de11bffcda5bc6bd92a0460d17ad03</digest>
            <lastModified>2008-10-10T15:011:48+01:00</lastModified>
            <mimeType>text/plain</mimeType>
            <name>1</name>
            <uri>http://127.0.0.1:9998/storage/containers/quotes/1</uri>
        </item>
  4. You can search for information within the contents of the quotes container. For example, the following command would search for the String king.

    curl "http://127.0.0.1:9998/storage/containers/quotes?search=king"

    This returns the information for the item that contains the text, similar to that shown in the previous step, but not the text itself. The next step shows how to see the contents of the item containing the text king.

  5. To get the contents of the item containing the text you found using the search, use the following command:

    curl  http://127.0.0.1:9998/storage/containers/quotes/3

    This step returns the contents of item 3, which is the quote catch the conscience of the king.

  6. To delete an item from the container, use the following command:

    curl -X DELETE http://127.0.0.1:9998/storage/containers/quotes/3
  7. To delete an item from the container, use the following command:

    curl -X DELETE http://127.0.0.1:9998/storage/containers/quotes/3

    You can use the GET command to verify that item 3 has been removed.

  8. To delete the entire quotes container, use the following command:

    curl -X DELETE http://127.0.0.1:9998/storage/containers/quotes

    You can use the GET command to verify that the container has been removed.

  9. For a discussion of the caching of HTTP requests and responses, look at the as-install/jersey/samples/Storage-Service/README.html file.

    If you go back to the terminal window that is running the web storage service, you will see the history of HTTP requests and responses, which will look something like this:

    GET CONTAINERS
    PUT CONTAINER quotes
    GET CONTAINERS
    PUT ITEM quotes 1
    PUT ITEM quotes 2
    PUT ITEM quotes 3
    PUT ITEM quotes 4
    GET CONTAINER quotes, search = null
    PUT ITEM quotes 4
    PUT ITEM quotes 4
    GET CONTAINER quotes, search = king
    DELETE ITEM quotes 3
    GET CONTAINER quotes, search = null
    DELETE CONTAINER quotes
    GET CONTAINER quotes, search = null
    GET CONTAINERS 

Extending the Storage-Service Example

This example demonstrates storing and returning plain text strings. This example can easily be modified to store and return arbitrary binary data, for example, images. You can easily store any piece of data with any media type. All you have to do is, in the examples in the previous section, change the text/plain parameter to image/jpeg, or some other value, and point to a JPEG file. For example, to create some content that is a JPEG file, you could use the following curl command.

curl -X PUT -HContent-type:image/jpeg --data 
			/home/jersey_logo.jpg  
			http://127.0.0.1:9998/storage/containers/images/1

To retrieve the contents of the item containing the image, use the following command:

curl  http://127.0.0.1:9998/storage/containers/images/1

Example: The Storage-Service WADL

This is the Web Application Description Language file (the WADL) that is generated for the storage-service sample application:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://research.sun.com/wadl/2006/10">
    <doc xmlns:jersey="http://jersey.dev.java.net/" 
    		jersey:generatedBy="Jersey: 1.0-ea-SNAPSHOT 10/02/2008 12:17 PM"/>
    <resources base="http://localhost:9998/storage/">
        <resource path="/containers">
            <method name="GET" id="getContainers">
                <response>
                    <representation mediaType="application/xml"/>
                </response>
            </method>
            <resource path="{container}">
                <param xmlns:xs="http://www.w3.org/2001/XMLSchema" 
									type="xs:string" style="template" name="container"/>
                <method name="PUT" id="putContainer">
                    <response>
                        <representation mediaType="application/xml"/>
                    </response>
                </method>
                <method name="DELETE" id="deleteContainer"/>
                <method name="GET" id="getContainer">
                    <request>
                        <param xmlns:xs="http://www.w3.org/2001/XMLSchema" 
													type="xs:string" style="query" name="search"/>
                    </request>
                    <response>
                        <representation mediaType="application/xml"/>
                    </response>
                </method>
                <resource path="{item: .+}">
                    <param xmlns:xs="http://www.w3.org/2001/XMLSchema" 
											type="xs:string" style="template" name="item"/>
                    <method name="PUT" id="putItem">
                        <request>
                            <representation mediaType="*/*"/>
                        </request>
                        <response>
                            <representation mediaType="*/*"/>
                        </response>
                    </method>
                    <method name="DELETE" id="deleteItem"/>
                    <method name="GET" id="getItem">
                        <response>
                            <representation mediaType="*/*"/>
                        </response>
                    </method>
                </resource>
            </resource>
        </resource>
    </resources>
</application>