Skip to content

Commit

Permalink
mint etags for all fedora resources and use them for conditional requ…
Browse files Browse the repository at this point in the history
…ests.
  • Loading branch information
cbeer committed Jul 29, 2013
1 parent dc01984 commit 8b3be13
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 10 deletions.
19 changes: 15 additions & 4 deletions fcrepo-http-api/src/main/java/org/fcrepo/api/FedoraNodes.java
Expand Up @@ -64,6 +64,7 @@
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Request;
Expand Down Expand Up @@ -134,13 +135,15 @@ public Dataset describe(@PathParam("path") final List<PathSegment> pathList,
final FedoraResource resource =
nodeService.getObject(session, path);


final EntityTag etag = new EntityTag(resource.getEtagValue());
final Date date = resource.getLastModifiedDate();
final Date roundedDate = new Date();
if (date != null) {
roundedDate.setTime(date.getTime() - date.getTime() % 1000);
}
final ResponseBuilder builder =
request.evaluatePreconditions(roundedDate);
request.evaluatePreconditions(roundedDate, etag);
if (builder != null) {
final CacheControl cc = new CacheControl();
cc.setMaxAge(0);
Expand All @@ -149,7 +152,7 @@ public Dataset describe(@PathParam("path") final List<PathSegment> pathList,
// the exception is not an error, it's genuinely
// an exceptional condition
throw new WebApplicationException(builder.cacheControl(cc)
.lastModified(date).build());
.lastModified(date).tag(etag).build());
}
final HttpGraphSubjects subjects =
new HttpGraphSubjects(session, FedoraNodes.class, uriInfo);
Expand Down Expand Up @@ -201,6 +204,10 @@ public Dataset describe(@PathParam("path") final List<PathSegment> pathList,

}

if (!etag.getValue().isEmpty()) {
servletResponse.addHeader("ETag", etag.toString());
}

servletResponse.addHeader("Accept-Patch", WebContent.contentTypeSPARQLUpdate);
servletResponse.addHeader("Link", "http://www.w3.org/ns/ldp/Resource;rel=\"type\"");

Expand Down Expand Up @@ -267,6 +274,7 @@ public Response updateSparql(@PathParam("path")
nodeService.getObject(session, path);


final EntityTag etag = new EntityTag(resource.getEtagValue());
final Date date = resource.getLastModifiedDate();
final Date roundedDate = new Date();

Expand All @@ -275,7 +283,7 @@ public Response updateSparql(@PathParam("path")
}

final ResponseBuilder builder =
request.evaluatePreconditions(roundedDate);
request.evaluatePreconditions(roundedDate, etag);

if (builder != null) {
throw new WebApplicationException(builder.build());
Expand Down Expand Up @@ -335,12 +343,15 @@ public Response createOrReplaceObjectRdf(
final Date date = resource.getLastModifiedDate();
final Date roundedDate = new Date();


final EntityTag etag = new EntityTag(resource.getEtagValue());

if (date != null) {
roundedDate.setTime(date.getTime() - date.getTime() % 1000);
}

final ResponseBuilder builder =
request.evaluatePreconditions(roundedDate);
request.evaluatePreconditions(roundedDate, etag);

if (builder != null) {
throw new WebApplicationException(builder.build());
Expand Down
Expand Up @@ -207,7 +207,7 @@ public void testDescribeObject() throws RepositoryException, IOException {
when(mockDataset.getDefaultModel()).thenReturn(mockModel);
when(mockDataset.getContext()).thenReturn(mockContext);

when(mockObject.getLastModifiedDate()).thenReturn(null);
when(mockObject.getEtagValue()).thenReturn("");
when(
mockObject.getPropertiesDataset(any(GraphSubjects.class),
anyLong(), anyInt())).thenReturn(mockDataset);
Expand All @@ -230,7 +230,7 @@ public void testDescribeObjectNoInlining() throws RepositoryException, IOExcepti
when(mockDataset.getDefaultModel()).thenReturn(mockModel);
when(mockDataset.getContext()).thenReturn(mockContext);

when(mockObject.getLastModifiedDate()).thenReturn(null);
when(mockObject.getEtagValue()).thenReturn("");
when(
mockObject.getPropertiesDataset(any(GraphSubjects.class),
anyLong(), eq(-2))).thenReturn(mockDataset);
Expand All @@ -252,7 +252,8 @@ public void testSparqlUpdate() throws RepositoryException, IOException {
new ByteArrayInputStream("my-sparql-statement".getBytes());
when(mockNodes.getObject(mockSession, path)).thenReturn(mockObject);
when(mockObject.updatePropertiesDataset(any(GraphSubjects.class), any(String.class)))
.thenReturn(mockDataset);
.thenReturn(mockDataset);
when(mockObject.getEtagValue()).thenReturn("");

when(mockObject.getLastModifiedDate()).thenReturn(Calendar.getInstance().getTime());
when(mockDataset.getNamedModel(GraphProperties.PROBLEMS_MODEL_NAME))
Expand All @@ -271,6 +272,7 @@ public void testReplaceRdf() throws RepositoryException, IOException, URISyntaxE
final String path = "/" + pid;
when(mockObject.getLastModifiedDate()).thenReturn(Calendar.getInstance().getTime());
when(mockObject.getNode()).thenReturn(mockNode);
when(mockObject.getEtagValue()).thenReturn("");
when(mockNode.getPath()).thenReturn(path);

final InputStream mockStream =
Expand Down
Expand Up @@ -575,8 +575,10 @@ public void testDescribeRdfCached() throws RepositoryException, IOException {
.getStatusLine().getStatusCode());
final String lastModed =
response.getFirstHeader("Last-Modified").getValue();
final String etag = response.getFirstHeader("ETag").getValue();
final HttpGet getObjMethod2 = new HttpGet(serverAddress + path);
getObjMethod2.setHeader("If-Modified-Since", lastModed);
getObjMethod2.setHeader("If-None-Match", etag);
response = specialClient.execute(getObjMethod2);

assertEquals("Client didn't return a NOT_MODIFIED!", NOT_MODIFIED
Expand Down
22 changes: 22 additions & 0 deletions fcrepo-kernel/src/main/java/org/fcrepo/FedoraResource.java
Expand Up @@ -34,6 +34,7 @@

import com.hp.hpl.jena.update.UpdateFactory;
import com.hp.hpl.jena.update.UpdateRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.fcrepo.rdf.GraphProperties;
import org.fcrepo.rdf.GraphSubjects;
import org.fcrepo.rdf.impl.JcrGraphProperties;
Expand Down Expand Up @@ -317,4 +318,25 @@ public Dataset replacePropertiesDataset(final GraphSubjects subjects, final Mode

return propertiesDataset;
}

/**
* Construct an ETag value from the last modified date and path.
*
* JCR has a mix:etag type, but it only takes into account binary
* properties. We actually want whole-object etag data.
*
* TODO : construct and store an ETag value on object modify
*
* @return
* @throws RepositoryException
*/
public String getEtagValue() throws RepositoryException {
final Date lastModifiedDate = getLastModifiedDate();

if (lastModifiedDate != null) {
return DigestUtils.shaHex(node.getPath() + lastModifiedDate.toString());
} else {
return "";
}
}
}
13 changes: 13 additions & 0 deletions fcrepo-kernel/src/test/java/org/fcrepo/FedoraResourceTest.java
Expand Up @@ -49,6 +49,7 @@
import javax.jcr.version.VersionHistory;

import com.hp.hpl.jena.rdf.model.ModelFactory;
import org.apache.commons.codec.digest.DigestUtils;
import org.fcrepo.rdf.GraphSubjects;
import org.fcrepo.rdf.impl.DefaultGraphSubjects;
import org.fcrepo.utils.FedoraTypesUtils;
Expand Down Expand Up @@ -358,5 +359,17 @@ public void testReplacePropertiesDataset() throws RepositoryException {
propertiesModel.createProperty("y"),
"z"));
}
@Test
public void shouldGetEtagForAnObject() throws RepositoryException {
final Property mockMod = mock(Property.class);
final Calendar modDate = Calendar.getInstance();
modDate.set(2013, Calendar.JULY, 30, 0, 0, 0);
when(mockNode.getPath()).thenReturn("some-path");
when(mockNode.hasProperty(JCR_LASTMODIFIED)).thenReturn(true);
when(mockNode.getProperty(JCR_LASTMODIFIED)).thenReturn(mockMod);
when(mockMod.getDate()).thenReturn(modDate);

assertEquals(DigestUtils.shaHex("some-path" + testObj.getLastModifiedDate().toString()), testObj.getEtagValue());
}

}
Expand Up @@ -17,9 +17,11 @@

import static com.hp.hpl.jena.graph.NodeFactory.createLiteral;
import static com.hp.hpl.jena.graph.NodeFactory.createURI;
import static junit.framework.Assert.assertNotNull;
import static org.fcrepo.utils.FedoraTypesUtils.getVersionHistory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
Expand All @@ -43,6 +45,7 @@
import org.fcrepo.services.DatastreamService;
import org.fcrepo.services.NodeService;
import org.fcrepo.services.ObjectService;
import org.fcrepo.utils.FedoraJcrTypes;
import org.fcrepo.utils.JcrRdfTools;
import org.junit.After;
import org.junit.Before;
Expand Down Expand Up @@ -396,9 +399,9 @@ public void testUpdatingRdfTypedValues() throws RepositoryException {
logger.warn(propertiesDataset.toString());

object.updatePropertiesDataset(subjects, "PREFIX example: <http://example.org/>\n" +
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" +
"INSERT { <info:fedora/testObjectRdfType> example:int-property \"0\"^^xsd:long } WHERE { }");
assertEquals(PropertyType.LONG, object.getNode().getProperty("example:int-property").getType());
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" +
"INSERT { <info:fedora/testObjectRdfType> example:int-property \"0\"^^xsd:long } WHERE { }");
assertEquals(PropertyType.LONG, object.getNode().getProperty("example:int-property").getType());
assertEquals(0L, object.getNode().getProperty("example:int-property").getValues()[0].getLong());
}

Expand All @@ -417,4 +420,16 @@ public void testUpdatingRdfType() throws RepositoryException {
assertEquals(PropertyType.URI, object.getNode().getProperty("rdf:type").getType());
assertEquals("http://some/uri", object.getNode().getProperty("rdf:type").getValues()[0].getString());
}

@Test
public void testEtagValue() throws RepositoryException {
final FedoraResource object =
objectService.createObject(session, "/testEtagObject");

session.save();

final String actual = object.getEtagValue();
assertNotNull(actual);
assertNotEquals("", actual);
}
}

0 comments on commit 8b3be13

Please sign in to comment.