/*
* Licensed to DuraSpace under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* DuraSpace licenses this file to you 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.integration.kernel.modeshape;
import static java.net.URI.create;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptySet;
import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring;
import static org.apache.jena.graph.Node.ANY;
import static org.apache.jena.graph.NodeFactory.createLiteral;
import static org.apache.jena.graph.NodeFactory.createURI;
import static org.apache.jena.rdf.model.ResourceFactory.createPlainLiteral;
import static org.apache.jena.rdf.model.ResourceFactory.createProperty;
import static org.apache.jena.rdf.model.ResourceFactory.createResource;
import static org.apache.jena.vocabulary.RDF.type;
import static org.apache.jena.vocabulary.DC.title;
import static javax.jcr.PropertyType.BINARY;
import static org.fcrepo.kernel.api.FedoraTypes.FCR_VERSIONS;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_CONTAINER;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_NON_RDF_SOURCE_DESCRIPTION;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_REPOSITORY_ROOT;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_RESOURCE;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_TOMBSTONE;
import static org.fcrepo.kernel.api.RdfCollectors.toModel;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_VERSION;
import static org.fcrepo.kernel.api.RdfLexicon.HAS_VERSION_LABEL;
import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.fcrepo.kernel.api.RequiredRdfContext.INBOUND_REFERENCES;
import static org.fcrepo.kernel.api.RequiredRdfContext.PROPERTIES;
import static org.fcrepo.kernel.api.RequiredRdfContext.SERVER_MANAGED;
import static org.fcrepo.kernel.api.RequiredRdfContext.VERSIONS;
import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.getJcrSession;
import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.FIELD_DELIMITER;
import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.ROOT;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.HAS_MIXIN_TYPE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.HAS_NODE_TYPE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.HAS_PRIMARY_TYPE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.HAS_PRIMARY_IDENTIFIER;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.JCR_NAMESPACE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.JCR_NT_NAMESPACE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.MIX_NAMESPACE;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.MODE_NAMESPACE;
import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getJcrNode;
import static org.fcrepo.kernel.modeshape.utils.UncheckedPredicate.uncheck;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.time.Instant;
import javax.inject.Inject;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;
import javax.jcr.version.Version;
import com.google.common.collect.Iterators;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Triple;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.commons.io.IOUtils;
import org.fcrepo.kernel.api.FedoraRepository;
import org.fcrepo.kernel.api.FedoraSession;
import org.fcrepo.kernel.api.exception.AccessDeniedException;
import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.api.exception.InvalidChecksumException;
import org.fcrepo.kernel.api.exception.InvalidPrefixException;
import org.fcrepo.kernel.api.exception.MalformedRdfException;
import org.fcrepo.kernel.api.models.FedoraBinary;
import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
import org.fcrepo.kernel.api.models.Container;
import org.fcrepo.kernel.api.models.FedoraResource;
import org.fcrepo.kernel.api.services.BinaryService;
import org.fcrepo.kernel.api.services.NodeService;
import org.fcrepo.kernel.api.services.ContainerService;
import org.fcrepo.kernel.api.services.VersionService;
import org.fcrepo.kernel.api.RdfStream;
import org.fcrepo.kernel.api.rdf.DefaultRdfStream;
import org.fcrepo.kernel.modeshape.FedoraResourceImpl;
import org.fcrepo.kernel.modeshape.NonRdfSourceDescriptionImpl;
import org.fcrepo.kernel.modeshape.rdf.impl.DefaultIdentifierTranslator;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.modeshape.jcr.security.SimplePrincipal;
import org.springframework.test.context.ContextConfiguration;
import org.apache.jena.graph.Node;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.RDF;
/**
* <p>FedoraResourceImplIT class.</p>
*
* @author ajs6f
*/
@ContextConfiguration({"/spring-test/repo.xml"})
public class FedoraResourceImplIT extends AbstractIT {
@Inject
FedoraRepository repo;
@Inject
NodeService nodeService;
@Inject
ContainerService containerService;
@Inject
BinaryService binaryService;
@Inject
VersionService versionService;
private FedoraSession session;
private DefaultIdentifierTranslator subjects;
@Before
public void setUp() throws RepositoryException {
session = repo.login();
subjects = new DefaultIdentifierTranslator(getJcrSession(session));
}
@After
public void tearDown() {
session.expire();
}
@Test
public void testGetRootNode() throws RepositoryException {
final FedoraSession session = repo.login();
final FedoraResource object = nodeService.find(session, "/");
assertEquals("/", object.getPath());
assertTrue(object.hasType(ROOT));
assertTrue(object.hasType(FEDORA_REPOSITORY_ROOT));
session.expire();
}
private Node createGraphSubjectNode(final FedoraResource obj) {
return subjects.reverse().convert(obj).asNode();
}
@Test
public void testRandomNodeGraph() {
final FedoraResource object = containerService.findOrCreate(session, "/testNodeGraph");
final Node s = subjects.reverse().convert(object).asNode();
final Model rdf = object.getTriples(subjects, PROPERTIES).collect(toModel());
assertFalse(rdf.getGraph().contains(s, HAS_PRIMARY_IDENTIFIER.asNode(), ANY));
assertFalse(rdf.getGraph().contains(s, HAS_PRIMARY_TYPE.asNode(), ANY));
assertFalse(rdf.getGraph().contains(s, HAS_NODE_TYPE.asNode(), ANY));
assertFalse(rdf.getGraph().contains(s, HAS_MIXIN_TYPE.asNode(), ANY));
}
@Test
public void testLastModified() throws RepositoryException {
final String pid = getRandomPid();
containerService.findOrCreate(session, "/" + pid);
try {
session.commit();
} finally {
session.expire();
}
session = repo.login();
final Container obj2 = containerService.findOrCreate(session, "/" + pid);
final Instant created = roundDate(obj2.getCreatedDate());
final Instant modified = roundDate(obj2.getLastModifiedDate());
assertFalse(modified + " should not be before " + created, modified.isBefore(created));
final Graph graph = obj2.getTriples(subjects, PROPERTIES).collect(toModel()).getGraph();
final Node s = createGraphSubjectNode(obj2);
final ExtendedIterator<Triple> iter = graph.find(s, LAST_MODIFIED_DATE.asNode(), ANY);
assertEquals("Should have one lastModified triple", 1, iter.toList().size());
}
@Test
public void testTouch() throws RepositoryException {
final String pid = getRandomPid();
containerService.findOrCreate(session, "/" + pid);
try {
session.commit();
} finally {
session.expire();
}
session = repo.login();
final Container obj2 = containerService.findOrCreate(session, "/" + pid);
final FedoraResourceImpl impl = new FedoraResourceImpl(getJcrNode(obj2));
final Instant oldMod = impl.getLastModifiedDate();
impl.touch();
assertTrue( oldMod.isBefore(obj2.getLastModifiedDate()) );
}
@Test
public void testRepositoryRootGraph() {
final FedoraResource object = nodeService.find(session, "/");
final Graph graph = object.getTriples(subjects, SERVER_MANAGED).collect(toModel()).getGraph();
final Node s = createGraphSubjectNode(object);
Node p =
createURI(REPOSITORY_NAMESPACE
+ "repositoryJcrRepositoryVendorUrl");
Node o = createLiteral("http://www.modeshape.org");
assertFalse(graph.contains(s, p, o));
p = HAS_NODE_TYPE.asNode();
o = createLiteral(FEDORA_RESOURCE);
assertFalse(graph.contains(s, p, o));
assertTrue(graph.contains(s, type.asNode(), createURI(REPOSITORY_NAMESPACE + "Resource")));
assertTrue(graph.contains(s, type.asNode(), createURI(REPOSITORY_NAMESPACE + "RepositoryRoot")));
assertTrue(graph.contains(s, type.asNode(), createURI(REPOSITORY_NAMESPACE + "Container")));
}
@Test
public void testObjectGraph() {
final String pid = "/" + getRandomPid();
final FedoraResource object =
containerService.findOrCreate(session, pid);
final Graph graph = object.getTriples(subjects, SERVER_MANAGED).collect(toModel()).getGraph();
// jcr property
Node s = createGraphSubjectNode(object);
Node p = HAS_PRIMARY_IDENTIFIER.asNode();
assertFalse(graph.contains(s, p, ANY));
// multivalued property
s = createGraphSubjectNode(object);
p = HAS_MIXIN_TYPE.asNode();
Node o = createLiteral(FEDORA_RESOURCE);
assertFalse(graph.contains(s, p, o));
o = createLiteral(FEDORA_CONTAINER);
assertFalse(graph.contains(s, p, o));
}
@Test
public void testObjectGraphWithCustomProperty() throws RepositoryException {
FedoraResource object =
containerService.findOrCreate(session, "/testObjectGraph");
final javax.jcr.Node node = getJcrNode(object);
node.setProperty("dc:title", "this-is-some-title");
node.setProperty("dc:subject", "this-is-some-subject-stored-as-a-binary", BINARY);
node.setProperty("jcr:data", "jcr-data-should-be-ignored", BINARY);
session.commit();
session.expire();
session = repo.login();
object = containerService.findOrCreate(session, "/testObjectGraph");
final Graph graph = object.getTriples(subjects, PROPERTIES).collect(toModel()).getGraph();
// jcr property
final Node s = createGraphSubjectNode(object);
Node p = title.asNode();
Node o = createLiteral("this-is-some-title");
assertTrue(graph.contains(s, p, o));
p = createURI("http://purl.org/dc/elements/1.1/subject");
o = createLiteral("this-is-some-subject-stored-as-a-binary");
assertTrue(graph.contains(s, p, o));
p = ANY;
o = createLiteral("jcr-data-should-be-ignored");
assertFalse(graph.contains(s, p, o));
}
@Test
public void testRdfTypeInheritance() throws RepositoryException {
final Session jcrSession = getJcrSession(session);
final NodeTypeManager mgr = jcrSession.getWorkspace().getNodeTypeManager();
//create supertype mixin
final NodeTypeTemplate type1 = mgr.createNodeTypeTemplate();
type1.setName("config:aSupertype");
type1.setMixin(true);
final NodeTypeDefinition[] nodeTypes = new NodeTypeDefinition[]{type1};
mgr.registerNodeTypes(nodeTypes, true);
//create a type inheriting above supertype
final NodeTypeTemplate type2 = mgr.createNodeTypeTemplate();
type2.setName("config:testInher");
type2.setMixin(true);
type2.setDeclaredSuperTypeNames(new String[]{"config:aSupertype"});
final NodeTypeDefinition[] nodeTypes2 = new NodeTypeDefinition[]{type2};
mgr.registerNodeTypes(nodeTypes2, true);
//create object with inheriting type
FedoraResource object = containerService.findOrCreate(session, "/testNTTnheritanceObject");
final javax.jcr.Node node = getJcrNode(object);
node.addMixin("config:testInher");
session.commit();
session.expire();
session = repo.login();
object = containerService.findOrCreate(session, "/testNTTnheritanceObject");
//test that supertype has been inherited as rdf:type
final Node s = createGraphSubjectNode(object);
final Node p = type.asNode();
final Node o = createProperty("info:fedoraconfig/aSupertype").asNode();
assertTrue("supertype config:aSupertype not found inherited in fedora:testInher!",
object.getTriples(subjects, PROPERTIES).collect(toModel()).getGraph().contains(s, p, o));
}
@Test
public void testDatastreamGraph() throws RepositoryException, InvalidChecksumException {
final Container parentObject = containerService.findOrCreate(session, "/testDatastreamGraphParent");
final Session jcrSession = getJcrSession(session);
binaryService.findOrCreate(session, "/testDatastreamGraph").setContent(
new ByteArrayInputStream("123456789test123456789".getBytes()),
"text/plain",
null,
null,
null
);
final FedoraResource object = binaryService.findOrCreate(session, "/testDatastreamGraph").getDescription();
getJcrNode(object).setProperty("fedora:isPartOf",
jcrSession.getNode("/testDatastreamGraphParent"));
final Graph graph = object.getTriples(subjects, PROPERTIES).collect(toModel()).getGraph();
// multivalued property
final Node s = createGraphSubjectNode(object);
Node p = HAS_MIXIN_TYPE.asNode();
Node o = createLiteral(FEDORA_RESOURCE);
assertFalse(graph.contains(s, p, o));
o = createLiteral(FEDORA_NON_RDF_SOURCE_DESCRIPTION);
assertFalse(graph.contains(s, p, o));
// structure
//TODO: re-enable number of children reporting, if practical
//assertTrue(datasetGraph.contains(ANY, s, p, o));
// relations
p = createURI(REPOSITORY_NAMESPACE + "isPartOf");
o = createGraphSubjectNode(parentObject);
assertTrue(graph.contains(s, p, o));
}
@Test
public void testUpdatingObjectGraph() {
final Node subject = createURI("info:fedora/testObjectGraphUpdates");
final FedoraResource object =
containerService.findOrCreate(session, "/testObjectGraphUpdates");
object.updateProperties(subjects, "INSERT { " + "<"
+ createGraphSubjectNode(object).getURI() + "> "
+ "<info:fcrepo/zyx> \"a\" } WHERE {} ", object.getTriples(subjects, emptySet()));
// jcr property
final Resource s = createResource(createGraphSubjectNode(object).getURI());
final Property p = createProperty("info:fcrepo/zyx");
Literal o = createPlainLiteral("a");
Model model = object.getTriples(subjects, PROPERTIES).collect(toModel());
assertTrue(model.contains(s, p, o));
object.updateProperties(subjects, "DELETE { " + "<"
+ createGraphSubjectNode(object).getURI() + "> "
+ "<info:fcrepo/zyx> ?o }\n" + "INSERT { " + "<"
+ createGraphSubjectNode(object).getURI() + "> "
+ "<info:fcrepo/zyx> \"b\" } " + "WHERE { " + "<"
+ createGraphSubjectNode(object).getURI() + "> "
+ "<info:fcrepo/zyx> ?o } ", DefaultRdfStream.fromModel(subject, model));
model = object.getTriples(subjects, PROPERTIES).collect(toModel());
assertFalse("found value we should have removed", model.contains(s, p, o));
o = createPlainLiteral("b");
assertTrue("could not find new value", model.contains(s, p, o));
}
/**
* Test technically correct DELETE/WHERE with multiple patterns on the same subject and predicate.
* See also FCREPO-2391
*/
@Test
public void testUpdatesWithMultiplePredicateMatches() throws IOException {
final Node subject = createURI("info:fedora/testUpdatesWithMultiplePredicateMatches");
final FedoraResource object =
containerService.findOrCreate(session, "/testUpdatesWithMultiplePredicateMatches");
final Function<String, String> read = i -> {
try {
return IOUtils.toString(getClass().getResource(i), Charset.forName("UTF8"));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
};
final Function<FedoraResource, Model> modelOf = o -> {
return o.getTriples(subjects, PROPERTIES).collect(toModel());
};
final BiFunction<FedoraResource, String, FedoraResource> update = (o, s) -> {
o.updateProperties(subjects, read.apply(s), DefaultRdfStream.fromModel(subject, modelOf.apply(o)));
return o;
};
update.apply(update.apply(object, "/patch-test/insert-data.txt"), "/patch-test/delete-where.txt");
final Resource s = createResource(createGraphSubjectNode(object).getURI());
final Property pid = createProperty("info:fedora/fedora-system:def/model#PID");
final Literal o = createPlainLiteral("cdc:17256");
final Model model = modelOf.apply(object);
assertTrue(model.contains(s, pid, o));
final Property dcSubject = createProperty("http://purl.org/dc/elements/1.1/subject");
assertFalse(model.contains(s, dcSubject, (RDFNode)null));
}
@Test
public void testGetRootObjectTypes() {
final FedoraResource object = nodeService.find(session, "/");
final List<URI> types = object.getTypes();
assertFalse(types.stream()
.map(x -> x.toString())
.anyMatch(x -> x.startsWith(JCR_NAMESPACE) || x.startsWith(MIX_NAMESPACE) ||
x.startsWith(MODE_NAMESPACE) || x.startsWith(JCR_NT_NAMESPACE)));
}
@Test
public void testGetObjectTypes() {
final FedoraResource object =
containerService.findOrCreate(session, "/testObjectVersionGraph");
final List<URI> types = object.getTypes();
assertTrue(types.contains(create(REPOSITORY_NAMESPACE + "Container")));
assertTrue(types.contains(create(REPOSITORY_NAMESPACE + "Resource")));
assertFalse(types.stream()
.map(x -> x.toString())
.anyMatch(x -> x.startsWith(JCR_NAMESPACE) || x.startsWith(MIX_NAMESPACE) ||
x.startsWith(MODE_NAMESPACE) || x.startsWith(JCR_NT_NAMESPACE)));
}
@Test
public void testGetObjectVersionGraph() throws RepositoryException {
final FedoraResource object =
containerService.findOrCreate(session, "/testObjectVersionGraph");
getJcrNode(object).addMixin("mix:versionable");
session.commit();
// create a version and make sure there are 2 versions (root + created)
versionService.createVersion(session, object.getPath(), "v0.0.1");
session.commit();
final Model graphStore = object.getTriples(subjects, VERSIONS).collect(toModel());
logger.debug(graphStore.toString());
// go querying for the version URI
Resource s = createResource(createGraphSubjectNode(object).getURI());
final ExtendedIterator<Statement> triples = graphStore.listStatements(s,HAS_VERSION, (RDFNode)null);
final List<Statement> list = triples.toList();
assertEquals(1, list.size());
// make sure the URI is derived from the label
s = list.get(0).getObject().asResource();
assertEquals("URI should be derived from label.", s.getURI(), createGraphSubjectNode(object).getURI()
+ "/" + FCR_VERSIONS + "/v0.0.1");
// make sure the label is listed
assertTrue(graphStore.contains(s, HAS_VERSION_LABEL, createPlainLiteral("v0.0.1")));
}
@Test(expected = MalformedRdfException.class)
public void testAddMissingReference() throws MalformedRdfException {
final FedoraResource object =
containerService.findOrCreate(session, "/testRefObject");
object.updateProperties(
subjects,
"PREFIX example: <http://example.org/>\n"
+ "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n"
+ "PREFIX fedora: <" + REPOSITORY_NAMESPACE + ">\n"
+ "INSERT { <> fedora:isPartOf <" + subjects.toDomain("/some-path") + ">}"
+ "WHERE { }", object.getTriples(subjects, emptySet()));
}
@Test(expected = AccessDeniedException.class)
public void testUpdateDenied() throws RepositoryException {
final FedoraResource object =
containerService.findOrCreate(session, "/testRefObject");
try {
object.updateProperties(
subjects,
"INSERT { <> <http://purl.org/dc/elements/1.1/title> \"test-original\". }"
+ " WHERE { }", object.getTriples(subjects, emptySet()));
} catch (final AccessDeniedException e) {
fail("Should fail at update, not create property");
}
final Session jcrSession = getJcrSession(session);
final AccessControlManager acm = jcrSession.getAccessControlManager();
final Privilege[] permissions = new Privilege[] {acm.privilegeFromName(Privilege.JCR_READ)};
final AccessControlList acl = (AccessControlList) acm.getApplicablePolicies("/testRefObject").next();
acl.addAccessControlEntry(SimplePrincipal.newInstance("anonymous"), permissions);
acm.setPolicy("/testRefObject", acl);
session.commit();
object.updateProperties(
subjects,
"INSERT { <> <http://purl.org/dc/elements/1.1/title> \"test-update\". }"
+ " WHERE { }", object.getTriples(subjects, emptySet()));
}
@Test (expected = IllegalArgumentException.class)
public void testInvalidSparqlUpdateValidation() {
final String pid = getRandomPid();
final FedoraResource object =
containerService.findOrCreate(session, pid);
object.updateProperties(
subjects,
"INSERT { <> <http://myurl.org/title/> \"fancy title\" . \n" +
" <> <http://myurl.org/title/> \"fancy title 2\" . } WHERE { }",
object.getTriples(subjects, emptySet()));
}
@Test (expected = InvalidPrefixException.class)
public void testInvalidPrefixSparqlUpdateValidation() {
final String pid = getRandomPid();
final FedoraResource object =
containerService.findOrCreate(session, pid);
object.updateProperties(
subjects,
"PREFIX pcdm: <http://pcdm.org/models#>\n"
+ "INSERT { <> a pcdm:Object}\n"
+ "WHERE { }", object.getTriples(subjects, emptySet()));
object.updateProperties(
subjects,
"PREFIX pcdm: <http://garbage.org/models#>\n"
+ "INSERT { <> a pcdm:Garbage}\n"
+ "WHERE { }", object.getTriples(subjects, emptySet()));
}
@Test
public void testValidSparqlUpdateWithLiteralTrailingSlash() {
final String pid = getRandomPid();
final FedoraResource object = containerService.findOrCreate(session, pid);
object.updateProperties(
subjects,
"INSERT { <> <http://myurl.org/title> \"fancy title/\" . \n" +
" <> <http://myurl.org/title> \"fancy title 2<br/>\" . } WHERE { }",
object.getTriples(subjects, emptySet()));
}
@Test
public void testValidSparqlUpdateValidationAltSyntax() {
final String pid = getRandomPid();
final FedoraResource object = containerService.findOrCreate(session, pid);
object.updateProperties(subjects,
"DELETE WHERE {" +
"<> <http://www.loc.gov/premis/rdf/v1#hasDateCreatedByApplication> ?o0 ." +
"}; INSERT DATA {" +
"<> <http://purl.org/dc/elements/1.1/title> \"Example Managed binary datastream\" ." +
"}",
object.getTriples(subjects, emptySet()));
}
@Test (expected = IllegalArgumentException.class)
public void testInvalidSparqlUpdateValidationAltSyntax() {
final String pid = getRandomPid();
final FedoraResource object = containerService.findOrCreate(session, pid);
object.updateProperties(subjects,
"DELETE WHERE {" +
"<> <http://www.loc.gov/premis/rdf/v1#hasDateCreatedByApplication> ?o0 ." +
"}; INSERT DATA {" +
"<> <http://purl.org/dc/elements/1.1/title/> \"Example Managed binary datastream\" ." +
"}",
object.getTriples(subjects, emptySet()));
}
@Test
public void testValidSparqlUpdateValidation1() {
final String pid = getRandomPid();
final FedoraResource object =
containerService.findOrCreate(session, pid);
object.updateProperties(
subjects,
"INSERT { <> <http://myurl.org/title> \"5\" . } WHERE { }",
object.getTriples(subjects, emptySet()));
}
@Test
public void testValidSparqlUpdateValidation2() {
final String pid = getRandomPid();
final FedoraResource object =
containerService.findOrCreate(session, pid);
object.updateProperties(
subjects,
"PREFIX dsc:<http://myurl.org/title> \n" +
"INSERT { <> dsc:p \"ccc\" } WHERE { }",
object.getTriples(subjects, emptySet()));
}
@Test
public void testUpdatingRdfType() throws RepositoryException {
final FedoraResource object =
containerService.findOrCreate(session, "/testObjectRdfType");
object.updateProperties(subjects, "INSERT { <"
+ createGraphSubjectNode(object).getURI() + "> <" + RDF.type
+ "> <http://some/uri> } WHERE { }", object.getTriples(subjects, emptySet() ));
assertTrue(getJcrNode(object).isNodeType("{http://some/}uri"));
}
@Test
public void testRemoveRdfType() throws RepositoryException {
final FedoraResource object =
containerService.findOrCreate(session, "/testRemoveObjectRdfType");
object.updateProperties(subjects, "INSERT { <"
+ createGraphSubjectNode(object).getURI() + "> <" + RDF.type
+ "> <http://some/uri> } WHERE { }", object.getTriples(subjects, PROPERTIES));
assertTrue(getJcrNode(object).isNodeType("{http://some/}uri"));
object.updateProperties(subjects, "DELETE { <"
+ createGraphSubjectNode(object).getURI() + "> <" + RDF.type
+ "> <http://some/uri> } WHERE { }", object.getTriples(subjects, PROPERTIES));
assertFalse(getJcrNode(object).isNodeType("{http://some/}uri"));
}
@Test
public void testEtagValue() throws RepositoryException {
final FedoraResource object =
containerService.findOrCreate(session, "/testEtagObject");
session.commit();
final String actual = object.getEtagValue();
assertNotNull(actual);
assertNotEquals("", actual);
}
@Test
public void testGetReferences() throws RepositoryException {
final String pid = getRandomPid();
final Session jcrSession = getJcrSession(session);
containerService.findOrCreate(session, pid);
final Container subject = containerService.findOrCreate(session, pid + "/a");
final Container object = containerService.findOrCreate(session, pid + "/b");
final Value value = jcrSession.getValueFactory().createValue(getJcrNode(object));
getJcrNode(subject).setProperty("fedora:isPartOf", new Value[] { value });
session.commit();
final Model model = object.getTriples(subjects, INBOUND_REFERENCES).collect(toModel());
assertTrue(
model.contains(subjects.reverse().convert(subject),
ResourceFactory.createProperty(REPOSITORY_NAMESPACE + "isPartOf"),
subjects.reverse().convert(object))
);
}
@Test
public void testReplaceProperties() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, pid);
final Session jcrSession = getJcrSession(session);
try (final RdfStream triples = object.getTriples(subjects, PROPERTIES)) {
final Model model = triples.collect(toModel());
final Resource resource = model.createResource();
final Resource subject = subjects.reverse().convert(object);
final Property predicate = model.createProperty("info:xyz");
model.add(subject, predicate, resource);
model.add(resource, model.createProperty("http://purl.org/dc/elements/1.1/title"), "xyz");
object.replaceProperties(subjects, model, object.getTriples(subjects, PROPERTIES));
@SuppressWarnings("unchecked")
final Iterator<javax.jcr.Property> properties = getJcrNode(object).getProperties();
final Iterator<javax.jcr.Property> relation = Iterators.filter(properties, uncheck(
(final javax.jcr.Property p) -> p.getName().contains("xyz_ref"))::test);
assertTrue(relation.hasNext());
final javax.jcr.Property next = relation.next();
final Value[] values = next.getValues();
assertEquals(1, values.length);
final javax.jcr.Node skolemizedNode = jcrSession.getNodeByIdentifier(values[0].getString());
assertTrue(skolemizedNode.getPath().contains("/#/"));
assertEquals("xyz" + FIELD_DELIMITER + XSDstring.getURI(),
skolemizedNode.getProperty("dc:title").getValues()[0].getString());
}
}
@Test
public void testReplacePropertiesHashURIs() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, pid);
final Model model = object.getTriples(subjects, PROPERTIES).collect(toModel());
final Resource hashResource = createResource(createGraphSubjectNode(object).getURI() + "#creator");
final Property foafName = model.createProperty("http://xmlns.com/foaf/0.1/name");
final Literal nameValue = model.createLiteral("xyz");
final Resource foafPerson = createResource("http://xmlns.com/foaf/0.1/Person");
model.add(hashResource, foafName, nameValue);
model.add(hashResource, type, foafPerson);
final Resource subject = subjects.reverse().convert(object);
final Property dcCreator = model.createProperty("http://purl.org/dc/elements/1.1/creator");
model.add(subject, dcCreator, hashResource);
object.replaceProperties(subjects, model, object.getTriples(subjects, PROPERTIES));
assertEquals(1, getJcrNode(object).getNode("#").getNodes().getSize());
final Model updatedModel = object.getTriples(subjects, PROPERTIES).collect(toModel());
updatedModel.remove(hashResource, foafName, nameValue);
object.replaceProperties(subjects, updatedModel, object.getTriples(subjects, PROPERTIES));
assertEquals(1, getJcrNode(object).getNode("#").getNodes().getSize());
final Model updatedModel2 = object.getTriples(subjects, PROPERTIES).collect(toModel());
updatedModel2.remove(hashResource, type, foafPerson);
object.replaceProperties(subjects, updatedModel2, object.getTriples(subjects, PROPERTIES));
assertEquals(1, getJcrNode(object).getNode("#").getNodes().getSize());
final Model updatedModel3 = object.getTriples(subjects, PROPERTIES).collect(toModel());
updatedModel3.remove(subject, dcCreator, hashResource);
object.replaceProperties(subjects, updatedModel3, object.getTriples(subjects, PROPERTIES));
assertEquals(0, getJcrNode(object).getNode("#").getNodes().getSize());
}
@Test
public void testDeleteObject() throws RepositoryException {
final String pid = getRandomPid();
final Session jcrSession = getJcrSession(session);
containerService.findOrCreate(session, "/" + pid);
session.commit();
containerService.findOrCreate(session, "/" + pid).delete();
session.commit();
assertTrue(jcrSession.getNode("/" + pid).isNodeType(FEDORA_TOMBSTONE));
}
@Test
public void testDeleteObjectWithInboundReferences() throws RepositoryException {
final String pid = getRandomPid();
final FedoraResource resourceA = containerService.findOrCreate(session, "/" + pid + "/a");
final FedoraResource resourceB = containerService.findOrCreate(session, "/" + pid + "/b");
final Session jcrSession = getJcrSession(session);
final Value value = jcrSession.getValueFactory().createValue(getJcrNode(resourceB));
getJcrNode(resourceA).setProperty("fedora:hasMember", new Value[] { value });
session.commit();
containerService.findOrCreate(session, "/" + pid + "/a").delete();
session.commit();
containerService.findOrCreate(session, "/" + pid + "/b").delete();
session.commit();
assertTrue(jcrSession.getNode("/" + pid + "/b").isNodeType(FEDORA_TOMBSTONE));
}
@Test
public void testDeleteObjectWithInboundReferencesToChildren() throws RepositoryException {
// Set up resources
final String pid = getRandomPid();
final FedoraResource resourceA = containerService.findOrCreate(session, "/" + pid + "/a");
containerService.findOrCreate(session, "/" + pid + "/b");
final FedoraResource resourceX = containerService.findOrCreate(session, "/" + pid + "/b/x");
final Session jcrSession = getJcrSession(session);
// Create a Weak reference
final Value value = jcrSession.getValueFactory().createValue(getJcrNode(resourceX), true);
getJcrNode(resourceA).setProperty("fedora:hasMember", new Value[] { value });
session.commit();
// Verify that relationship exists
final Node s = subjects.reverse().convert(resourceA).asNode();
final Node hasMember = createProperty(REPOSITORY_NAMESPACE, "hasMember").asNode();
final Model rdf = resourceA.getTriples(subjects, PROPERTIES).collect(toModel());
assertTrue(rdf.toString(), rdf.getGraph().contains(s, hasMember, ANY));
// Delete parent of reference target
containerService.findOrCreate(session, "/" + pid + "/b").delete();
session.commit();
// Verify that relationship does NOT exist, and that the resource successfully loads.
containerService.find(session, "/" + pid + "/a");
final Model rdfAfter = resourceA.getTriples(subjects, PROPERTIES).collect(toModel());
assertFalse(rdfAfter.getGraph().contains(s, hasMember, ANY));
}
@Test
public void testGetContainer() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = containerService.findOrCreate(session, "/" + pid + "/a");
assertEquals(container, resource.getContainer());
}
@Test
public void testGetChildren() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = containerService.findOrCreate(session, "/" + pid + "/a");
assertEquals(resource, container.getChildren().findFirst().get());
}
@Test
public void testGetChildrenRecursively() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
containerService.findOrCreate(session, "/" + pid + "/a");
containerService.findOrCreate(session, "/" + pid + "/a/b");
containerService.findOrCreate(session, "/" + pid + "/a/b/c");
containerService.findOrCreate(session, "/" + pid + "/a/c/d");
containerService.findOrCreate(session, "/" + pid + "/a/c/e");
assertEquals(5, container.getChildren(true).count());
assertEquals(1, container.getChildren(false).count());
}
@Test
public void testGetChildrenWithBinary() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = binaryService.findOrCreate(session, "/" + pid + "/a");
assertEquals(resource, container.getChildren().findFirst().get());
}
@Test
public void testGetContainerForBinary() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = binaryService.findOrCreate(session, "/" + pid + "/a");
assertEquals(container, resource.getContainer());
}
@Test
public void testGetContainerWithHierarchy() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = containerService.findOrCreate(session, "/" + pid + "/a/b/c/d");
assertEquals(container, resource.getContainer());
}
@Test
public void testGetChildrenWithHierarchy() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = containerService.findOrCreate(session, "/" + pid + "/a/b/c/d");
assertEquals(resource, container.getChildren().findFirst().get());
}
@Test
public void testGetChildrenTombstonesAreHidden() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
final FedoraResource resource = containerService.findOrCreate(session, "/" + pid + "/a");
resource.delete();
assertFalse(container.getChildren().findFirst().isPresent());
}
@Test
public void testGetChildrenHidesHashUris() {
final String pid = getRandomPid();
final Container container = containerService.findOrCreate(session, "/" + pid);
containerService.findOrCreate(session, "/" + pid + "/#/a");
assertFalse(container.getChildren().findFirst().isPresent());
}
@Test
public void testGetUnfrozenResource() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
object.enableVersioning();
session.commit();
addVersionLabel("some-label", object);
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath())
.getVersionByLabel("some-label");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode());
assertEquals(object, frozenResource.getUnfrozenResource());
}
@Test
public void testGetVersionedAncestor() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
final Session jcrSession = getJcrSession(session);
object.enableVersioning();
session.commit();
containerService.findOrCreate(session, "/" + pid + "/a/b/c");
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("some-label", object);
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager()
.getVersionHistory(node.getPath()).getVersionByLabel("some-label");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode().getNode("a"));
assertEquals(object, frozenResource.getVersionedAncestor().getUnfrozenResource());
}
@Test
public void testVersionedChild() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
final Session jcrSession = getJcrSession(session);
object.enableVersioning();
session.commit();
final Container child = containerService.findOrCreate(session, "/" + pid + "/child");
child.enableVersioning();
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("object-v0", object);
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(child.getPath());
addVersionLabel("child-v0", child);
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager()
.getVersionHistory(node.getPath()).getVersionByLabel("object-v0");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode().getNode("child"));
assertEquals(child, frozenResource.getVersionedAncestor().getUnfrozenResource());
}
@Test
public void testVersionedChildDeleted() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
final Session jcrSession = getJcrSession(session);
object.enableVersioning();
session.commit();
final Container child = containerService.findOrCreate(session, "/" + pid + "/child");
getJcrNode(child).setProperty("dc:title", "this-is-some-title");
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("object-v0", object);
session.commit();
// Delete the child!
child.delete();
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath())
.getVersionByLabel("object-v0");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode());
// Parent version is correct
assertEquals(object, frozenResource.getVersionedAncestor().getUnfrozenResource());
// Versioned child still exists
final FedoraResourceImpl frozenChild = new FedoraResourceImpl(version.getFrozenNode().getNode("child"));
final javax.jcr.Property property = frozenChild.getNode().getProperty("dc:title");
assertNotNull(property);
assertEquals("this-is-some-title", property.getString());
}
@Test
public void testVersionedChildBinaryDeleted() throws RepositoryException, InvalidChecksumException, IOException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
object.enableVersioning();
session.commit();
final FedoraBinary child = binaryService.findOrCreate(session, "/" + pid + "/child");
final String content = "123456789test123456789";
final Session jcrSession = getJcrSession(session);
child.setContent(new ByteArrayInputStream(content.getBytes()), "text/plain", null, null, null);
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("object-v0", object);
session.commit();
// Delete the child!
child.delete();
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath())
.getVersionByLabel("object-v0");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode());
// Parent version is correct
assertEquals(object, frozenResource.getVersionedAncestor().getUnfrozenResource());
// Versioned child still exists
final NonRdfSourceDescription frozenChild =
new NonRdfSourceDescriptionImpl(version.getFrozenNode().getNode("child"));
try (final InputStream contentStream = ((FedoraBinary) frozenChild.getDescribedResource()).getContent()) {
assertNotNull(contentStream);
assertEquals(content, IOUtils.toString(contentStream, UTF_8));
}
}
@Test
public void testDeleteLinkedVersionedResources() throws RepositoryException {
final Container object1 = containerService.findOrCreate(session, "/" + getRandomPid());
final Container object2 = containerService.findOrCreate(session, "/" + getRandomPid());
// Create a link between objects 1 and 2
object2.updateProperties(subjects, "PREFIX example: <http://example.org/>\n" +
"INSERT { <> <example:link> " + "<" + createGraphSubjectNode(object1).getURI() + ">" +
" } WHERE {} ",
object2.getTriples(subjects, emptySet()));
// Create version of object2
versionService.createVersion(session, object2.getPath(), "obj2-v0");
// Verify that the objects exist
assertTrue("object1 should exist!", exists(object1));
assertTrue("object2 should exist!", exists(object2));
// This is the test: verify successful deletion of the objects
object2.delete();
session.commit();
object1.delete();
session.commit();
// Double-verify that the objects are gone
assertFalse("/object2 should NOT exist!", exists(object2));
assertFalse("/object1 should NOT exist!", exists(object1));
}
private boolean exists(final Container resource) {
try {
resource.getPath();
return true;
} catch (RepositoryRuntimeException e) {
return false;
}
}
@Test (expected = RepositoryRuntimeException.class)
public void testNullBaseVersion() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
session.commit();
object.getBaseVersion();
}
@Test
public void testGetNodeVersion() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
final Session jcrSession = getJcrSession(session);
object.enableVersioning();
session.commit();
containerService.findOrCreate(session, "/" + pid + "/a/b/c");
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("some-label", object);
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath())
.getVersionByLabel("some-label");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode());
assertNull(frozenResource.getVersion("some-label"));
}
@Test
public void testGetNullNodeVersion() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
final Session jcrSession = getJcrSession(session);
object.enableVersioning();
session.commit();
containerService.findOrCreate(session, "/" + pid + "/a/b/c");
session.commit();
jcrSession.getWorkspace().getVersionManager().checkpoint(object.getPath());
addVersionLabel("some-label", object);
session.commit();
final javax.jcr.Node node = getJcrNode(object);
final Version version = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath())
.getVersionByLabel("some-label");
final FedoraResource frozenResource = new FedoraResourceImpl(version.getFrozenNode().getNode("a"));
assertNull(frozenResource.getVersion("some-label"));
}
@Test
public void testDisableVersioning() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
object.enableVersioning();
session.commit();
assertTrue(object.isVersioned());
object.disableVersioning();
assertFalse(object.isVersioned());
}
@Test (expected = RepositoryRuntimeException.class)
public void testDisableVersioningException() {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
object.disableVersioning();
}
@Test
public void testHash() throws RepositoryException {
final String pid = getRandomPid();
final Container object = containerService.findOrCreate(session, "/" + pid);
object.enableVersioning();
session.commit();
final FedoraResourceImpl frozenResource = new FedoraResourceImpl(getJcrNode(object));
assertFalse(frozenResource.hashCode() == 0);
}
@Test
public void testDeletePartOfMultiValueProperty() throws RepositoryException {
final String pid = getRandomPid();
final String relation = "config:fakeRel";
containerService.findOrCreate(session, pid);
final Container subject = containerService.findOrCreate(session, pid + "/a");
final Container referent1 = containerService.findOrCreate(session, pid + "/b");
final Container referent2 = containerService.findOrCreate(session, pid + "/c");
final Session jcrSession = getJcrSession(session);
final Value[] values = new Value[2];
values[0] = jcrSession.getValueFactory().createValue(getJcrNode(referent1));
values[1] = jcrSession.getValueFactory().createValue(getJcrNode(referent2));
getJcrNode(subject).setProperty(relation, values);
session.commit();
final Model model1 = referent1.getTriples(subjects, INBOUND_REFERENCES).collect(toModel());
assertTrue(model1.contains(subjects.reverse().convert(subject),
createProperty("info:fedoraconfig/fakeRel"),
createResource("info:fedora/" + pid + "/b")));
assertTrue(model1.contains(subjects.reverse().convert(subject),
createProperty("info:fedoraconfig/fakeRel"),
createResource("info:fedora/" + pid + "/c")));
// This is the test! Ensure that only the delete resource is removed from the "subject" container.
referent2.delete();
final Model model2 = referent1.getTriples(subjects, INBOUND_REFERENCES).collect(toModel());
assertTrue(model2.contains(subjects.reverse().convert(subject),
createProperty("info:fedoraconfig/fakeRel"),
createResource("info:fedora/" + pid + "/b")));
assertFalse(model2.contains(subjects.reverse().convert(subject),
createProperty("info:fedoraconfig/fakeRel"),
createResource("info:fedora/" + pid + "/c")));
}
private void addVersionLabel(final String label, final FedoraResource r) throws RepositoryException {
final Session jcrSession = getJcrSession(session);
addVersionLabel(label, jcrSession.getWorkspace().getVersionManager().getBaseVersion(r.getPath()));
}
private static void addVersionLabel(final String label, final Version v) throws RepositoryException {
v.getContainingHistory().addVersionLabel(v.getName(), label, false);
}
private static Instant roundDate(final Instant date) {
return date.minusMillis(date.toEpochMilli() % 1000);
}
}