Skip to content

Commit

Permalink
Working implementation of FCREPO-1411
Browse files Browse the repository at this point in the history
  • Loading branch information
ajs6f committed Jul 13, 2015
1 parent 4c4e1c3 commit a2f5259
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 85 deletions.
Expand Up @@ -16,8 +16,6 @@
package org.fcrepo.http.api;


import static com.google.common.base.Predicates.alwaysTrue;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterators.concat;
import static com.google.common.collect.Iterators.filter;
Expand All @@ -41,13 +39,15 @@
import static org.fcrepo.kernel.RdfLexicon.INDIRECT_CONTAINER;
import static org.fcrepo.kernel.RdfLexicon.LDP_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.isManagedNamespace;
import static org.fcrepo.kernel.impl.rdf.ManagedRdf.isManagedTriple;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.Iterator;
import java.util.function.Predicate;

import javax.inject.Inject;
import javax.jcr.AccessDeniedException;
Expand All @@ -74,7 +74,6 @@
import org.fcrepo.kernel.exception.InvalidChecksumException;
import org.fcrepo.kernel.exception.MalformedRdfException;
import org.fcrepo.kernel.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.impl.rdf.ManagedRdf;
import org.fcrepo.kernel.impl.rdf.impl.AclRdfContext;
import org.fcrepo.kernel.impl.rdf.impl.BlankNodeRdfContext;
import org.fcrepo.kernel.impl.rdf.impl.ChildrenRdfContext;
Expand All @@ -101,8 +100,6 @@
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.jvnet.hk2.annotations.Optional;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.hp.hpl.jena.graph.Triple;
Expand Down Expand Up @@ -158,14 +155,7 @@ protected Response getContent(final String rangeValue,
final Model inputModel = createDefaultModel()
.read(content, (resource()).toString(), format);

rdfStream.concat(Iterators.transform(inputModel.listStatements(),
new Function<Statement, Triple>() {

@Override
public Triple apply(final Statement input) {
return input.asTriple();
}
}));
rdfStream.concat(Iterators.transform(inputModel.listStatements(), Statement::asTriple));
} else {

final MediaType mediaType = MediaType.valueOf(contentTypeString);
Expand Down Expand Up @@ -213,24 +203,24 @@ protected RdfStream getResourceTriples() {

final Predicate<Triple> tripleFilter;
if (ldpPreferences.prefersServerManaged()) {
tripleFilter = alwaysTrue();
tripleFilter = x -> true;
} else {
tripleFilter = and(not(ManagedRdf.isManagedTriple), not(new Predicate<Triple>() {
tripleFilter = new Predicate<Triple>() {
@Override
public boolean apply(final Triple input) {
public boolean test(final Triple input) {
return input.getPredicate().equals(RDF.type.asNode())
&& isManagedNamespace.apply(input.getObject().getNameSpace());
}
}));
}.negate().and(not(isManagedTriple)::apply);
}

if (ldpPreferences.prefersServerManaged()) {
rdfStream.concat(getTriples(LdpRdfContext.class));
}

rdfStream.concat(filter(getTriples(TypeRdfContext.class), tripleFilter));
rdfStream.concat(filter(getTriples(TypeRdfContext.class), tripleFilter::test));

rdfStream.concat(filter(getTriples(PropertiesRdfContext.class), tripleFilter));
rdfStream.concat(filter(getTriples(PropertiesRdfContext.class), tripleFilter::test));

if (!returnPreference.getValue().equals("minimal")) {

Expand Down Expand Up @@ -258,15 +248,15 @@ public boolean apply(final Triple input) {
final FedoraResource described = ((NonRdfSourceDescription) resource()).getDescribedResource();
rdfStream.concat(filter(described.getTriples(translator(), ImmutableList.of(TypeRdfContext.class,
PropertiesRdfContext.class,
ContentRdfContext.class)), tripleFilter));
ContentRdfContext.class)), tripleFilter::test));
if (ldpPreferences.prefersServerManaged()) {
rdfStream.concat(getTriples(described,LdpRdfContext.class));
}
}

// Embed all hash and blank nodes
rdfStream.concat(filter(getTriples(HashRdfContext.class), tripleFilter));
rdfStream.concat(filter(getTriples(BlankNodeRdfContext.class), tripleFilter));
rdfStream.concat(filter(getTriples(HashRdfContext.class), tripleFilter::test));
rdfStream.concat(filter(getTriples(BlankNodeRdfContext.class), tripleFilter::test));

// Include inbound references to this object
if (ldpPreferences.prefersReferences()) {
Expand All @@ -278,17 +268,11 @@ public boolean apply(final Triple input) {

final Iterator<FedoraResource> children = resource().getChildren();

rdfStream.concat(filter(concat(transform(children,
new Function<FedoraResource, RdfStream>() {

@Override
public RdfStream apply(final FedoraResource child) {
return child.getTriples(translator(), ImmutableList.of(
TypeRdfContext.class,
PropertiesRdfContext.class,
BlankNodeRdfContext.class));
}
})), tripleFilter));
rdfStream.concat(filter(concat(transform(children, child ->
child.getTriples(translator(),
ImmutableList.of(
TypeRdfContext.class, PropertiesRdfContext.class, BlankNodeRdfContext.class)))),
tripleFilter::test));

}
}
Expand Down Expand Up @@ -602,10 +586,16 @@ protected void replaceResourceWithStream(final FedoraResource resource,
}

protected void patchResourcewithSparql(final FedoraResource resource,
final String requestBody,
final RdfStream resourceTriples)
throws MalformedRdfException, AccessDeniedException {
resource.updateProperties(translator(), requestBody, resourceTriples);
final String requestBody,
final RdfStream resourceTriples)
throws MalformedRdfException, AccessDeniedException {
if (resource instanceof NonRdfSourceDescription) {
// update the description instead
((NonRdfSourceDescription) resource).getDescribedResource()
.updateProperties(translator(), requestBody, resourceTriples);
} else {
resource.updateProperties(translator(), requestBody, resourceTriples);
}
}

/**
Expand Down
Expand Up @@ -161,6 +161,7 @@

/**
* @author cabeer
* @author ajs6f
*/
public class FedoraLdpIT extends AbstractResourceIT {

Expand Down Expand Up @@ -592,7 +593,7 @@ public void testPatchBinaryDescription() throws Exception {
patch.addHeader("Content-Type", "application/sparql-update");
final BasicHttpEntity e = new BasicHttpEntity();
e.setContent(new ByteArrayInputStream(
("INSERT { <" + location +
("INSERT { <" + serverAddress + pid + "/x" +
"> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\" } WHERE {}")
.getBytes()));
patch.setEntity(e);
Expand Down Expand Up @@ -2416,7 +2417,7 @@ public void testEmbeddedChildResources() throws Exception {
final GraphStore graphStore = getGraphStore(httpGet);
assertTrue("Property on child binary should be found!" + graphStore, graphStore.contains(
ANY,
createResource(serverAddress + pid + "/" + binaryId + "/fcr:metadata").asNode(),
createResource(serverAddress + pid + "/" + binaryId).asNode(),
createProperty("http://purl.org/dc/elements/1.1/title").asNode(),
createLiteral("this is a title")));
}
Expand Down Expand Up @@ -2502,5 +2503,21 @@ public String apply(final Header h) {
});
}

@Test
public void testUpdateObjectGraphWithNonLocalTriples() throws IOException {
final String pid = getRandomUniquePid();
createObject(pid);
final String otherPid = getRandomUniquePid();
createObject(otherPid);
final String location = serverAddress + pid;
final String otherLocation = serverAddress + otherPid;
final HttpPatch updateObjectGraphMethod = new HttpPatch(location);
updateObjectGraphMethod.addHeader("Content-Type", "application/sparql-update");
updateObjectGraphMethod.setEntity(new StringEntity("INSERT { <" + location +
"> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\". " + "<" + otherLocation +
"> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\"" + " } WHERE {}"));
assertEquals("It ought not be possible to use PATCH to create non-local triples!",
FORBIDDEN.getStatusCode(),getStatus(updateObjectGraphMethod));
}

}
@@ -0,0 +1,45 @@
/**
* Copyright 2015 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.http.commons.exceptionhandlers;

import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.Response.Status.FORBIDDEN;

import org.fcrepo.kernel.exception.IncorrectTripleSubjectException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;

/**
* @author ajs6f
* @since 2015-07-15
*/
@Provider
public class IncorrectTripleSubjectExceptionMapper extends ConstraintExceptionMapper<IncorrectTripleSubjectException> {

@Context
private UriInfo uriInfo;

@Override
public Response toResponse(final IncorrectTripleSubjectException e) {

final Link link = buildConstraintLink(e, uriInfo);
final String msg = e.getMessage();
return status(FORBIDDEN).entity(msg).links(link).build();
}
}
Expand Up @@ -410,8 +410,8 @@ public void updateProperties(final IdentifierConverter<Resource, FedoraResource>

final Model model = originalTriples.asModel();

final JcrPropertyStatementListener listener =
new JcrPropertyStatementListener(idTranslator, getSession());
final JcrPropertyStatementListener listener = new JcrPropertyStatementListener(
idTranslator, getSession(), idTranslator.reverse().convert(this).asNode());

model.register(listener);

Expand Down Expand Up @@ -537,7 +537,8 @@ public Boolean isNew() {
public void replaceProperties(final IdentifierConverter<Resource, FedoraResource> idTranslator,
final Model inputModel, final RdfStream originalTriples) throws MalformedRdfException {

final RdfStream replacementStream = new RdfStream().namespaces(inputModel.getNsPrefixMap());
final RdfStream replacementStream = new RdfStream().namespaces(inputModel.getNsPrefixMap())
.topic(idTranslator.reverse().convert(this).asNode());

final GraphDifferencingIterator differencer =
new GraphDifferencingIterator(inputModel, originalTriples);
Expand Down
Expand Up @@ -23,6 +23,7 @@

import org.fcrepo.kernel.models.FedoraResource;
import org.fcrepo.kernel.exception.ConstraintViolationException;
import org.fcrepo.kernel.exception.IncorrectTripleSubjectException;
import org.fcrepo.kernel.exception.MalformedRdfException;
import org.fcrepo.kernel.exception.OutOfDomainSubjectException;
import org.fcrepo.kernel.exception.RepositoryRuntimeException;
Expand All @@ -31,6 +32,7 @@

import org.slf4j.Logger;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
Expand Down Expand Up @@ -58,15 +60,17 @@ public class JcrPropertyStatementListener extends StatementListener {

private final List<Exception> exceptions;

private final Node topic;

/**
* Construct a statement listener within the given session
*
* @param idTranslator the id translator
* @param session the session
*/
public JcrPropertyStatementListener(final IdentifierConverter<Resource, FedoraResource> idTranslator,
final Session session) {
this(idTranslator, new JcrRdfTools(idTranslator, session));
final Session session, final Node topic) {
this(idTranslator, new JcrRdfTools(idTranslator, session), topic);
}

/**
Expand All @@ -76,11 +80,12 @@ public JcrPropertyStatementListener(final IdentifierConverter<Resource, FedoraRe
* @param jcrRdfTools the jcr rdf tools
*/
public JcrPropertyStatementListener(final IdentifierConverter<Resource, FedoraResource> idTranslator,
final JcrRdfTools jcrRdfTools) {
final JcrRdfTools jcrRdfTools, final Node topic) {
super();
this.idTranslator = idTranslator;
this.jcrRdfTools = jcrRdfTools;
this.exceptions = new ArrayList<>();
this.topic = topic;
}

/**
Expand All @@ -93,12 +98,27 @@ public void addedStatement(final Statement input) {
LOGGER.debug(">> added statement {}", input);

try {
// if it's not about the right kind of node, ignore it.
final Resource subject = input.getSubject();

// if it's not about a node, ignore it.
if (!idTranslator.inDomain(subject) && !subject.isAnon()) {
LOGGER.error("subject ({}) is not in repository domain.", subject);
throw new OutOfDomainSubjectException(subject.toString());
final String subjectURI = subject.getURI();
// blank nodes are okay
if (!subject.isAnon()) {
// hash URIs with the same base as the topic are okay
final int hashIndex = subjectURI.lastIndexOf("#");
if (!(hashIndex > 0 && topic.getURI().equals(subjectURI.substring(0, hashIndex)))) {
// the topic itself is okay
if (!topic.equals(subject.asNode())) {
// it's a bad subject, but it could still be in-domain
if (idTranslator.inDomain(subject)) {
LOGGER.error("{} is not in the topic of this RDF, which is {}.", subject, topic);
throw new IncorrectTripleSubjectException(subject +
" is not in the topic of this RDF, which is " + topic);
}
// it's not even in the right domain!
LOGGER.error("subject ({}) is not in repository domain.", subject);
throw new OutOfDomainSubjectException(subject.asNode());
}
}
}

final Statement s = jcrRdfTools.skolemize(idTranslator, input);
Expand Down Expand Up @@ -135,12 +155,28 @@ public void removedStatement(final Statement s) {
LOGGER.trace(">> removed statement {}", s);

try {
// if it's not about the right kind of node, ignore it.
final Resource subject = s.getSubject();
final String subjectURI = subject.getURI();
// blank nodes are okay
if (!subject.isAnon()) {
// hash URIs with the same base as the topic are okay
final int hashIndex = subjectURI.lastIndexOf("#");
if (!(hashIndex > 0 && topic.getURI().equals(subjectURI.substring(0, hashIndex)))) {
// the topic itself is okay
if (!topic.equals(subject.asNode())) {
// it's a bad subject, but it could still be in-domain
if (idTranslator.inDomain(subject)) {
LOGGER.error("{} is not in the topic of this RDF, which is {}.", subject, topic);
throw new IncorrectTripleSubjectException(subject +
" is not in the topic of this RDF, which is " + topic);
}
// it's not even in the right domain!
LOGGER.error("subject ({}) is not in repository domain.", subject);
throw new OutOfDomainSubjectException(subject.asNode());
}

// if it's not about a node, we don't care.
if (!idTranslator.inDomain(subject)) {
LOGGER.error("subject ({}) is not in repository domain.", subject);
throw new OutOfDomainSubjectException(subject.toString());
}
}

final FedoraResource resource = idTranslator.convert(subject);
Expand Down

0 comments on commit a2f5259

Please sign in to comment.