Skip to content

Commit

Permalink
Optionally include inbound references in the object rdf serialization
Browse files Browse the repository at this point in the history
Inbound references can be included in the GET /some/object/path response
by using the Prefer: header, e.g.:

```
GET /some/object/path HTTP/1.1
...
Prefer: return="representation"; include="http://fedora.info/definitions/v4/repository#InboundReferences"
...

```

And the response will include inbound triples, e.g.:

```
</some/other/path> <fedorarelsext:isPartOf> </some/object/path>
```

;# Please enter the commit message for your changes. Lines starting
  • Loading branch information
cbeer authored and Andrew Woods committed May 6, 2014
1 parent 38482a1 commit d77194c
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 2 deletions.
Expand Up @@ -49,6 +49,7 @@
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_DATASTREAM;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_OBJECT;
import static org.fcrepo.kernel.RdfLexicon.FIRST_PAGE;
import static org.fcrepo.kernel.RdfLexicon.INBOUND_REFERENCES;
import static org.fcrepo.kernel.RdfLexicon.LDP_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.NEXT_PAGE;
import static org.fcrepo.kernel.rdf.GraphProperties.PROBLEMS_MODEL_NAME;
Expand Down Expand Up @@ -259,6 +260,9 @@ public RdfStream describe(@PathParam("path") final List<PathSegment> pathList,
&& !contains(omits, LDP_NAMESPACE + "PreferContainment");


final boolean references = contains(includes, INBOUND_REFERENCES.toString())
&& !contains(omits, INBOUND_REFERENCES.toString());

final HierarchyRdfContextOptions hierarchyRdfContextOptions
= new HierarchyRdfContextOptions(limit, offset, membership, containment);

Expand All @@ -270,6 +274,11 @@ public RdfStream describe(@PathParam("path") final List<PathSegment> pathList,
appliedIncludes.add(LDP_NAMESPACE + "PreferContainment");
}

if (references) {
rdfStream.concat(resource.getReferencesTriples(subjects));
appliedIncludes.add(INBOUND_REFERENCES.toString());
}

rdfStream.concat(resource.getHierarchyTriples(subjects, hierarchyRdfContextOptions));

final String preferences = "return=representation; include=\""
Expand Down
Expand Up @@ -46,6 +46,7 @@
import static org.fcrepo.kernel.RdfLexicon.DC_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.DC_TITLE;
import static org.fcrepo.kernel.RdfLexicon.FIRST_PAGE;
import static org.fcrepo.kernel.RdfLexicon.INBOUND_REFERENCES;
import static org.fcrepo.kernel.RdfLexicon.NEXT_PAGE;
import static org.fcrepo.kernel.RdfLexicon.HAS_CHILD;
import static org.fcrepo.kernel.RdfLexicon.HAS_OBJECT_COUNT;
Expand Down Expand Up @@ -79,6 +80,7 @@

import javax.ws.rs.core.Variant;

import com.hp.hpl.jena.graph.NodeFactory;
import nu.validator.htmlparser.sax.HtmlParser;
import nu.validator.saxtree.TreeBuilder;

Expand All @@ -98,6 +100,7 @@
import org.apache.http.impl.client.cache.CachingHttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.fcrepo.http.commons.domain.RDFMediaType;
import org.fcrepo.kernel.RdfLexicon;
import org.junit.Ignore;
import org.junit.Test;
import org.xml.sax.ErrorHandler;
Expand Down Expand Up @@ -577,6 +580,51 @@ public void testGetObjectOmitContainment() throws Exception {

}

@Test
public void testGetObjectReferences() throws Exception {
final String pid = getRandomUniquePid();
createObject(pid);
createObject(pid + "/a");
createObject(pid + "/b");

final HttpPatch updateObjectGraphMethod = new HttpPatch(serverAddress + pid + "/a");

updateObjectGraphMethod.addHeader("Content-Type", "application/sparql-update");

BasicHttpEntity e = new BasicHttpEntity();
e.setContent(
new ByteArrayInputStream(
("INSERT { " +
"<" + serverAddress + pid + "/a" + "> <http://fedora.info/definitions/v4/rels-ext#isPartOf> <" + serverAddress + pid + "/b" + "> . \n" +
"<" + serverAddress + pid + "/a" + "> <info:xyz#some-other-property> <" + serverAddress + pid + "/b" + "> " +
"} WHERE {}").getBytes()
)
);

updateObjectGraphMethod.setEntity(e);
client.execute(updateObjectGraphMethod);

final HttpGet getObjMethod = new HttpGet(serverAddress + pid + "/b");

getObjMethod.addHeader("Prefer", "return=representation; include=\"" + INBOUND_REFERENCES.toString() + "\"");
getObjMethod.addHeader("Accept", "application/n-triples");

final GraphStore graphStore = getGraphStore(getObjMethod);

assertTrue(graphStore.contains(Node.ANY,
NodeFactory.createURI(serverAddress + pid + "/a"),
NodeFactory.createURI("http://fedora.info/definitions/v4/rels-ext#isPartOf"),
NodeFactory.createURI(serverAddress + pid + "/b")
));

assertTrue(graphStore.contains(Node.ANY,
NodeFactory.createURI(serverAddress + pid + "/a"),
NodeFactory.createURI("info:xyz#some-other-property"),
NodeFactory.createURI(serverAddress + pid + "/b")
));

}

@Test
public void testGetObjectGraphByUUID() throws Exception {
final HttpResponse createResponse = createObject("");
Expand Down
Expand Up @@ -146,6 +146,14 @@ RdfStream getHierarchyTriples(final IdentifierTranslator graphSubjects,
RdfStream getVersionTriples(final IdentifierTranslator graphSubjects)
throws RepositoryException;

/**
* Serialize inbound References to this object as an {@link RdfStream}
* @param graphSubjects
* @return
* @throws RepositoryException
*/
RdfStream getReferencesTriples(final IdentifierTranslator graphSubjects) throws RepositoryException;

/**
* Tag the current version of the Node with a version label that
* can be retrieved by name later.
Expand Down
Expand Up @@ -294,6 +294,7 @@ public final class RdfLexicon {
// RDF EXTRACTION
public static final Property COULD_NOT_STORE_PROPERTY =
createProperty(REPOSITORY_NAMESPACE + "couldNotStoreProperty");
public static final Property INBOUND_REFERENCES = createProperty(REPOSITORY_NAMESPACE + "InboundReferences");

// IMPORTANT JCR PROPERTIES
public static final Property HAS_PRIMARY_IDENTIFIER =
Expand Down
Expand Up @@ -304,6 +304,11 @@ public RdfStream getVersionTriples(final IdentifierTranslator graphSubjects)
.getVersionTriples(node);
}

@Override
public RdfStream getReferencesTriples(final IdentifierTranslator graphSubjects) throws RepositoryException {
return JcrRdfTools.withContext(graphSubjects, node.getSession()).getReferencesTriples(node);
}

/* (non-Javadoc)
* @see org.fcrepo.kernel.FedoraResource#addVersionLabel(java.lang.String)
*/
Expand Down
25 changes: 23 additions & 2 deletions fcrepo-kernel/src/main/java/org/fcrepo/kernel/rdf/JcrRdfTools.java
Expand Up @@ -32,7 +32,9 @@
import static org.fcrepo.kernel.RdfLexicon.JCR_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.LDP_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.fcrepo.kernel.utils.FedoraTypesUtils.isReferenceProperty;
import static org.fcrepo.kernel.utils.NamespaceTools.getNamespaceRegistry;
import static org.fcrepo.kernel.utils.NodePropertiesTools.getReferencePropertyOriginalName;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.Iterator;
Expand All @@ -53,6 +55,7 @@
import org.fcrepo.kernel.rdf.impl.HierarchyRdfContext;
import org.fcrepo.kernel.rdf.impl.NamespaceRdfContext;
import org.fcrepo.kernel.rdf.impl.PropertiesRdfContext;
import org.fcrepo.kernel.rdf.impl.ReferencesRdfContext;
import org.fcrepo.kernel.rdf.impl.VersionsRdfContext;
import org.fcrepo.kernel.rdf.impl.WorkspaceRdfContext;
import org.fcrepo.kernel.services.LowLevelStorageService;
Expand Down Expand Up @@ -329,6 +332,17 @@ public RdfStream getTreeTriples(final Node node) throws RepositoryException {
return getTreeTriples(node, HierarchyRdfContextOptions.DEFAULT);
}


/**
* Add the properties for inbound references to this node
* @param node
* @return
* @throws RepositoryException
*/
public RdfStream getReferencesTriples(final Node node) throws RepositoryException {
return new ReferencesRdfContext(node, graphSubjects);
}

/**
* Decides whether the RDF representation of this {@link Node} will receive LDP Container status.
*
Expand Down Expand Up @@ -652,9 +666,17 @@ public com.hp.hpl.jena.rdf.model.Property apply(
if (property instanceof Namespaced) {
final Namespaced nsProperty = (Namespaced) property;
final String uri = nsProperty.getNamespaceURI();
final String localName = nsProperty.getLocalName();
final String rdfLocalName;

if (isReferenceProperty.apply(property)) {
rdfLocalName = getReferencePropertyOriginalName(localName);
} else {
rdfLocalName = localName;
}
return createProperty(
getRDFNamespaceForJcrNamespace(uri),
nsProperty.getLocalName());
rdfLocalName);
}
return createProperty(property.getName());
} catch (final RepositoryException e) {
Expand All @@ -663,5 +685,4 @@ public com.hp.hpl.jena.rdf.model.Property apply(

}
};

}
@@ -0,0 +1,88 @@
/**
* Copyright 2014 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.kernel.rdf.impl;

import com.google.common.collect.Iterators;
import com.hp.hpl.jena.graph.Triple;
import org.fcrepo.kernel.rdf.IdentifierTranslator;
import org.fcrepo.kernel.rdf.impl.mappings.PropertyToTriple;
import org.fcrepo.kernel.rdf.impl.mappings.ZippingIterator;
import org.fcrepo.kernel.utils.iterators.PropertyIterator;
import org.fcrepo.kernel.utils.iterators.RdfStream;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;

import java.util.Iterator;

import static org.fcrepo.kernel.utils.FedoraTypesUtils.property2values;

/**
* Accumulate inbound references to a given node
*
* @author cabeer
*/
public class ReferencesRdfContext extends RdfStream {

private final Node node;
private PropertyToTriple property2triple;

/**
* Add the inbound references from other nodes to this node to the {@link RdfStream}
*
* @param node
* @param graphSubjects
* @throws javax.jcr.RepositoryException
*/

public ReferencesRdfContext(final javax.jcr.Node node, final IdentifierTranslator graphSubjects)
throws RepositoryException {
super();
this.node = node;
property2triple = new PropertyToTriple(graphSubjects);
concat(putStrongReferencePropertiesIntoContext());
concat(putWeakReferencePropertiesIntoContext());
}

private Iterator<Triple> putWeakReferencePropertiesIntoContext() throws RepositoryException {
final Iterator<Property> properties = new PropertyIterator(node.getWeakReferences());

final Iterator<Property> propertiesCopy = new PropertyIterator(node.getWeakReferences());

return zipPropertiesToTriples(properties, propertiesCopy);

}

private Iterator<Triple> putStrongReferencePropertiesIntoContext() throws RepositoryException {
final Iterator<Property> properties = new PropertyIterator(node.getReferences());

final Iterator<Property> propertiesCopy = new PropertyIterator(node.getReferences());

return zipPropertiesToTriples(properties, propertiesCopy);

}

private Iterator<Triple> zipPropertiesToTriples(final Iterator<Property> propertyIterator,
final Iterator<Property> propertyIteratorCopy) {
return Iterators.concat(
new ZippingIterator<>(
Iterators.transform(propertyIterator, property2values),
Iterators.transform(propertyIteratorCopy, property2triple)
)
);
}
}
Expand Up @@ -234,6 +234,21 @@ public static String getReferencePropertyName(final String propertyName) {
return propertyName + REFERENCE_PROPERTY_SUFFIX;
}

/**
* Given an internal reference node property, get the original name
* @param refPropertyName
* @return
*/
public static String getReferencePropertyOriginalName(final String refPropertyName) {
final int i = refPropertyName.lastIndexOf(REFERENCE_PROPERTY_SUFFIX);

if (i < 0) {
return refPropertyName;
} else {
return refPropertyName.substring(0, i);
}
}

private static String getReferencePropertyName(final Property property) throws RepositoryException {
return getReferencePropertyName(property.getName());
}
Expand Down
Expand Up @@ -40,8 +40,10 @@

import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.UUID;

import javax.inject.Inject;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.PropertyType;
Expand All @@ -50,6 +52,8 @@
import javax.jcr.Session;
import javax.jcr.nodetype.NodeTypeManager;

import com.hp.hpl.jena.rdf.model.ResourceFactory;
import org.fcrepo.kernel.FedoraObject;
import org.fcrepo.kernel.FedoraResource;
import org.fcrepo.kernel.exception.InvalidChecksumException;
import org.fcrepo.kernel.rdf.impl.DefaultIdentifierTranslator;
Expand Down Expand Up @@ -523,4 +527,24 @@ public void testEtagValue() throws RepositoryException {
assertNotNull(actual);
assertNotEquals("", actual);
}

@Test
public void testGetReferences() throws RepositoryException {
final String pid = UUID.randomUUID().toString();
objectService.createObject(session, pid);
final FedoraObject subject = objectService.createObject(session, pid + "/a");
final FedoraObject object = objectService.createObject(session, pid + "/b");
final Value value = session.getValueFactory().createValue(object.getNode());
subject.getNode().setProperty("fedorarelsext:isPartOf", new Value[] { value });

session.save();

final Model model = object.getReferencesTriples(subjects).asModel();

assertTrue(
model.contains(subjects.getSubject(subject.getPath()),
ResourceFactory.createProperty("http://fedora.info/definitions/v4/rels-ext#isPartOf"),
subjects.getSubject(object.getPath()))
);
}
}

0 comments on commit d77194c

Please sign in to comment.