/*
* 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.kernel.modeshape;
import static org.apache.jena.graph.NodeFactory.createURI;
import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
import static java.util.Calendar.JULY;
import static org.apache.commons.codec.digest.DigestUtils.sha1Hex;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_PAIRTREE;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_TOMBSTONE;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_LASTMODIFIED;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel;
import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.FROZEN_NODE;
import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.JCR_CREATED;
import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.JCR_LASTMODIFIED;
import static org.fcrepo.kernel.modeshape.RdfJcrLexicon.JCR_NAMESPACE;
import static org.fcrepo.kernel.modeshape.testutilities.TestNodeIterator.nodeIterator;
import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getJcrNode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
import java.net.URI;
import java.util.Calendar;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import java.time.Instant;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeType;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import javax.jcr.version.VersionManager;
import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.api.exception.MalformedRdfException;
import org.fcrepo.kernel.api.models.FedoraResource;
import org.fcrepo.kernel.api.identifiers.IdentifierConverter;
import org.fcrepo.kernel.api.RdfStream;
import org.fcrepo.kernel.modeshape.rdf.JcrRdfTools;
import org.fcrepo.kernel.modeshape.rdf.impl.DefaultIdentifierTranslator;
import org.fcrepo.kernel.modeshape.testutilities.TestPropertyIterator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
/**
* <p>FedoraResourceImplTest class.</p>
*
* @author ajs6f
*/
@RunWith(MockitoJUnitRunner.class)
public class FedoraResourceImplTest {
private FedoraResource testObj;
@Mock
private Node mockNode, mockRoot, mockChild, mockContainer;
@Mock
private NodeType mockPrimaryNodeType, mockMixinNodeType, mockPrimarySuperNodeType, mockMixinSuperNodeType;
@Mock
private Session mockSession;
@Mock
private Property mockProp, mockContainerProperty;
@Mock
private NamespaceRegistry mockNamespaceRegistry;
@Mock
private Workspace mockWorkspace;
@Mock
private JcrRdfTools mockJcrRdfTools;
@Mock
private IdentifierConverter<Resource, FedoraResource> mockSubjects;
@Before
public void setUp() throws RepositoryException {
when(mockNode.getSession()).thenReturn(mockSession);
when(mockNode.getPath()).thenReturn("/some/path");
final NodeType mockNodeType = mock(NodeType.class);
when(mockNodeType.getName()).thenReturn("nt:folder");
when(mockNode.getPrimaryNodeType()).thenReturn(mockNodeType);
testObj = new FedoraResourceImpl(mockNode);
assertEquals(mockNode, getJcrNode(testObj));
mockSubjects = new DefaultIdentifierTranslator(mockSession);
}
@Test
public void testGetPath() throws RepositoryException {
testObj.getPath();
verify(mockNode).getPath();
}
@Test(expected = RepositoryRuntimeException.class)
public void testGetPathException() throws RepositoryException {
doThrow(RepositoryException.class).when(mockNode).getPath();
testObj.getPath();
}
@Test
public void testGetCreatedDate() throws RepositoryException {
final Calendar someDate = Calendar.getInstance();
when(mockProp.getDate()).thenReturn(someDate);
when(mockNode.hasProperty(JCR_CREATED)).thenReturn(true);
when(mockNode.getProperty(JCR_CREATED)).thenReturn(mockProp);
assertEquals(someDate.getTimeInMillis(), testObj.getCreatedDate().toEpochMilli());
}
@Test
public void testGetTypes() throws RepositoryException {
final String mockNodeTypePrefix = "jcr";
final String mockPrimaryNodeTypeName = "somePrimaryType";
final String mockMixinNodeTypeName = "someMixinType";
final String mockPrimarySuperNodeTypeName = "somePrimarySuperType";
final String mockMixinSuperNodeTypeName = "someMixinSuperType";
final Workspace mockWorkspace = mock(Workspace.class);
final NamespaceRegistry mockNamespaceRegistry = mock(NamespaceRegistry.class);
when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
when(mockWorkspace.getNamespaceRegistry()).thenReturn(mockNamespaceRegistry);
when(mockNamespaceRegistry.getURI("jcr")).thenReturn(JCR_NAMESPACE);
when(mockNode.getPrimaryNodeType()).thenReturn(mockPrimaryNodeType);
when(mockPrimaryNodeType.getName()).thenReturn(
mockNodeTypePrefix + ":" + mockPrimaryNodeTypeName);
when(mockNode.getMixinNodeTypes()).thenReturn(
new NodeType[]{mockMixinNodeType});
when(mockMixinNodeType.getName()).thenReturn(
mockNodeTypePrefix + ":" + mockMixinNodeTypeName);
when(mockPrimaryNodeType.getSupertypes()).thenReturn(
new NodeType[]{mockPrimarySuperNodeType});
when(mockPrimarySuperNodeType.getName()).thenReturn(
mockNodeTypePrefix + ":" + mockPrimarySuperNodeTypeName);
when(mockMixinNodeType.getSupertypes()).thenReturn(
new NodeType[] {mockMixinSuperNodeType, mockMixinSuperNodeType});
when(mockMixinSuperNodeType.getName()).thenReturn(
mockNodeTypePrefix + ":" + mockMixinSuperNodeTypeName);
final List<URI> types = testObj.getTypes();
assertFalse(types.contains(URI.create(REPOSITORY_NAMESPACE + mockPrimaryNodeTypeName)));
assertFalse(types.contains(URI.create(REPOSITORY_NAMESPACE + mockMixinNodeTypeName)));
assertFalse(types.contains(URI.create(REPOSITORY_NAMESPACE + mockPrimarySuperNodeTypeName)));
assertFalse(types.contains(URI.create(REPOSITORY_NAMESPACE + mockMixinSuperNodeTypeName)));
assertEquals(0, types.size());
}
@Test
public void testGetLastModifiedDateDefault() throws RepositoryException {
// test missing JCR_LASTMODIFIED/FEDORA_LASTMODIFIED
final Calendar someDate = Calendar.getInstance();
someDate.add(Calendar.DATE, -1);
try {
when(mockNode.hasProperty(FEDORA_LASTMODIFIED)).thenReturn(false);
when(mockNode.hasProperty(JCR_LASTMODIFIED)).thenReturn(false);
when(mockProp.getDate()).thenReturn(someDate);
when(mockNode.hasProperty(JCR_CREATED)).thenReturn(true);
when(mockNode.getProperty(JCR_CREATED)).thenReturn(mockProp);
when(mockNode.getSession()).thenReturn(mockSession);
} catch (final RepositoryException e) {
e.printStackTrace();
}
final Instant actual = testObj.getLastModifiedDate();
assertEquals(someDate.getTimeInMillis(), actual.toEpochMilli());
// this is a read operation, it must not persist the session
verify(mockSession, never()).save();
}
@Test
public void testGetLastModifiedDate() {
// test existing FEDORA_LASTMODIFIED
final Calendar someDate = Calendar.getInstance();
someDate.add(Calendar.DATE, -1);
try {
when(mockProp.getDate()).thenReturn(someDate);
when(mockNode.hasProperty(JCR_CREATED)).thenReturn(true);
when(mockNode.getProperty(JCR_CREATED)).thenReturn(mockProp);
when(mockNode.getSession()).thenReturn(mockSession);
} catch (final RepositoryException e) {
e.printStackTrace();
}
final Property mockMod = mock(Property.class);
final Calendar modDate = Calendar.getInstance();
try {
when(mockNode.hasProperty(FEDORA_LASTMODIFIED)).thenReturn(true);
when(mockNode.getProperty(FEDORA_LASTMODIFIED)).thenReturn(mockMod);
when(mockMod.getDate()).thenReturn(modDate);
} catch (final RepositoryException e) {
System.err.println("What are we doing in the second test?");
e.printStackTrace();
}
final Instant actual = testObj.getLastModifiedDate();
assertEquals(modDate.getTimeInMillis(), actual.toEpochMilli());
}
@Test
public void testTouch() throws RepositoryException {
// test existing JCR_LASTMODIFIED
final Calendar someDate = Calendar.getInstance();
someDate.add(Calendar.DATE, -1);
when(mockProp.getDate()).thenReturn(someDate);
when(mockNode.hasProperty(JCR_CREATED)).thenReturn(true);
when(mockNode.getProperty(JCR_CREATED)).thenReturn(mockProp);
when(mockNode.getSession()).thenReturn(mockSession);
final Property mockMod = mock(Property.class);
final Calendar modDate = Calendar.getInstance();
when(mockNode.hasProperty(JCR_LASTMODIFIED)).thenReturn(true);
when(mockNode.getProperty(JCR_LASTMODIFIED)).thenReturn(mockMod);
when(mockMod.getDate()).thenReturn(modDate);
final Instant actual = testObj.getLastModifiedDate();
assertEquals(modDate.getTimeInMillis(), actual.toEpochMilli());
}
@Test
public void testGetBaseVersion() throws RepositoryException {
final Version mockVersion = mock(Version.class);
when(mockVersion.getName()).thenReturn("uuid");
final Workspace mockWorkspace = mock(Workspace.class);
when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
final VersionManager mockVersionManager = mock(VersionManager.class);
when(mockWorkspace.getVersionManager()).thenReturn(mockVersionManager);
when(mockVersionManager.getBaseVersion(anyString())).thenReturn(
mockVersion);
testObj.getBaseVersion();
verify(mockVersionManager).getBaseVersion(anyString());
}
@Test
public void testGetVersionLabels() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(true);
final VersionHistory mockVersionHistory = mock(VersionHistory.class);
final Version mockVersion = mock(Version.class);
when(mockVersion.getName()).thenReturn("uuid");
final Workspace mockWorkspace = mock(Workspace.class);
when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
final VersionManager mockVersionManager = mock(VersionManager.class);
when(mockWorkspace.getVersionManager()).thenReturn(mockVersionManager);
final VersionIterator mockVersionIterator = mock(VersionIterator.class);
when(mockVersionHistory.getAllVersions()).thenReturn(mockVersionIterator);
when(mockVersionIterator.hasNext()).thenReturn(false);
when(mockVersionManager.getVersionHistory(anyString())).thenReturn(
mockVersionHistory);
testObj.getVersions();
verify(mockVersionManager).getVersionHistory(anyString());
}
@Test
public void testIsNew() {
when(mockNode.isNew()).thenReturn(true);
assertTrue("resource state should be the same as the node state",
testObj.isNew());
}
@Test
public void testIsNotNew() {
when(mockNode.isNew()).thenReturn(false);
assertFalse("resource state should be the same as the node state",
testObj.isNew());
}
@Test(expected = MalformedRdfException.class)
public void testReplacePropertiesDataset() throws RepositoryException {
final DefaultIdentifierTranslator defaultGraphSubjects = new DefaultIdentifierTranslator(mockSession);
when(mockNode.getPath()).thenReturn("/xyz");
when(mockSession.getNode("/xyz")).thenReturn(mockNode);
final Model propertiesModel = createDefaultModel();
propertiesModel.add(propertiesModel.createResource("a"),
propertiesModel.createProperty("b"),
"c");
try (final RdfStream propertiesStream = fromModel(createURI("info:fedora/xyz"), propertiesModel)) {
final Model replacementModel = createDefaultModel();
replacementModel.add(replacementModel.createResource("a"), replacementModel.createProperty("b"), "n");
testObj.replaceProperties(defaultGraphSubjects, replacementModel, propertiesStream);
}
}
@Test(expected = IllegalArgumentException.class)
public void testUpdateInvalidObjectUrlInSparql() throws RepositoryException {
testUpdateInvalidSPARQL(
"INSERT DATA {<> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://pcdm.org/models##file> .}");
}
@Test(expected = IllegalArgumentException.class)
public void testUpdateInvalidPredicateUrlInSparql() throws RepositoryException {
testUpdateInvalidSPARQL("INSERT DATA {<> <http://www.w3.org/1999/02/> <http://pcdm.org/models#file> .}");
}
private void testUpdateInvalidSPARQL(final String sparqlUpdateStatement) throws RepositoryException {
final DefaultIdentifierTranslator defaultGraphSubjects = new DefaultIdentifierTranslator(mockSession);
when(mockNode.getPath()).thenReturn("/xyz");
when(mockSession.getNode("/xyz")).thenReturn(mockNode);
when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
when(mockWorkspace.getNamespaceRegistry()).thenReturn(mockNamespaceRegistry);
final Model propertiesModel = createDefaultModel();
try (final RdfStream propertiesStream = fromModel(createURI("info:fedora/xyz"), propertiesModel)) {
testObj.updateProperties(defaultGraphSubjects, sparqlUpdateStatement, propertiesStream);
}
}
@Test
public void shouldGetEtagForAnObject() throws RepositoryException {
final Property mockMod = mock(Property.class);
final Calendar modDate = Calendar.getInstance();
modDate.set(2013, JULY, 30, 0, 0, 0);
when(mockNode.getPath()).thenReturn("some-path");
when(mockNode.hasProperty(FEDORA_LASTMODIFIED)).thenReturn(true);
when(mockNode.getProperty(FEDORA_LASTMODIFIED)).thenReturn(mockMod);
when(mockMod.getDate()).thenReturn(modDate);
assertEquals(sha1Hex("some-path"
+ testObj.getLastModifiedDate().toEpochMilli()), testObj
.getEtagValue());
}
@Test
public void testGetContainer() throws RepositoryException {
when(mockNode.getParent()).thenReturn(mockContainer);
when(mockNode.getDepth()).thenReturn(1);
final FedoraResource actual = testObj.getContainer();
assertEquals(new FedoraResourceImpl(mockContainer), actual);
}
@Test
public void testGetContainerForNestedResource() throws RepositoryException {
when(mockNode.getParent()).thenReturn(mockChild);
when(mockNode.getDepth()).thenReturn(3);
when(mockChild.getParent()).thenReturn(mockContainer);
when(mockChild.getDepth()).thenReturn(2);
when(mockChild.isNodeType(FEDORA_PAIRTREE)).thenReturn(true);
when(mockContainer.getDepth()).thenReturn(1);
final FedoraResource actual = testObj.getContainer();
assertEquals(new FedoraResourceImpl(mockContainer), actual);
}
@Test
public void testGetChild() throws RepositoryException {
when(mockNode.getNode("xyz")).thenReturn(mockChild);
final FedoraResource actual = testObj.getChild("xyz");
assertEquals(new FedoraResourceImpl(mockChild), actual);
}
@Test
public void testGetChildrenWithEmptyChildren() throws RepositoryException {
when(mockNode.getNodes()).thenReturn(nodeIterator());
final Stream<FedoraResource> children = testObj.getChildren();
assertFalse("Expected an empty stream", children.findFirst().isPresent());
}
@Test
public void testGetChildrenWithChildren() throws RepositoryException {
when(mockNode.getNodes()).thenReturn(nodeIterator(mockChild));
when(mockChild.getName()).thenReturn("x");
final Optional<FedoraResource> child = testObj.getChildren().findFirst();
assertTrue("Expected a stream with values", child.isPresent());
assertEquals("Expected to find the child", mockChild, getJcrNode(child.get()));
}
@Test
public void testGetChildrenExcludesModeSystem() throws RepositoryException {
when(mockNode.getNodes()).thenReturn(nodeIterator(mockChild));
when(mockChild.isNodeType("mode:system")).thenReturn(true);
when(mockChild.getName()).thenReturn("x");
final Stream<FedoraResource> children = testObj.getChildren();
assertFalse("Expected an empty stream", children.findFirst().isPresent());
}
@Test
public void testGetChildrenExcludesTombstones() throws RepositoryException {
when(mockNode.getNodes()).thenReturn(nodeIterator(mockChild));
when(mockChild.isNodeType(FEDORA_TOMBSTONE)).thenReturn(true);
when(mockChild.getName()).thenReturn("x");
final Stream<FedoraResource> children = testObj.getChildren();
assertFalse("Expected an empty stream", children.findFirst().isPresent());
}
@Test
public void testGetChildrenExcludesJcrContent() throws RepositoryException {
when(mockNode.getNodes()).thenReturn(nodeIterator(mockChild));
when(mockChild.getName()).thenReturn(JCR_CONTENT);
final Stream<FedoraResource> children = testObj.getChildren();
assertFalse("Expected an empty stream", children.findFirst().isPresent());
}
@Test
public void testHasProperty() throws RepositoryException {
when(mockNode.hasProperty("xyz")).thenReturn(true);
final boolean actual = testObj.hasProperty("xyz");
assertTrue("Expected same value as Node#hasProperty", actual);
}
@Test
public void testGetProperty() throws RepositoryException {
when(mockNode.getProperty("xyz")).thenReturn(mockProp);
final Property actual = getJcrNode(testObj).getProperty("xyz");
assertEquals(mockProp, actual);
}
@Test
public void testEquals() {
assertEquals(new FedoraResourceImpl(mockNode), new FedoraResourceImpl(mockNode));
assertNotEquals(new FedoraResourceImpl(mockNode), new FedoraResourceImpl(mockRoot));
}
@Test
public void testDelete() throws RepositoryException {
when(mockNode.getReferences()).thenReturn(new TestPropertyIterator());
when(mockNode.getWeakReferences()).thenReturn(new TestPropertyIterator());
testObj.delete();
verify(mockNode).remove();
}
@Test
public void testDeleteLeavesATombstone() throws RepositoryException {
when(mockNode.getReferences()).thenReturn(new TestPropertyIterator());
when(mockNode.getWeakReferences()).thenReturn(new TestPropertyIterator());
when(mockNode.getName()).thenReturn("a");
when(mockNode.getParent()).thenReturn(mockContainer);
when(mockNode.getDepth()).thenReturn(2);
when(mockContainer.getNode("a")).thenThrow(new PathNotFoundException());
when(mockContainer.getPath()).thenReturn("b");
when(mockContainer.getSession()).thenReturn(mockSession);
when(mockSession.nodeExists(anyString())).thenReturn(false);
when(mockSession.getNode("b")).thenReturn(mockContainer);
testObj.delete();
verify(mockNode).remove();
verify(mockContainer).addNode("a", FEDORA_TOMBSTONE);
}
@Test
public void testGetUnfrozenResourceForFrozenNode() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(true);
when(mockNode.hasProperty("jcr:frozenUuid")).thenReturn(true);
when(mockNode.getProperty("jcr:frozenUuid")).thenReturn(mockProp);
when(mockProp.getString()).thenReturn("frozen-id");
when(mockSession.getNodeByIdentifier("frozen-id")).thenReturn(mockChild);
final FedoraResource actual = testObj.getUnfrozenResource();
assertEquals(mockChild, getJcrNode(actual));
}
@Test
public void testGetUnfrozenResourceForResource() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(false);
final FedoraResource actual = testObj.getUnfrozenResource();
assertEquals(testObj, actual);
}
@Test
public void testGetVersionedAncestorForResource() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(false);
final FedoraResource actual = testObj.getVersionedAncestor();
assertNull(actual);
}
@Test
public void testGetVersionedAncestorForVersionedResource() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(true);
when(mockNode.hasProperty("jcr:frozenUuid")).thenReturn(true);
when(mockNode.getProperty("jcr:frozenUuid")).thenReturn(mockProp);
when(mockNode.isNodeType("mix:versionable")).thenReturn(true);
when(mockProp.getString()).thenReturn("frozen-id");
when(mockSession.getNodeByIdentifier("frozen-id")).thenReturn(mockNode);
final FedoraResource actual = testObj.getVersionedAncestor();
assertEquals(mockNode, getJcrNode(actual));
}
@Test
public void testGetVersionedAncestorForVersionedResourceWithinTree() throws RepositoryException {
when(mockNode.isNodeType(FROZEN_NODE)).thenReturn(true);
when(mockNode.getDepth()).thenReturn(2);
when(mockNode.hasProperty("jcr:frozenUuid")).thenReturn(true);
when(mockNode.getProperty("jcr:frozenUuid")).thenReturn(mockProp);
when(mockNode.isNodeType("mix:versionable")).thenReturn(false);
when(mockProp.getString()).thenReturn("frozen-id");
when(mockSession.getNodeByIdentifier("frozen-id")).thenReturn(mockNode);
when(mockNode.getParent()).thenReturn(mockContainer);
when(mockContainer.getSession()).thenReturn(mockSession);
when(mockContainer.getDepth()).thenReturn(1);
when(mockContainer.isNodeType(FROZEN_NODE)).thenReturn(true);
when(mockContainer.hasProperty("jcr:frozenUuid")).thenReturn(true);
when(mockContainer.getProperty("jcr:frozenUuid")).thenReturn(mockContainerProperty);
when(mockContainerProperty.getString()).thenReturn("frozen-id-container");
when(mockSession.getNodeByIdentifier("frozen-id-container")).thenReturn(mockContainer);
when(mockContainer.isNodeType("mix:versionable")).thenReturn(true);
final FedoraResource actual = testObj.getVersionedAncestor();
assertEquals(mockContainer, getJcrNode(actual));
}
}