Skip to content

Commit

Permalink
Suppress JCR types from RDF representation
Browse files Browse the repository at this point in the history
  • Loading branch information
acoburn authored and Andrew Woods committed Nov 4, 2015
1 parent 84c12da commit 0014cd7
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 51 deletions.
4 changes: 1 addition & 3 deletions fcrepo-http-api/src/main/resources/views/common-metadata.vsl
@@ -1,6 +1,4 @@
<dl>
<dt>UUID</dt> <dd>$esc.html($helpers.getObjectsAsString($rdf, $topic, $rdfLexicon.HAS_PRIMARY_IDENTIFIER, true))</dd>

<dt>Created at</dt> <dd>$helpers.getObjectsAsString($rdf, $topic, $rdfLexicon.CREATED_DATE, true)
by $esc.html($helpers.getObjectsAsString($rdf, $topic, $rdfLexicon.CREATED_BY, true))</dd>

Expand All @@ -16,4 +14,4 @@
</ol>
</dd>

</dl>
</dl>
Expand Up @@ -20,10 +20,9 @@
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;
import static com.hp.hpl.jena.graph.NodeFactory.createLiteral;
import static com.hp.hpl.jena.graph.Node.ANY;
import static com.hp.hpl.jena.graph.NodeFactory.createURI;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FEDORA_PAIRTREE;
import static com.hp.hpl.jena.vocabulary.RDF.type;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -67,8 +66,8 @@ public void shouldRoundTripOnePairtree() throws IOException {

try (final CloseableGraphStore graphStore = getGraphStore(new HttpGet(serverAddress + pairtreeName))) {
assertTrue("Resource \"" + objName + " " + pairtreeName + "\" must be pairtree.",
graphStore.contains(ANY, createURI(serverAddress + pairtreeName),
createURI(REPOSITORY_NAMESPACE + "mixinTypes"), createLiteral(FEDORA_PAIRTREE)));
graphStore.contains(ANY, createURI(serverAddress + pairtreeName), type.asNode(),
createURI(REPOSITORY_NAMESPACE + "Pairtree")));
}
testRoundtrip(pairtreeName);
}
Expand Down
Expand Up @@ -57,8 +57,6 @@
import static org.fcrepo.http.commons.domain.RDFMediaType.POSSIBLE_RDF_RESPONSE_VARIANTS_STRING;
import static org.fcrepo.http.commons.domain.RDFMediaType.POSSIBLE_RDF_VARIANTS;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FCR_METADATA;
import static org.fcrepo.kernel.api.FedoraJcrTypes.ROOT;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FEDORA_PAIRTREE;
import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER;
import static org.fcrepo.kernel.api.RdfLexicon.CONSTRAINED_BY;
import static org.fcrepo.kernel.api.RdfLexicon.CONTAINS;
Expand All @@ -67,6 +65,7 @@
import static org.fcrepo.kernel.api.RdfLexicon.HAS_CHILD;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_MEMBER_RELATION;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_MIME_TYPE;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_MIXIN_TYPE;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_ORIGINAL_NAME;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_PRIMARY_IDENTIFIER;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_PRIMARY_TYPE;
Expand All @@ -77,6 +76,7 @@
import static org.fcrepo.kernel.api.RdfLexicon.LDP_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.MEMBERSHIP_RESOURCE;
import static org.fcrepo.kernel.api.RdfLexicon.MIX_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.MODE_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -851,8 +851,7 @@ public void testIngestOnPairtree() throws IOException {

try (final CloseableGraphStore graphStore = getGraphStore(getObjMethod(pairtreeName))) {
assertTrue("Resource \"" + objName + " " + pairtreeName + "\" must be pairtree.", graphStore.contains(ANY,
createURI(serverAddress + pairtreeName), createURI(REPOSITORY_NAMESPACE + "mixinTypes"),
createLiteral(FEDORA_PAIRTREE)));
createURI(serverAddress + pairtreeName), type.asNode(), createURI(REPOSITORY_NAMESPACE + "Pairtree")));
}
// Attempting to POST to the child of the pairtree node...
final int status = getStatus(postObjMethod(pairtreeName));
Expand Down Expand Up @@ -937,8 +936,8 @@ public void testDeleteDatastream() throws IOException {
public void testGetRepositoryGraph() throws IOException {
try (final CloseableGraphStore graph = getGraphStore(getObjMethod(""))) {
logger.trace("Retrieved repository graph:\n" + graph);
assertTrue("Should find the root type", graph.contains(ANY,
ANY, HAS_PRIMARY_TYPE.asNode(), createLiteral(ROOT)));
assertFalse("Should not find the root type", graph.contains(ANY,
ANY, type.asNode(), createURI(MODE_NAMESPACE + "root")));
}
}

Expand Down Expand Up @@ -990,14 +989,14 @@ public void verifyFullSetOfRdfTypes() throws IOException {
final Node resource = createURI(serverAddress + id);
verifyResource(graph, resource, REPOSITORY_NAMESPACE, "Container");
verifyResource(graph, resource, REPOSITORY_NAMESPACE, "Resource");
verifyResource(graph, resource, MIX_NAMESPACE, "created");
verifyResource(graph, resource, MIX_NAMESPACE, "lastModified");
verifyResource(graph, resource, MIX_NAMESPACE, "referenceable");
verifyResource(graph, resource, MIX_NAMESPACE, "simpleVersionable");
verifyResource(graph, resource, MIX_NAMESPACE, "versionable");
verifyResource(graph, resource, JCR_NT_NAMESPACE, "base");
verifyResource(graph, resource, JCR_NT_NAMESPACE, "folder");
verifyResource(graph, resource, JCR_NT_NAMESPACE, "hierarchyNode");
verifyAbsenceOfResource(graph, resource, MIX_NAMESPACE, "created");
verifyAbsenceOfResource(graph, resource, MIX_NAMESPACE, "lastModified");
verifyAbsenceOfResource(graph, resource, MIX_NAMESPACE, "referenceable");
verifyAbsenceOfResource(graph, resource, MIX_NAMESPACE, "simpleVersionable");
verifyAbsenceOfResource(graph, resource, MIX_NAMESPACE, "versionable");
verifyAbsenceOfResource(graph, resource, JCR_NT_NAMESPACE, "base");
verifyAbsenceOfResource(graph, resource, JCR_NT_NAMESPACE, "folder");
verifyAbsenceOfResource(graph, resource, JCR_NT_NAMESPACE, "hierarchyNode");
}
logger.trace("Leaving verifyFullSetOfRdfTypes()...");
}
Expand All @@ -1006,6 +1005,12 @@ private static void verifyResource(final GraphStore g, final Node subject, final
assertTrue("Should find type: " + ns + type, g.contains(ANY, subject, rdfType, createURI(ns + type)));
}

private static void verifyAbsenceOfResource(final GraphStore g, final Node subject, final String ns,
final String type) {
assertFalse("Should not find type: " + ns + type, g.contains(ANY, subject, rdfType, createURI(ns + type)));
}


@Test
public void testGetObjectGraphWithChild() throws IOException {
final String id = getRandomUniqueId();
Expand Down Expand Up @@ -1207,6 +1212,28 @@ public void testGetObjectGraphLacksUUID() throws Exception {
}
}

@Test
public void testGetObjectGraphLacksPrimaryType() throws Exception {
final String location = getLocation(postObjMethod());
final HttpGet getObjMethod = new HttpGet(location);
try (final CloseableGraphStore graphStore = getGraphStore(getObjMethod)) {
final Iterator<Quad> iterator =
graphStore.find(ANY, createURI(location), HAS_PRIMARY_TYPE.asNode(), ANY);
assertFalse("Graph should not contain a primaryType!", iterator.hasNext());
}
}

@Test
public void testGetObjectGraphLacksMixinType() throws Exception {
final String location = getLocation(postObjMethod());
final HttpGet getObjMethod = new HttpGet(location);
try (final CloseableGraphStore graphStore = getGraphStore(getObjMethod)) {
final Iterator<Quad> iterator =
graphStore.find(ANY, createURI(location), HAS_MIXIN_TYPE.asNode(), ANY);
assertFalse("Graph should not contain a mixinType!", iterator.hasNext());
}
}

@Test
public void testLinkToNonExistent() throws IOException {
final HttpPatch patch = new HttpPatch(getLocation(postObjMethod()));
Expand Down
Expand Up @@ -32,9 +32,10 @@
import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE;
import static org.fcrepo.kernel.api.RdfLexicon.DC_TITLE;
import static org.fcrepo.kernel.api.RdfLexicon.EMBED_CONTAINS;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_PRIMARY_TYPE;
import static org.fcrepo.kernel.api.RdfLexicon.JCR_NT_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_SERIALIZATION;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_VERSION;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_VERSION_HISTORY;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_VERSION_LABEL;
import static org.fcrepo.kernel.api.RdfLexicon.MIX_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
Expand Down Expand Up @@ -148,7 +149,9 @@ public void testAddAndRetrieveVersion() throws IOException {
logger.debug("Got version profile:");

assertTrue("Didn't find a version triple!", versionResults.contains(ANY,
ANY, HAS_PRIMARY_TYPE.asNode(), createLiteral("nt:frozenNode")));
ANY, type.asNode(), createURI(REPOSITORY_NAMESPACE + "Version")));
assertFalse("Found a jcr version triple!", versionResults.contains(ANY,
ANY, type.asNode(), createURI(JCR_NT_NAMESPACE + "frozenNode")));
assertTrue("Should find a title in historic version", versionResults.contains(ANY,
ANY, DC_TITLE.asNode(), ANY));
assertTrue("Should find original title in historic version", versionResults.contains(ANY,
Expand Down Expand Up @@ -429,8 +432,8 @@ public void testVersionOperationAddsVersionableMixin() throws IOException {
}
postObjectVersion(id, "label");
try (final CloseableGraphStore updatedObjectProperties = getContent(serverAddress + id)) {
assertTrue("Node is expected to have versionable mixin.", updatedObjectProperties.contains(ANY,
subject, type.asNode(), createURI(MIX_NAMESPACE + "versionable")));
assertTrue("Node is expected to contain hasVersions triple.", updatedObjectProperties.contains(ANY,
subject, HAS_VERSION_HISTORY.asNode(), ANY));
}
}

Expand Down
Expand Up @@ -15,9 +15,6 @@
*/
package org.fcrepo.kernel.api;

import static com.google.common.collect.ImmutableList.of;
import java.util.List;

/**
* Convenience class with constants for commonly used JCR types.
*
Expand Down Expand Up @@ -76,6 +73,8 @@ public interface FedoraJcrTypes {

String FROZEN_MIXIN_TYPES = "jcr:frozenMixinTypes";

String FROZEN_PRIMARY_TYPE = "jcr:frozenPrimaryType";

String JCR_PRIMARY_TYPE = "jcr:primaryType";

String JCR_MIXIN_TYPES = "jcr:mixinTypes";
Expand All @@ -86,6 +85,4 @@ public interface FedoraJcrTypes {
String LDP_IS_MEMBER_OF_RELATION = "ldp:isMemberOfRelation";
String LDP_MEMBER_RESOURCE = "ldp:membershipResource";

List<String> EXPOSED_PROTECTED_JCR_TYPES = of(JCR_LASTMODIFIED, JCR_CREATED, JCR_CREATEDBY,
JCR_PRIMARY_TYPE, JCR_MIXIN_TYPES);
}
Expand Up @@ -26,13 +26,15 @@
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.codec.digest.DigestUtils.shaHex;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.isFrozen;
import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.property2values;
import static org.fcrepo.kernel.api.utils.UncheckedFunction.uncheck;
import static org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter.nodeConverter;
import static org.fcrepo.kernel.modeshape.rdf.JcrRdfTools.getRDFNamespaceForJcrNamespace;
import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isFrozenNode;
import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalNode;
import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalType;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
import static org.slf4j.LoggerFactory.getLogger;

Expand Down Expand Up @@ -399,13 +401,20 @@ public List<URI> getTypes() {
.flatMap(Arrays::stream)
.forEach(nodeTypes::add);

return nodeTypes.stream()
.map(uncheck(x -> x.getName()))
final List<URI> types = nodeTypes.stream()
.filter(isInternalType.negate())
.map(uncheck(NodeType::getName))
.distinct()
.map(nodeTypeNameToURI::apply)
.peek(x -> LOGGER.debug("node has rdf:type {}", x))
.collect(Collectors.toList());

if (isFrozenResource()) {
types.add(URI.create(REPOSITORY_NAMESPACE + "Version"));
}

return types;

} catch (final PathNotFoundException e) {
throw new PathNotFoundRuntimeException(e);
} catch (final RepositoryException e) {
Expand Down
Expand Up @@ -16,11 +16,9 @@
package org.fcrepo.kernel.modeshape.rdf.impl;

import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalProperty;
import static org.fcrepo.kernel.api.utils.UncheckedPredicate.uncheck;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.Iterator;
import java.util.function.Predicate;

import javax.jcr.Property;
import javax.jcr.RepositoryException;
Expand All @@ -47,8 +45,6 @@ public class PropertiesRdfContext extends NodeRdfContext {

private static final Logger LOGGER = getLogger(PropertiesRdfContext.class);

private static final Predicate<Property> IS_NOT_UUID = uncheck(p -> !p.getName().equals("jcr:uuid"));

/**
* Default constructor.
*
Expand All @@ -70,7 +66,7 @@ private Iterator<Triple> triplesFromProperties(final FedoraResource n)
LOGGER.trace("Creating triples for node: {}", n);
final Iterator<Property> nodeProps = n.getNode().getProperties();
final Iterator<Property> properties = Iterators.filter(nodeProps,
isInternalProperty.negate().and(IS_NOT_UUID)::test);
isInternalProperty.negate()::test);
return flatMap(properties, property2triple);
}
}
Expand Up @@ -17,6 +17,7 @@

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import org.fcrepo.kernel.api.FedoraJcrTypes;
Expand All @@ -36,6 +37,18 @@
import static java.util.Arrays.stream;
import static javax.jcr.PropertyType.REFERENCE;
import static javax.jcr.PropertyType.WEAKREFERENCE;
import static com.google.common.collect.ImmutableSet.of;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FROZEN_MIXIN_TYPES;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FROZEN_PRIMARY_TYPE;
import static org.fcrepo.kernel.api.FedoraJcrTypes.FROZEN_NODE;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_CONTENT;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_CREATED;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_CREATEDBY;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_LASTMODIFIED;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_MIXIN_TYPES;
import static org.fcrepo.kernel.api.FedoraJcrTypes.JCR_PRIMARY_TYPE;
import static org.fcrepo.kernel.api.FedoraJcrTypes.ROOT;
import static org.fcrepo.kernel.api.FedoraJcrTypes.VERSIONABLE;
import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.isBinaryContentProperty;
import static org.fcrepo.kernel.api.utils.UncheckedPredicate.uncheck;
import static org.slf4j.LoggerFactory.getLogger;
Expand All @@ -53,6 +66,34 @@ public abstract class FedoraTypesUtils implements FedoraJcrTypes {

private static final Logger LOGGER = getLogger(FedoraTypesUtils.class);

private static Set<String> privateProperties = of(
"jcr:mime",
"jcr:mimeType",
"jcr:frozenUuid",
"jcr:uuid",
JCR_CONTENT,
JCR_PRIMARY_TYPE,
JCR_MIXIN_TYPES,
FROZEN_MIXIN_TYPES,
FROZEN_PRIMARY_TYPE);

private static Set<String> protectedProperties = of(
JCR_CREATED,
JCR_CREATEDBY,
JCR_LASTMODIFIED);

private static Set<String> protectedTypes = of(
"mix:created",
"mix:lastModified",
"mix:referenceable",
"mix:simpleVersionable",
"nt:base",
"nt:folder",
"nt:hierarchyNode",
VERSIONABLE,
ROOT,
FROZEN_NODE);

/**
* Predicate for determining whether this {@link Node} is a {@link org.fcrepo.kernel.api.models.Container}.
*/
Expand Down Expand Up @@ -93,7 +134,7 @@ public abstract class FedoraTypesUtils implements FedoraJcrTypes {
* Check whether a property is protected (ie, cannot be modified directly) but
* is not one we've explicitly chosen to include.
*/
public static Predicate<Property> isProtectedAndShouldBeHidden = uncheck(p -> {
private static Predicate<Property> isProtectedAndShouldBeHidden = uncheck(p -> {
if (!p.getDefinition().isProtected()) {
return false;
} else if (p.getParent().isNodeType(FROZEN_NODE)) {
Expand All @@ -103,22 +144,22 @@ public abstract class FedoraTypesUtils implements FedoraJcrTypes {
// things cannot be edited.
return false;
} else {
return !EXPOSED_PROTECTED_JCR_TYPES.stream().anyMatch(p.getName()::equals);
return !protectedProperties.contains(p.getName());
}
});

/**
* Check if a property is intentionally suppressed.
*/
private static Predicate<Property> isSuppressedProperty = uncheck(p -> p.getName().equals("jcr:mimeType"));

/**
* Check whether a property is an internal property that should be suppressed
* from external output.
*/
public static Predicate<Property> isInternalProperty = p -> isBinaryContentProperty.test(p) ||
isProtectedAndShouldBeHidden.test(p) || isSuppressedProperty.test(p);
public static Predicate<Property> isInternalProperty = isBinaryContentProperty
.or(isProtectedAndShouldBeHidden::test)
.or(uncheck(p -> privateProperties.contains(p.getName())));

/**
* Check whether a type should be internal.
*/
public static Predicate<NodeType> isInternalType = uncheck(p -> protectedTypes.contains(p.getName()));

/**
* Check if a node is "internal" and should not be exposed e.g. via the REST
Expand Down

4 comments on commit 0014cd7

@awoods
Copy link

@awoods awoods commented on 0014cd7 Nov 30, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acoburn, it appears that this commit broke the "Revert/Delete historic version" capability in the HTML UI. I will give it a look tomorrow... unless you beat me to it.

@awoods
Copy link

@awoods awoods commented on 0014cd7 Nov 30, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests fail... but this seems to get the functionality back. It could obviously use some Java8 love (ViewHelpers.java).

     public boolean isFrozenNode(final Graph graph, final Node subject) {
-        return getValue(graph, subject, HAS_PRIMARY_TYPE.asNode()).filter("nt:frozenNode"::equals).isPresent();
+        final Iterator<Node> objects = listObjects(graph, subject, RDF.type.asNode());
+        while (objects.hasNext()) {
+            final Node object = objects.next();
+            if (object.isURI() && object.getURI().equals(REPOSITORY_NAMESPACE + "Version")) {
+                return true;
+            }
+        }
+        return false;
     }

@acoburn
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@awoods: I have a fix for this in this PR: #959

@awoods
Copy link

@awoods awoods commented on 0014cd7 Nov 30, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@acoburn++
When we have a moment, the following instances of "frozenNode" should also be assessed:
https://gist.github.com/awoods/1bbc2f6dc773fe0d9313

Please sign in to comment.