RESTful Java with JAX-RS 2.0 (2013)
Part II. JAX-RS Workbook
Chapter 25. Examples for Chapter 11
In Chapter 11, you learned about HTTP caching techniques. Servers can tell HTTP clients if and how long they can cache retrieved resources. You can revalidate expired caches to avoid resending big messages by issuing conditional GET invocations. Conditional PUT operations can be invoked for safe concurrent updates.
Example ex11_1: Caching and Concurrent Updates
The example in this chapter expands on the CustomerResource example repeated throughout this book to support caching, conditional GETs, and conditional PUTs.
The Server Code
The first thing is to add a hashCode() method to the Customer class:
src/main/java/com/restfully/shop/domain/Customer.java
@XmlRootElement(name="customer")
publicclassCustomer
{
...
@Override
publicinthashCode()
{
intresult=id;
result=31*result+(firstName!=null
?firstName.hashCode():0);
result=31*result+(lastName!=null
?lastName.hashCode():0);
result=31*result+(street!=null
?street.hashCode():0);
result=31*result+(city!=null?city.hashCode():0);
result=31*result+(state!=null?state.hashCode():0);
result=31*result+(zip!=null?zip.hashCode():0);
result=31*result+(country!=null
?country.hashCode():0);
returnresult;
}
}
This method is used in the CustomerResource class to generate semi-unique ETag header values. While a hash code calculated in this manner isn’t guaranteed to be unique, there is a high probability that it will be. A database application might use an incremented version column to calculate the ETag value.
The CustomerResource class is expanded to support conditional GETs and PUTs. Let’s take a look at the relevant pieces of code:
src/main/java/com/restfully/shop/services/CustomerResource.java
@Path("/customers")
publicclassCustomerResource
{
...
@GET
@Path("{id}")
@Produces("application/xml")
publicResponsegetCustomer(@PathParam("id")intid,
@ContextRequestrequest){
Customercust=customerDB.get(id);
if(cust==null)
{
thrownewWebApplicationException(Response.Status.NOT_FOUND);
}
if(sent==null)System.out.println("No ETag sent by client");
EntityTagtag=newEntityTag(Integer.toString(cust.hashCode()));
CacheControlcc=newCacheControl();
cc.setMaxAge(5);
The getCustomer() method first starts out by retrieving the current Customer object identified by the id parameter. A current ETag value is created from the hash code of the Customer object. A new Cache-Control header is instantiated as well.
Response.ResponseBuilderbuilder=
request.evaluatePreconditions(tag);
if(builder!=null){
System.out.println(
"** revalidation on the server was successful");
builder.cacheControl(cc);
returnbuilder.build();
}
Next, Request.evaluatePreconditions() is called to perform a conditional GET. If the client has sent an If-None-Match header that matches the calculated current ETag, the method returns immediately with an empty response body. In this case, a new Cache-Control header is sent back to refresh the max-age the client will use.
// Preconditions not met!
cust.setLastViewed(newDate().toString());
builder=Response.ok(cust,"application/xml");
builder.cacheControl(cc);
builder.tag(tag);
returnbuilder.build();
}
}
If no If-None-Match header was sent or the preconditions were not met, the Customer is sent back to the client with an updated Cache-Control header.
@Path("{id}")
@PUT
@Consumes("application/xml")
publicResponseupdateCustomer(@PathParam("id")intid,
@ContextRequestrequest,
Customerupdate){
Customercust=customerDB.get(id);
if(cust==null)
thrownewWebApplicationException(Response.Status.NOT_FOUND);
EntityTagtag=newEntityTag(Integer.toString(cust.hashCode()));
The updateCustomer() method is responsible for updating a customer. It first starts off by finding the current Customer with the given id. From this queried customer, it generates the up-to-date value of the ETag header.
Response.ResponseBuilderbuilder=
request.evaluatePreconditions(tag);
if(builder!=null){
// Preconditions not met!
returnbuilder.build();
}
The current ETag header is compared against any If-Match header sent by the client. If it does match, the update can be performed:
// Preconditions met, perform update
cust.setFirstName(update.getFirstName());
cust.setLastName(update.getLastName());
cust.setStreet(update.getStreet());
cust.setState(update.getState());
cust.setZip(update.getZip());
cust.setCountry(update.getCountry());
builder=Response.noContent();
returnbuilder.build();
}
}
Finally, the update is performed.
The Client Code
The client code first performs a conditional GET. It then tries to do a conditional PUT using a bad ETag value.
publicclassCustomerResourceTest
{
@Test
publicvoidtestCustomerResource()throwsException
{
WebTargetcustomerTarget=
client.target("http://localhost:8080/services/customers/1");
Responseresponse=customerTarget.request().get();
Assert.assertEquals(200,response.getStatus());
Customercust=response.readEntity(Customer.class);
EntityTagetag=response.getEntityTag();
response.close();
The testCustomerResource() method starts off by fetching a preinitialized Customer object. It does this so that it can obtain the current ETag of the Customer representation.
System.out.println("Doing a conditional GET with ETag: "
+etag.toString());
response=customerTarget.request()
.header("If-None-Match",etag).get();
Assert.assertEquals(304,response.getStatus());
response.close();
This code is performing a conditional GET. We set the If-None-Match header using the previously fetched ETag value. The client is expecting that the server will return a 304, “Not Modified,” response.
// Update and send a bad etag with conditional PUT
cust.setCity("Bedford");
response=customerTarget.request()
.header("If-Match","JUNK")
.put(Entity.xml(cust));
Assert.assertEquals(412,response.getStatus());
response.close();
}
}
Finally, the code does a conditional PUT with a bad ETag value sent with the If-Match header. The client is expecting this operation to fail with a 412, “Precondition Failed,” response.
Build and Run the Example Program
Perform the following steps:
1. Open a command prompt or shell terminal and change to the ex11_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.
Another interesting thing you might want to try is to start up and leave the application running by using maven jetty:run. Open your browser to http://localhost:8080/customers/1. Continually refresh this URL. You will be able to see if your browser performs a conditional GET request or not by viewing the <last-viewed> element of the returned XML. I found that Firefox 3.5.2 does a conditional GET, while Safari 4.0.1 does not.
All materials on the site are licensed Creative Commons Attribution-Sharealike 3.0 Unported CC BY-SA 3.0 & GNU Free Documentation License (GFDL)
If you are the copyright holder of any material contained on our site and intend to remove it, please contact our site administrator for approval.
© 2016-2026 All site design rights belong to S.Y.A.