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.
Two of the resource classes have similar names. ContainersResource (plural) and ContainerResource (singular).
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 |
Make sure that Maven is installed and configured, as described in Running the Jersey Examples.
Open a terminal prompt and navigate to jersey.home/samples/storage-service.
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.
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.
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
Leave the terminal window open, and open a new terminal window to continue with exploring this sample.
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.
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.
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.
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>
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.
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.
To delete an item from the container, use the following command:
curl -X DELETE http://127.0.0.1:9998/storage/containers/quotes/3
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.
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.
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
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
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>