Skip to content

Commit

Permalink
Add LDP#Patch
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeer committed Sep 25, 2014
1 parent c79aebd commit 5f9ee08
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
104 changes: 104 additions & 0 deletions fcrepo-http-api/src/main/java/org/fcrepo/http/api/FedoraLdp.java
Expand Up @@ -20,12 +20,19 @@
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jena.riot.Lang;
import org.fcrepo.http.commons.AbstractResource;
import org.fcrepo.http.commons.api.rdf.HttpIdentifierTranslator;
import org.fcrepo.http.commons.domain.ContentLocation;
import org.fcrepo.http.commons.domain.PATCH;
import org.fcrepo.http.commons.domain.Prefer;
import org.fcrepo.http.commons.domain.PreferTag;
import org.fcrepo.http.commons.domain.Range;
Expand Down Expand Up @@ -54,11 +61,14 @@
import javax.inject.Inject;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
Expand Down Expand Up @@ -93,6 +103,7 @@
import static javax.ws.rs.core.Response.noContent;
import static javax.ws.rs.core.Response.ok;
import static javax.ws.rs.core.Response.status;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate;
import static org.fcrepo.http.commons.domain.RDFMediaType.JSON_LD;
Expand All @@ -104,6 +115,7 @@
import static org.fcrepo.http.commons.domain.RDFMediaType.TURTLE;
import static org.fcrepo.http.commons.domain.RDFMediaType.TURTLE_X;
import static org.fcrepo.kernel.RdfLexicon.LDP_NAMESPACE;
import static org.fcrepo.kernel.rdf.GraphProperties.PROBLEMS_MODEL_NAME;
import static org.slf4j.LoggerFactory.getLogger;

/**
Expand Down Expand Up @@ -409,6 +421,64 @@ public Response deleteObject() {
}
}

/**
* Update an object using SPARQL-UPDATE
*
* @return 201
* @throws RepositoryException
* @throws IOException
*/
@PATCH
@Consumes({contentTypeSPARQLUpdate})
@Timed
public Response updateSparql(@ContentLocation final InputStream requestBodyStream) throws IOException {

LOGGER.debug("Attempting to update path: {}", path);

if (resource() instanceof FedoraBinary) {
throw new BadRequestException(resource() + " is not a valid object to receive a PATCH");
}

if (null == requestBodyStream) {
throw new BadRequestException("SPARQL-UPDATE requests must have content!");
}

try {
final String requestBody = IOUtils.toString(requestBodyStream);
if (isBlank(requestBody)) {
throw new BadRequestException("SPARQL-UPDATE requests must have content!");
}

evaluateRequestPreconditions(request, servletResponse, resource(), session);

final Dataset properties = resource().updatePropertiesDataset(translator(), requestBody);


handleProblems(properties);

try {
session.save();
versionService.nodeUpdated(resource().getNode());
} catch (final RepositoryException e) {
throw new RepositoryRuntimeException(e);
}

addCacheControlHeaders(servletResponse, resource(), session);

return noContent().build();

} catch ( final RuntimeException ex ) {
final Throwable cause = ex.getCause();
if ( cause != null && cause instanceof PathNotFoundException) {
// the sparql update referred to a repository resource that doesn't exist
throw new BadRequestException(cause.getMessage());
}
throw ex;
} finally {
session.logout();
}
}


private void addResourceHttpHeaders(final FedoraResource resource) {

Expand Down Expand Up @@ -530,4 +600,38 @@ private URI getUri(final FedoraResource resource) {
private String getPath(final String uri) {
return translator().getPathFromSubject(ResourceFactory.createResource(uri));
}

private void handleProblems(final Dataset properties) {

final Model problems = properties.getNamedModel(PROBLEMS_MODEL_NAME);

if (!problems.isEmpty()) {
LOGGER.info(
"Found these problems updating the properties for {}: {}",
path, problems);
final StringBuilder error = new StringBuilder();
final StmtIterator sit = problems.listStatements();
while (sit.hasNext()) {
final String message = getMessage(sit.next());
if (StringUtils.isNotEmpty(message) && error.indexOf(message) < 0) {
error.append(message + " \n");
}
}

throw new ForbiddenException(error.length() > 0 ? error.toString() : problems.toString());
}
}

/*
* Return the statement's predicate and its literal value if there's any
* @param stmt
* @return
*/
private static String getMessage(final Statement stmt) {
final Literal literal = stmt.getLiteral();
if (literal != null) {
return stmt.getPredicate().getURI() + ": " + literal.getString();
}
return null;
}
}
Expand Up @@ -27,17 +27,21 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.util.EntityUtils;
import org.apache.jena.riot.Lang;
import org.junit.Test;
import org.slf4j.Logger;

import javax.ws.rs.core.MediaType;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;
import static org.apache.jena.riot.RDFLanguages.contentTypeToLang;
import static org.apache.jena.riot.WebContent.contentTypeN3;
Expand Down Expand Up @@ -310,4 +314,72 @@ public void testDeleteBinary() throws Exception {
}


@Test
public void testEmptyPatch() throws Exception {
final String pid = getRandomUniquePid();

createObject(pid);

final String location = serverAddress + "/fcr:ldp/" + pid;
final HttpPatch patch = new HttpPatch(location);
patch.addHeader("Content-Type", "application/sparql-update");
assertEquals(BAD_REQUEST.getStatusCode(), getStatus(patch));
}

@Test
public void testUpdateObjectGraph() throws Exception {
final String pid = getRandomUniquePid();

createObject(pid);

final String location = serverAddress + "/fcr:ldp/" + pid;
final HttpPatch updateObjectGraphMethod = new HttpPatch(location);
updateObjectGraphMethod.addHeader("Content-Type",
"application/sparql-update");
final BasicHttpEntity e = new BasicHttpEntity();
e.setContent(new ByteArrayInputStream(
("INSERT { <" + location +
"> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\" } WHERE {}")
.getBytes()));
updateObjectGraphMethod.setEntity(e);
final HttpResponse response = client.execute(updateObjectGraphMethod);
assertEquals(NO_CONTENT.getStatusCode(), response.getStatusLine()
.getStatusCode());

}

@Test
public void testPatchBinary() throws Exception {
final String pid = getRandomUniquePid();


createDatastream(pid, "x", "some content");

final String location = serverAddress + "/fcr:ldp/" + pid + "/x";
final HttpPatch patch = new HttpPatch(location);
patch.addHeader("Content-Type", "application/sparql-update");
assertEquals(BAD_REQUEST.getStatusCode(), getStatus(patch));
}

@Test
public void testPatchBinaryDescription() throws Exception {
final String pid = getRandomUniquePid();


createDatastream(pid, "x", "some content");

final String location = serverAddress + "/fcr:ldp/" + pid + "/x/fcr:metadata";
final HttpPatch patch = new HttpPatch(location);
patch.addHeader("Content-Type", "application/sparql-update");
final BasicHttpEntity e = new BasicHttpEntity();
e.setContent(new ByteArrayInputStream(
("INSERT { <" + location +
"> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\" } WHERE {}")
.getBytes()));
patch.setEntity(e);
final HttpResponse response = client.execute(patch);
assertEquals(NO_CONTENT.getStatusCode(), response.getStatusLine()
.getStatusCode());
}

}

0 comments on commit 5f9ee08

Please sign in to comment.