/* * ModeShape (http://www.modeshape.org) * * 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.modeshape.jcr; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsSame.sameInstance; import static org.junit.Assert.assertThat; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.jcr.AccessDeniedException; import javax.jcr.Binary; import javax.jcr.Credentials; import javax.jcr.ImportUUIDBehavior; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.LoginException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.ValueFactory; import javax.jcr.Workspace; import javax.jcr.lock.LockException; import javax.jcr.lock.LockManager; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NodeDefinitionTemplate; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.nodetype.PropertyDefinitionTemplate; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionIterator; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.test.AbstractJCRTest; import org.apache.jackrabbit.test.api.ShareableNodeTest; import org.modeshape.common.FixFor; import org.modeshape.jcr.api.JcrTools; /** * Additional ModeShape tests that check for JCR compliance. */ public class ModeShapeTckTest extends AbstractJCRTest { private static final String SYSTEM_ROOT_PATH = "/" + JcrLexicon.SYSTEM.getString(); private static final String TEST_ROOT_PATH = "/testroot"; Session session; private Map<String, Node> testAreasByWorkspace = new HashMap<String, Node>(); protected boolean print = false; public ModeShapeTckTest( String testName ) { this.setName(testName); this.isReadOnly = true; this.print = false; } @Override protected void tearDown() throws Exception { superuser.logout(); Session superuserSession = getHelper().getSuperuserSession(); removeTestNodesForSession(superuserSession, false); removeTestNodesForSession(getHelper().getSuperuserSession("otherWorkspace"), true); if (session != null) { session.logout(); session = null; } super.tearDown(); Credentials creds = getHelper().getSuperuserCredentials(); Repository repository = getHelper().getRepository(); String superUserWs = superuserSession.getWorkspace().getName(); testAreasByWorkspace.entrySet() .stream() .filter(entry -> !entry.getKey().equals(superUserWs)) .forEach(entry -> { Session session = null; try { session = repository.login(creds, entry.getKey()); Node node = session.getNode(entry.getValue().getPath()); node.remove(); session.save(); } catch (Exception e) { // ignore ... } finally { if (session != null) { session.logout(); } } }); } @SuppressWarnings("unchecked") private void removeTestNodesForSession(Session superUserSession, boolean ignoreTestRootPath) throws RepositoryException { Node root = superUserSession.getRootNode(); root.getNodes().forEachRemaining((object) -> { Node node = (Node) object; try { String path = node.getPath(); if (!path.startsWith(SYSTEM_ROOT_PATH) && (ignoreTestRootPath || !path.startsWith(TEST_ROOT_PATH))) { node.remove(); } } catch (RepositoryException e) { throw new RuntimeException(e); } }); superUserSession.save(); } protected Node getTestRoot( Session session ) throws Exception { // Create a temporary area for the test ... String workspaceName = session.getWorkspace().getName(); Node node = testAreasByWorkspace.get(workspaceName); if (node == null) { node = session.getRootNode().addNode("tmp"); testAreasByWorkspace.put(workspaceName, node); } else { // There is a node, but check which session it came from ... if (node.getSession() != session) { // Look it up in the supplied session ... node = session.getRootNode().getNode("tmp"); } } return node; } private void testRead( Session session ) throws Exception { Node rootNode = session.getRootNode(); for (NodeIterator iter = rootNode.getNodes(); iter.hasNext();) { iter.nextNode(); } } private void testAddNode( Session session ) throws Exception { session.refresh(false); Node root = getTestRoot(session); root.addNode(nodeName1, testNodeType); session.save(); } private void testRemoveNode( Session session ) throws Exception { session.refresh(false); Node root = getTestRoot(session); Node node = root.getNode(nodeName1); node.remove(); session.save(); } private void testSetProperty( Session session ) throws Exception { session.refresh(false); Node root = getTestRoot(session); root.setProperty(this.propertyName1, "test value"); session.save(); } private void testRemoveProperty( Session session ) throws Exception { Session localAdmin = getHelper().getRepository().login(getHelper().getSuperuserCredentials(), session.getWorkspace().getName()); assertEquals(session.getWorkspace().getName(), superuser.getWorkspace().getName()); Node superRoot = localAdmin.getRootNode(); Node superNode; try { superNode = superRoot.getNode(this.nodeName1); } catch (PathNotFoundException pnfe) { superNode = superRoot.addNode(nodeName1, testNodeType); } superNode.setProperty(this.propertyName1, "test value"); localAdmin.save(); localAdmin.logout(); session.refresh(false); Node root = session.getRootNode(); Node node = root.getNode(nodeName1); Property property = node.getProperty(this.propertyName1); property.remove(); session.save(); } private void testRegisterNamespace( Session session ) throws Exception { String unusedPrefix = session.getUserID(); session.getWorkspace().getNamespaceRegistry().registerNamespace(unusedPrefix, unusedPrefix); session.getWorkspace().getNamespaceRegistry().unregisterNamespace(unusedPrefix); } private void testRegisterType( Session session ) throws Exception { JcrNodeTypeManager nodeTypes = (JcrNodeTypeManager)session.getWorkspace().getNodeTypeManager(); NodeTypeTemplate newType = nodeTypes.createNodeTypeTemplate(); String nodeTypeName = session.getUserID() + "Type"; newType.setName(nodeTypeName); nodeTypes.registerNodeType(newType, false); nodeTypes.unregisterNodeTypes(Collections.singleton(nodeTypeName)); } private void testWrite( Session session ) throws Exception { testAddNode(session); testSetProperty(session); testRemoveProperty(session); testRemoveNode(session); } private void testAdmin( Session session ) throws Exception { testRegisterNamespace(session); testRegisterType(session); } protected boolean useDeprecatedApi() { return false; } @SuppressWarnings( "deprecation" ) protected VersionHistory versionHistory( Node node ) throws RepositoryException { if (useDeprecatedApi()) return node.getVersionHistory(); return session.getWorkspace().getVersionManager().getVersionHistory(node.getPath()); } @SuppressWarnings( "deprecation" ) protected Version baseVersion( Node node ) throws RepositoryException { if (useDeprecatedApi()) return node.getBaseVersion(); return session.getWorkspace().getVersionManager().getBaseVersion(node.getPath()); } @SuppressWarnings( "deprecation" ) protected Version checkin( Node node ) throws RepositoryException { if (useDeprecatedApi()) return node.checkin(); return session.getWorkspace().getVersionManager().checkin(node.getPath()); } @SuppressWarnings( "deprecation" ) protected void checkout( Node node ) throws RepositoryException { if (useDeprecatedApi()) { node.checkout(); } else { session.getWorkspace().getVersionManager().checkout(node.getPath()); } } @SuppressWarnings( "deprecation" ) protected void restore( Node node, Version version, boolean removeExisting ) throws RepositoryException { if (useDeprecatedApi()) { node.restore(version, removeExisting); } else { session.getWorkspace().getVersionManager().restore(version, removeExisting); } } @SuppressWarnings( "deprecation" ) protected void lock( Node node, boolean isDeep, boolean isSessionScoped ) throws RepositoryException { if (useDeprecatedApi()) { node.lock(isDeep, isSessionScoped); } else { session.getWorkspace().getLockManager().lock(node.getPath(), isDeep, isSessionScoped, 1L, "owner"); } } protected void print( String msg ) { if (print) { System.out.println(msg); } } protected void printSubgraph( Node node ) throws RepositoryException { if (print) { JcrTools tools = new JcrTools(); tools.printSubgraph(node); } } protected void printVersionHistory( Node node ) throws RepositoryException { VersionHistory history = session.getWorkspace().getVersionManager().getVersionHistory(node.getPath()); printSubgraph(history); } /** * Tests that read-only sessions can read nodes by loading all of the children of the root node * * @throws Exception */ public void testShouldAllowReadOnlySessionToRead() throws Exception { session = getHelper().getReadOnlySession(); testRead(session); } /** * Tests that read-only sessions cannot add nodes, remove nodes, set nodes, or set properties. * * @throws Exception */ public void testShouldNotAllowReadOnlySessionToWrite() throws Exception { session = getHelper().getReadOnlySession(); try { testAddNode(session); fail("Read-only sessions should not be able to add nodes"); } catch (AccessDeniedException expected) { } try { testSetProperty(session); fail("Read-only sessions should not be able to set properties"); } catch (AccessDeniedException expected) { } try { testRemoveProperty(session); fail("Read-only sessions should not be able to remove properties"); } catch (AccessDeniedException expected) { } try { testRemoveNode(session); fail("Read-only sessions should not be able to remove nodes"); } catch (AccessDeniedException expected) { } } /** * Tests that read-only sessions cannot register namespaces or types * * @throws Exception */ public void testShouldNotAllowReadOnlySessionToAdmin() throws Exception { session = getHelper().getReadOnlySession(); try { testRegisterNamespace(session); fail("Read-only sessions should not be able to register namespaces"); } catch (AccessDeniedException expected) { } try { testRegisterType(session); fail("Read-only sessions should not be able to register types"); } catch (AccessDeniedException expected) { } } /** * Tests that read-write sessions can read nodes by loading all of the children of the root node * * @throws Exception */ public void testShouldAllowReadWriteSessionToRead() throws Exception { session = getHelper().getReadWriteSession(); testRead(session); } /** * Tests that read-write sessions can add nodes, remove nodes, set nodes, and set properties. * * @throws Exception */ public void testShouldAllowReadWriteSessionToWrite() throws Exception { session = getHelper().getReadWriteSession(); testWrite(session); } /** * Tests that read-write sessions cannot register namespaces or types * * @throws Exception */ public void testShouldNotAllowReadWriteSessionToAdmin() throws Exception { session = getHelper().getReadWriteSession(); try { testRegisterNamespace(session); fail("Read-write sessions should not be able to register namespaces"); } catch (AccessDeniedException expected) { } try { testRegisterType(session); fail("Read-write sessions should not be able to register types"); } catch (AccessDeniedException expected) { } } /** * Tests that admin sessions can read nodes by loading all of the children of the root node * * @throws Exception */ public void testShouldAllowAdminSessionToRead() throws Exception { session = getHelper().getSuperuserSession(); testRead(session); } /** * Tests that admin sessions can add nodes, remove nodes, set nodes, and set properties. * * @throws Exception */ public void testShouldAllowAdminSessionToWrite() throws Exception { session = getHelper().getSuperuserSession(); testWrite(session); } /** * Tests that admin sessions can register namespaces and types * * @throws Exception */ public void testShouldAllowAdminSessionToAdmin() throws Exception { session = getHelper().getSuperuserSession(); testAdmin(session); } /** * User defaultuser is configured to have readwrite in "otherWorkspace" and readonly in the default workspace. This test makes * sure both work. * * @throws Exception */ public void testShouldMapReadRolesToWorkspacesWhenSpecified() throws Exception { Credentials creds = new SimpleCredentials("defaultonly", "defaultonly".toCharArray()); session = getHelper().getRepository().login(creds); testRead(session); session.logout(); // If the repo only supports one workspace, stop here if ("default".equals(this.workspaceName)) return; session = getHelper().getRepository().login(creds, this.workspaceName); testRead(session); try { testWrite(session); fail("User 'defaultuser' should not have write access to '" + this.workspaceName + "'"); } catch (AccessDeniedException expected) { } session.logout(); } /** * User defaultuser is configured to have readwrite in "otherWorkspace" and readonly in the default workspace. This test makes * sure both work. * * @throws Exception */ public void testShouldMapWriteRolesToWorkspacesWhenSpecified() throws Exception { Credentials creds = new SimpleCredentials("defaultonly", "defaultonly".toCharArray()); session = getHelper().getRepository().login(creds); testRead(session); testWrite(session); session.logout(); session = getHelper().getRepository().login(creds, "otherWorkspace"); testRead(session); try { testWrite(session); fail("User 'defaultuser' should not have write access to 'otherWorkspace'"); } catch (AccessDeniedException expected) { } session.logout(); } /** * Users should not be able to see workspaces to which they don't at least have read access. User 'noaccess' has no access to * the default workspace. * * @throws Exception */ public void testShouldNotSeeWorkspacesWithoutReadPermission() throws Exception { Credentials creds = new SimpleCredentials("noaccess", "noaccess".toCharArray()); try { session = getHelper().getRepository().login(creds); fail("User 'noaccess' with no access to the default workspace should not be able to log into that workspace"); } catch (LoginException le) { // Expected } // If the repo only supports one workspace, stop here if ("default".equals(this.workspaceName)) return; session = getHelper().getRepository().login(creds, this.workspaceName); String[] workspaceNames = session.getWorkspace().getAccessibleWorkspaceNames(); assertThat(workspaceNames.length, is(1)); assertThat(workspaceNames[0], is(this.workspaceName)); session.logout(); } public void testShouldCopyFromAnotherWorkspace() throws Exception { session = getHelper().getSuperuserSession("otherWorkspace"); String nodetype1 = this.getProperty("nodetype"); Node node1 = session.getRootNode().addNode(nodeName1, nodetype1); node1.addNode(nodeName2, nodetype1); session.save(); session.logout(); superuser.getRootNode().addNode(nodeName4, nodetype1); superuser.save(); superuser.getWorkspace().copy("otherWorkspace", "/" + nodeName1, "/" + nodeName4 + "/" + nodeName1); Node node4 = superuser.getRootNode().getNode(nodeName4); Node node4node1 = node4.getNode(nodeName1); Node node4node1node2 = node4node1.getNode(nodeName2); assertNotNull(node4node1node2); } /** * A clone operation with removeExisting = true should fail if it would require removing an existing node that is a mandatory * child node of some other parent (and not replacing it as part of the clone operation). * * @throws Exception if an error occurs */ public void testShouldNotCloneIfItWouldViolateTypeSemantics() throws Exception { session = getHelper().getSuperuserSession("otherWorkspace"); assertThat(session.getWorkspace().getName(), is("otherWorkspace")); String nodetype1 = this.getProperty("nodetype"); Node node1 = session.getRootNode().addNode("cloneSource", nodetype1); // This node is not a mandatory child of nodetype1 (modetest:referenceableUnstructured) node1.addNode("modetest:mandatoryChild", nodetype1); session.save(); session.logout(); // /cloneTarget in the default workspace is type mode:referenceableUnstructured superuser.getRootNode().addNode("cloneTarget", nodetype1); // /node3 in the default workspace is type mode:referenceableUnstructured superuser.getRootNode().addNode(nodeName3, nodetype1); superuser.save(); // Throw the cloned items under cloneTarget superuser.getWorkspace().clone("otherWorkspace", "/cloneSource", "/cloneTarget/cloneSource", false); superuser.refresh(false); Node node3 = (Node)superuser.getItem("/node3"); assertThat(node3.getNodes().getSize(), is(0L)); Node node4node1 = (Node)superuser.getItem("/cloneTarget/cloneSource"); assertThat(node4node1.getNodes().getSize(), is(1L)); // Now clone from the same source under node3 and remove the existing records superuser.getWorkspace().clone("otherWorkspace", "/cloneSource", "/" + nodeName3 + "/cloneSource", true); superuser.refresh(false); Node node3node1 = (Node)superuser.getItem("/node3/cloneSource"); assertThat(node3node1.getNodes().getSize(), is(1L)); // Check that the nodes were indeed removed Node node4 = (Node)superuser.getItem("/cloneTarget"); assertThat(node4.getNodes().getSize(), is(0L)); superuser.getRootNode().addNode("nodeWithMandatoryChild", "modetest:nodeWithMandatoryChild"); try { superuser.save(); fail("A node with type modetest:nodeWithMandatoryChild should not be savable until the child is added"); } catch (ConstraintViolationException cve) { // Expected } superuser.move("/node3/cloneSource/modetest:mandatoryChild", "/nodeWithMandatoryChild/modetest:mandatoryChild"); superuser.save(); superuser.refresh(false); // Now clone from the same source under node3 and remove the existing records try { superuser.getWorkspace().clone("otherWorkspace", "/cloneSource", "/" + nodeName3 + "/cloneSource", true); fail("Should not be able to use clone to remove the mandatory child node at /nodeWithMandatoryChild/modetest:mandatoryChild"); } catch (ConstraintViolationException cve) { // expected } } private void ensureExactlyOneVersionRoot( VersionManager vm, String absPath ) throws Exception { boolean foundRoot = false; VersionHistory vh = vm.getVersionHistory(absPath); VersionIterator vi = vh.getAllVersions(); while (vi.hasNext()) { Version v = vi.nextVersion(); if ("jcr:rootVersion".equals(v.getName())) { if (foundRoot) { fail("Found multiple root versions of versionable node at " + absPath); } foundRoot = true; } } if (!foundRoot) fail("No root version found for versionable node at " + absPath); } @FixFor( "MODE-1017" ) public void testShouldNotHaveTwoRootVersions() throws Exception { Session session = superuser; ValueFactory vf = session.getValueFactory(); Node root = session.getRootNode(); Node file = root.addNode("createfile.mode", "nt:file"); Node content = file.addNode("jcr:content", "nt:resource"); content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); session.save(); file.addMixin("mix:versionable"); // Per Section 15.1: // "Under both simple and full versioning, on persist of a new versionable node N that neither corresponds // nor shares with an existing node: // - The jcr:isCheckedOut property of N is set to true and // - A new VersionHistory (H) is created for N. H contains one Version, the root version (V0) // (see §3.13.5.2 Root Version)." // // This means that the version history should not be created until save is performed. This makes sense, // because otherwise the version history would be persisted for a newly-created node, even though that node // is not yet persisted. Tests with the reference implementation (see sandbox) verified this behavior. try { session.getWorkspace().getVersionManager().getVersionHistory(file.getPath()); fail("It should not be possible to obtain the version history for a transient node."); } catch (UnsupportedRepositoryOperationException e) { // this exception is expected } session.save(); ensureExactlyOneVersionRoot(session.getWorkspace().getVersionManager(), file.getPath()); session.refresh(false); ensureExactlyOneVersionRoot(session.getWorkspace().getVersionManager(), file.getPath()); } public void testShouldFailToGetVersionHistoryOfTransientNode() throws Exception { Session session = superuser; VersionManager vm = session.getWorkspace().getVersionManager(); Node root = session.getRootNode(); Node file = root.addNode("createfile2.mode", "nt:file"); Node content = file.addNode("jcr:content", "nt:resource"); content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); // Do not save the node ... // session.save(); // Now add the mixin transiently ... file.addMixin("mix:versionable"); try { vm.getVersionHistory(file.getPath()); fail("It should not be possible to obtain the version history for a transient node."); } catch (InvalidItemStateException e) { // expected } session.save(); ensureExactlyOneVersionRoot(vm, file.getPath()); } public void testShouldFailToGetVersionHistoryOfPersistentNonVersionableNodeThatWasTransientlyMadeVersionable() throws Exception { Session session = superuser; VersionManager vm = session.getWorkspace().getVersionManager(); Node root = session.getRootNode(); Node file = root.addNode("createfile3.mode", "nt:file"); Node content = file.addNode("jcr:content", "nt:resource"); content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); // Save the node ... session.save(); // Now add the mixin transiently ... file.addMixin("mix:versionable"); try { vm.getVersionHistory(file.getPath()); fail("It should not be possible to obtain the version history for a persistent node that was newly-made versionable."); } catch (UnsupportedRepositoryOperationException e) { // expected } session.save(); ensureExactlyOneVersionRoot(vm, file.getPath()); } public void testShouldNotHaveVersionHistoryForTransientVersionableNode() throws Exception { Session session = superuser; ValueFactory vf = session.getValueFactory(); Node root = session.getRootNode(); Node file = root.addNode("createfile.mode", "nt:file"); Node content = file.addNode("jcr:content", "nt:resource"); content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); file.addMixin("mix:versionable"); // Per Section 15.1: // "Under both simple and full versioning, on persist of a new versionable node N that neither corresponds // nor shares with an existing node: // - The jcr:isCheckedOut property of N is set to true and // - A new VersionHistory (H) is created for N. H contains one Version, the root version (V0) // (see §3.13.5.2 Root Version)." // // This means that the version history should not be created until save is performed. This makes sense, // because otherwise the version history would be persisted for a newly-created node, even though that node // is not yet persisted. Tests with the reference implementation (see sandbox) verified this behavior. try { session.getWorkspace().getVersionManager().getVersionHistory(file.getPath()); fail("It should not be possible to obtain the version history for a transient node."); } catch (RepositoryException e) { // some repository exception is expected; the ref impl throws a RepositoryException } session.save(); ensureExactlyOneVersionRoot(session.getWorkspace().getVersionManager(), "/createfile.mode"); session.refresh(false); ensureExactlyOneVersionRoot(session.getWorkspace().getVersionManager(), "/createfile.mode"); } @FixFor( "MODE-1089" ) public void testShouldNotFailGettingVersionHistoryForNodeMadeVersionableSinceLastSave() throws Exception { Session session1 = getHelper().getSuperuserSession(); VersionManager vm = session1.getWorkspace().getVersionManager(); // Create node structure Node root = session1.getRootNode(); Node area = root.addNode("tmp2", "nt:unstructured"); Node outer = area.addNode("outerFolder"); Node inner = outer.addNode("innerFolder"); Node file = inner.addNode("testFile.dat"); file.setProperty("jcr:mimeType", "text/plain"); file.setProperty("jcr:data", "Original content"); session1.save(); file.addMixin("mix:versionable"); // session.save(); isVersionable(vm, file); // here's the problem session1.save(); Version v1 = vm.checkin(file.getPath()); assertThat(v1, is(notNullValue())); // System.out.println("Created version: " + v1); // ubgraph(root.getNode("jcr:system/jcr:versionStorage")); } @SuppressWarnings( "deprecation" ) public void testShouldCreateProperVersionHistoryWhenSavingVersionedNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = session.getRootNode().addNode("test", "nt:unstructured"); node.addMixin("mix:versionable"); session.save(); assertThat(node.hasProperty("jcr:isCheckedOut"), is(true)); assertThat(node.getProperty("jcr:isCheckedOut").getBoolean(), is(true)); assertThat(node.hasProperty("jcr:versionHistory"), is(true)); Node history = node.getProperty("jcr:versionHistory").getNode(); assertThat(history, is(notNullValue())); assertThat(node.hasProperty("jcr:baseVersion"), is(true)); Node version = node.getProperty("jcr:baseVersion").getNode(); assertThat(version, is(notNullValue())); assertThat(version.getParent(), is(history)); assertThat(node.hasProperty("jcr:uuid"), is(true)); assertThat(node.getProperty("jcr:uuid").getString(), is(history.getProperty("jcr:versionableUuid").getString())); assertThat(versionHistory(node).getUUID(), is(history.getUUID())); assertThat(versionHistory(node).getIdentifier(), is(history.getIdentifier())); assertThat(versionHistory(node).getPath(), is(history.getPath())); assertThat(baseVersion(node).getUUID(), is(version.getUUID())); assertThat(baseVersion(node).getIdentifier(), is(version.getIdentifier())); assertThat(baseVersion(node).getPath(), is(version.getPath())); } public void testShouldCreateProperStructureForPropertiesOnTheFirstCheckInOfANode() throws Exception { session = getHelper().getReadWriteSession(); Node node = session.getRootNode().addNode("/checkInTest", "modetest:versionTest"); session.save(); node.addMixin("mix:versionable"); session.save(); node.setProperty("abortProp", "abortPropValue"); node.setProperty("copyProp", "copyPropValue"); node.setProperty("ignoreProp", "ignorePropValue"); node.setProperty("versionProp", "versionPropValue"); session.save(); try { checkin(node); fail("Should not be able to checkin a node with a property that has an OnParentVersionAction of ABORT"); } catch (VersionException ve) { assertThat(node.getProperty("jcr:isCheckedOut").getBoolean(), is(true)); } node.setProperty("abortProp", (String)null); session.save(); checkin(node); assertThat(node.getProperty("jcr:isCheckedOut").getBoolean(), is(false)); Version version = baseVersion(node); assertThat(version, is(notNullValue())); assertThat(version.getProperty("jcr:frozenNode/copyProp").getString(), is("copyPropValue")); assertThat(version.getProperty("jcr:frozenNode/versionProp").getString(), is("versionPropValue")); try { version.getProperty("jcr:frozenNode/ignoreProp"); fail("Frozen version should not record a property that has an OnParentVersionAction of IGNORE"); } catch (PathNotFoundException pnfe) { // Expected } checkout(node); node.setProperty("abortProp", "abortPropValueNew"); node.setProperty("copyProp", "copyPropValueNew"); node.setProperty("ignoreProp", "ignorePropValueNew"); node.setProperty("versionProp", "versionPropValueNew"); version = baseVersion(node); assertThat(version, is(notNullValue())); assertThat(version.getProperty("jcr:frozenNode/copyProp").getString(), is("copyPropValue")); assertThat(version.getProperty("jcr:frozenNode/versionProp").getString(), is("versionPropValue")); try { version.getProperty("ignoreProp"); fail("Frozen version should not record a property that has an OnParentVersionAction of IGNORE"); } catch (PathNotFoundException pnfe) { // Expected } session.save(); } public void testShouldCreateProperHistoryForNodeWithCopySemantics() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode/copyNode/AbortNode with the first copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); Node midLevel = copyNode.addNode("copyNode", "modetest:versionTest"); midLevel.setProperty("ignoreProp", "ignorePropValue"); midLevel.setProperty("copyProp", "copyPropValue"); Node abortNode = midLevel.addNode("abortNode", "modetest:versionTest"); abortNode.setProperty("ignoreProp", "ignorePropValue"); abortNode.setProperty("copyProp", "copyPropValue"); /* * Create /checkinTest/copyNode/versionNode with versionNode being versionable as well. This should * create a copy of versionNode in the version history, due to copyNode (the root of the checkin) having * COPY semantics for the OnParentVersionAction. */ Node versionNode = copyNode.addNode("versionNode", "modetest:versionTest"); versionNode.addMixin("mix:versionable"); session.save(); Version version = checkin(copyNode); Version version2 = checkin(versionNode); assertThat(version.getProperty("jcr:frozenNode/versionNode/jcr:primaryType").getString(), is("nt:versionedChild")); assertThat(version.getProperty("jcr:frozenNode/copyNode/abortNode/copyProp").getString(), is("copyPropValue")); try { version.getProperty("jcr:frozenNode/abortNode/ignoreProp"); fail("Property with OnParentVersionAction of IGNORE should not have been copied"); } catch (PathNotFoundException pnfe) { // Expected } assertThat(version2.getProperty("jcr:frozenNode/jcr:primaryType").getString(), is("nt:frozenNode")); assertThat(version2.getProperty("jcr:frozenNode/jcr:frozenPrimaryType").getString(), is("modetest:versionTest")); assertThat(version2.getProperty("jcr:frozenNode/jcr:frozenUuid").getString(), is(versionNode.getIdentifier())); } public void testShouldThrowExceptionWhenVersioningChildNodeWithOnParentVersionSemanticsOfAbort() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/versionNode/abortNode with versionNode being versionable. This should fail * when versionNode is checked in, as the OnParentVersionAction semantics come from the OPV of the child. */ Node versionNode = node.addNode("versionNode", "modetest:versionTest"); versionNode.addMixin("mix:versionable"); Node abortNode = versionNode.addNode("abortNode", "modetest:versionTest"); abortNode.setProperty("ignoreProp", "ignorePropValue"); abortNode.setProperty("copyProp", "copyPropValue"); session.save(); try { checkin(versionNode); fail("Child node with OnParentVersionAction of ABORT should have resulted in exception."); } catch (VersionException ve) { // Expected } } public void testShouldCreateProperHistoryForVersionableChildOfNodeWithVersionSemantics() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/versionNode/copyNode with versionNode and copyNode being versionable. This should * create a child of type nt:childVersionedNode under the frozen node. */ Node versionNode = node.addNode("versionNode", "modetest:versionTest"); versionNode.addMixin("mix:versionable"); Node copyNode = versionNode.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("copyProp", "copyPropValue"); session.save(); Version version = checkin(versionNode); assertThat(version.getProperty("jcr:frozenNode/copyNode/jcr:primaryType").getString(), is("nt:frozenNode")); assertThat(version.getProperty("jcr:frozenNode/copyNode/jcr:frozenPrimaryType").getString(), is("modetest:versionTest")); try { version.getProperty("jcr:frozenNode/copyNode/copyProp"); } catch (PathNotFoundException pnfe) { fail("Property should be copied to versionable child of versioned node, because copyNode was copied (see Section 3.13.9 Item 5 of JSR-283)"); } try { version.getProperty("jcr:frozenNode/copyNode/ignoreProp"); } catch (PathNotFoundException pnfe) { fail("Ignored property should be copied to versionable child of versioned node when in a COPY subgraph"); } } public void testShouldRestorePropertiesOnVersionableNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode with copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("copyProp", "copyPropValue"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("computeProp", "computePropValue"); session.save(); Version version = checkin(copyNode); /* * Make some changes */ checkout(copyNode); copyNode.addMixin("mix:lockable"); copyNode.setProperty("copyProp", "copyPropValueNew"); copyNode.setProperty("ignoreProp", "ignorePropValueNew"); copyNode.setProperty("versionProp", "versionPropValueNew"); copyNode.setProperty("computeProp", "computePropValueNew"); session.save(); checkin(copyNode); restore(copyNode, version, false); assertThat(copyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(copyNode.getProperty("ignoreProp").getString(), is("ignorePropValueNew")); assertThat(copyNode.getProperty("computeProp").getString(), is("computePropValueNew")); try { copyNode.getProperty("versionProp"); fail("Property with OnParentVersionAction of VERSION added after version should be removed during restore"); } catch (PathNotFoundException pnfe) { // Expected } } @FixFor( "MODE-1228" ) public void testShouldRestoreWithNoReplaceTheNonReferenceableCopiedChildNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode with copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("copyProp", "copyPropValue"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("computeProp", "computePropValue"); Node belowCopyNode = copyNode.addNode("copyNode", "nt:unstructured"); belowCopyNode.addMixin("mix:title"); belowCopyNode.setProperty("copyProp", "copyPropValue"); belowCopyNode.setProperty("ignoreProp", "ignorePropValue"); belowCopyNode.setProperty("computeProp", "computePropValue"); belowCopyNode.setProperty("versionProp", "versionPropValue"); session.save(); assertThat(belowCopyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode.getMixinNodeTypes()[0].getName(), is("mix:title")); Version version = checkin(copyNode); /* * Make some changes */ checkout(copyNode); copyNode.addMixin("mix:lockable"); copyNode.setProperty("copyProp", "copyPropValueNew"); copyNode.setProperty("ignoreProp", "ignorePropValueNew"); copyNode.setProperty("versionProp", "versionPropValueNew"); copyNode.setProperty("computeProp", "computePropValueNew"); belowCopyNode.setProperty("versionProp", "versionPropValueNew"); belowCopyNode.setProperty("computeProp", "computePropValueNew"); session.save(); checkin(copyNode); // printSubgraph(copyNode); // printVersionHistory(copyNode); restore(copyNode, version, false); assertThat(copyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(copyNode.getProperty("ignoreProp").getString(), is("ignorePropValueNew")); assertThat(copyNode.getProperty("computeProp").getString(), is("computePropValueNew")); try { copyNode.getProperty("versionProp"); fail("Property with OnParentVersionAction of VERSION added after version should be removed during restore"); } catch (PathNotFoundException pnfe) { // Expected } // Note that in the case of properties on copied subnodes of a restored node, they replace the nodes // in the workspace unless there is a node with the same identifier. See Section 15.7.6 for details. Node belowCopyNode2 = copyNode.getNode("copyNode"); assertThat(belowCopyNode2.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode2.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode2.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode2.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode2.getMixinNodeTypes()[0].getName(), is("mix:title")); } @FixFor( "MODE-1228" ) public void testShouldRestoreWithReplaceTheNonReferenceableCopiedChildNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode with copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("copyProp", "copyPropValue"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("computeProp", "computePropValue"); Node belowCopyNode = copyNode.addNode("copyNode", "nt:unstructured"); belowCopyNode.addMixin("mix:title"); belowCopyNode.setProperty("copyProp", "copyPropValue"); belowCopyNode.setProperty("ignoreProp", "ignorePropValue"); belowCopyNode.setProperty("computeProp", "computePropValue"); belowCopyNode.setProperty("versionProp", "versionPropValue"); session.save(); assertThat(belowCopyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode.getMixinNodeTypes()[0].getName(), is("mix:title")); Version version = checkin(copyNode); /* * Make some changes */ checkout(copyNode); copyNode.addMixin("mix:lockable"); copyNode.setProperty("copyProp", "copyPropValueNew"); copyNode.setProperty("ignoreProp", "ignorePropValueNew"); copyNode.setProperty("versionProp", "versionPropValueNew"); copyNode.setProperty("computeProp", "computePropValueNew"); belowCopyNode.setProperty("versionProp", "versionPropValueNew"); belowCopyNode.setProperty("computeProp", "computePropValueNew"); session.save(); checkin(copyNode); restore(copyNode, version, true); assertThat(copyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(copyNode.getProperty("ignoreProp").getString(), is("ignorePropValueNew")); assertThat(copyNode.getProperty("computeProp").getString(), is("computePropValueNew")); try { copyNode.getProperty("versionProp"); fail("Property with OnParentVersionAction of VERSION added after version should be removed during restore"); } catch (PathNotFoundException pnfe) { // Expected } // Note that in the case of properties on copied subnodes of a restored node, they replace the nodes // in the workspace unless there is a node with the same identifier. See Section 15.7.6 for details. Node belowCopyNode2 = copyNode.getNode("copyNode"); assertThat(belowCopyNode2.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode2.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode2.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode2.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode2.getMixinNodeTypes()[0].getName(), is("mix:title")); } @FixFor( "MODE-1228" ) public void testShouldRestoreWithNoReplaceTheReferenceableCopiedChildNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode with copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("copyProp", "copyPropValue"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("computeProp", "computePropValue"); Node belowCopyNode = copyNode.addNode("copyNode", "nt:unstructured"); belowCopyNode.addMixin("mix:referenceable"); belowCopyNode.setProperty("copyProp", "copyPropValue"); belowCopyNode.setProperty("ignoreProp", "ignorePropValue"); belowCopyNode.setProperty("computeProp", "computePropValue"); belowCopyNode.setProperty("versionProp", "versionPropValue"); session.save(); assertThat(belowCopyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode.getMixinNodeTypes()[0].getName(), is("mix:referenceable")); String belowCopyNodeId = belowCopyNode.getIdentifier(); Version version = checkin(copyNode); // printSubgraph(version); /* * Make some changes */ checkout(copyNode); copyNode.addMixin("mix:lockable"); copyNode.setProperty("copyProp", "copyPropValueNew"); copyNode.setProperty("ignoreProp", "ignorePropValueNew"); copyNode.setProperty("versionProp", "versionPropValueNew"); copyNode.setProperty("computeProp", "computePropValueNew"); belowCopyNode.setProperty("versionProp", "versionPropValueNew"); belowCopyNode.setProperty("computeProp", "computePropValueNew"); session.save(); checkin(copyNode); // printVersionHistory(copyNode); restore(copyNode, version, false); assertThat(copyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(copyNode.getProperty("ignoreProp").getString(), is("ignorePropValueNew")); assertThat(copyNode.getProperty("computeProp").getString(), is("computePropValueNew")); try { copyNode.getProperty("versionProp"); fail("Property with OnParentVersionAction of VERSION added after version should be removed during restore"); } catch (PathNotFoundException pnfe) { // Expected } // Note that in the case of properties on copied subnodes of a restored node, they do NOT replace the // referenceable nodes (with the same identifier) in the workspace. See Section 15.7.6 for details. // Therefore, the properties should match the updated values... Node belowCopyNode2 = copyNode.getNode("copyNode"); String belowCopyNode2Id = belowCopyNode.getIdentifier(); assertThat(belowCopyNodeId, is(belowCopyNode2Id)); assertThat(belowCopyNode2.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode2.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode2.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode2.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode2.getMixinNodeTypes()[0].getName(), is("mix:referenceable")); } @FixFor( "MODE-1228" ) public void testShouldRestoreWithReplaceTheReferenceableCopiedChildNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "modetest:versionTest"); session.save(); /* * Create /checkinTest/copyNode with copyNode being versionable. This should be able * to be checked in, as the ABORT status of abortNode is ignored when copyNode is checked in. */ Node copyNode = node.addNode("copyNode", "modetest:versionTest"); copyNode.addMixin("mix:versionable"); copyNode.setProperty("copyProp", "copyPropValue"); copyNode.setProperty("ignoreProp", "ignorePropValue"); copyNode.setProperty("computeProp", "computePropValue"); Node belowCopyNode = copyNode.addNode("copyNode", "nt:unstructured"); belowCopyNode.addMixin("mix:referenceable"); belowCopyNode.setProperty("copyProp", "copyPropValue"); belowCopyNode.setProperty("ignoreProp", "ignorePropValue"); belowCopyNode.setProperty("computeProp", "computePropValue"); belowCopyNode.setProperty("versionProp", "versionPropValue"); session.save(); assertThat(belowCopyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode.getMixinNodeTypes()[0].getName(), is("mix:referenceable")); String belowCopyNodeId = belowCopyNode.getIdentifier(); Version version = checkin(copyNode); // printSubgraph(version); /* * Make some changes */ checkout(copyNode); copyNode.addMixin("mix:lockable"); copyNode.setProperty("copyProp", "copyPropValueNew"); copyNode.setProperty("ignoreProp", "ignorePropValueNew"); copyNode.setProperty("versionProp", "versionPropValueNew"); copyNode.setProperty("computeProp", "computePropValueNew"); belowCopyNode.setProperty("versionProp", "versionPropValueNew"); belowCopyNode.setProperty("computeProp", "computePropValueNew"); session.save(); checkin(copyNode); // printVersionHistory(copyNode); restore(copyNode, version, true); assertThat(copyNode.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(copyNode.getProperty("ignoreProp").getString(), is("ignorePropValueNew")); assertThat(copyNode.getProperty("computeProp").getString(), is("computePropValueNew")); try { copyNode.getProperty("versionProp"); fail("Property with OnParentVersionAction of VERSION added after version should be removed during restore"); } catch (PathNotFoundException pnfe) { // Expected } // Note that in the case of properties on copied subnodes of a restored node, they replace the nodes // in the workspace unless there is a node with the same identifier. See Section 15.7.6 for details. Node belowCopyNode2 = copyNode.getNode("copyNode"); String belowCopyNode2Id = belowCopyNode.getIdentifier(); assertThat(belowCopyNodeId, is(belowCopyNode2Id)); assertThat(belowCopyNode2.getProperty("copyProp").getString(), is("copyPropValue")); assertThat(belowCopyNode2.getProperty("ignoreProp").getString(), is("ignorePropValue")); assertThat(belowCopyNode2.getProperty("computeProp").getString(), is("computePropValue")); assertThat(belowCopyNode2.getProperty("versionProp").getString(), is("versionPropValue")); assertThat(belowCopyNode2.getMixinNodeTypes()[0].getName(), is("mix:referenceable")); } @FixFor( "MODE-693" ) public void testShouldAllowDeletingNodesWhenLargePropertyIsPresent() throws Exception { session = getHelper().getReadWriteSession(); Node root = session.getRootNode(); final int SIZE = 2048; byte[] largeArray = new byte[SIZE]; for (int i = 0; i < SIZE; i++) { largeArray[i] = (byte)'x'; } Node projectNode = root.addNode("mode693", "nt:unstructured"); Node fileNode = projectNode.addNode("fileNode", "nt:file"); Node contentNode = fileNode.addNode("jcr:content", "nt:resource"); Binary binaryValue = session.getValueFactory().createBinary(new ByteArrayInputStream(largeArray)); contentNode.setProperty("jcr:data", binaryValue); contentNode.setProperty("jcr:lastModified", Calendar.getInstance()); contentNode.setProperty("jcr:mimeType", "application/octet-stream"); Node otherNode = projectNode.addNode("otherNode", "nt:unstructured"); session.save(); String pathToNode = projectNode.getName() + "/" + otherNode.getName(); if (!root.hasNode(pathToNode)) { throw new RepositoryException("Cannot delete node at path=" + pathToNode + " since no node exists at this path"); } Node nodeToDelete = root.getNode(pathToNode); nodeToDelete.remove(); session.save(); // Make sure that only one node was deleted assertThat(root.hasNode(projectNode.getName()), is(true)); assertThat(projectNode.hasNode(fileNode.getName()), is(true)); assertThat(fileNode.hasNode(contentNode.getName()), is(true)); assertThat(root.hasNode(pathToNode), is(false)); } @FixFor( "MODE-704" ) public void testShouldReturnSilentlyWhenCheckingOutACheckedOutNode() throws Exception { session = getHelper().getReadWriteSession(); Node root = session.getRootNode(); Node testNode = root.addNode("checkedOutNodeTest", "nt:unstructured"); session.save(); // Add the mixin, but don't save it testNode.addMixin("mix:versionable"); checkout(testNode); session.save(); // Now check that it still returns silently on a saved node that was never checked in. checkout(testNode); } @FixFor( "MODE-709" ) public void testShouldCreateVersionStorageForWhenVersionableNodesCopied() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("versionableNodeForCopy", "nt:unstructured"); parentNode.addMixin("mix:versionable"); Node childNode = parentNode.addNode("versionableChild", "nt:unstructured"); childNode.addMixin("mix:versionable"); Node targetNode = root.addNode("destForCopy", "nt:unstructured"); session.save(); String newParentPath = targetNode.getPath() + "/" + parentNode.getName(); session.getWorkspace().copy(parentNode.getPath(), newParentPath); parentNode = (Node)session.getItem(newParentPath); childNode = parentNode.getNode("versionableChild"); checkout(parentNode); checkin(parentNode); checkout(childNode); checkin(childNode); } @FixFor( "MODE-1822" ) public void testShouldBeAbleToVersionWithinUserTransactionAndJBossTransactionManager() throws Exception { session = getHelper().getReadWriteSession(); VersionManager vm = session.getWorkspace().getVersionManager(); Node node = session.getRootNode().addNode("Test3"); node.addMixin("mix:versionable"); node.setProperty("name", "lalalal"); node.setProperty("code", "lalalal"); session.save(); vm.checkin(node.getPath()); print("Checked in " + node.getPath()); for (int i = 0; i != 2; ++i) { // Check it back out before we commit ... node = session.getRootNode().getNode("Test3"); print("Checking out " + node.getPath()); vm.checkout(node.getPath()); // Make some more changes ... node.setProperty("code", "fa-lalalal"); print("Saving changes to " + node.getPath()); session.save(); // Check it back in ... print("Checking in " + node.getPath()); vm.checkin(node.getPath()); } } @FixFor( "MODE-720" ) @SuppressWarnings( "unchecked" ) public void testShouldBeAbleToReferToUnsavedReferenceNode() throws Exception { session = getHelper().getSuperuserSession(); JcrNodeTypeManager nodeTypes = (JcrNodeTypeManager)session.getWorkspace().getNodeTypeManager(); /* * Register a one-off node type with a reference property that has a constraint on it */ NodeTypeTemplate ntt = nodeTypes.createNodeTypeTemplate(); ntt.setName("modetest:constrainedPropType"); PropertyDefinitionTemplate pdt = nodeTypes.createPropertyDefinitionTemplate(); pdt.setName("modetest:constrainedProp"); pdt.setRequiredType(PropertyType.REFERENCE); pdt.setValueConstraints(new String[] {"nt:unstructured"}); ntt.getPropertyDefinitionTemplates().add(pdt); nodeTypes.registerNodeType(ntt, false); /* * Add a node that would satisfy the constraint */ Node root = session.getRootNode(); Node parentNode = root.addNode("constrainedNodeTest", "nt:unstructured"); Node targetNode = parentNode.addNode("target", "nt:unstructured"); targetNode.addMixin("mix:referenceable"); /* * Now add a node with the one-off type. */ Node referringNode = parentNode.addNode("referer", "modetest:constrainedPropType"); referringNode.setProperty("modetest:constrainedProp", targetNode); session.save(); } @FixFor( "MODE-1092" ) @SuppressWarnings( "unchecked" ) public void testShouldBeAbleToSetWeakReferences() throws Exception { session = getHelper().getSuperuserSession(); JcrNodeTypeManager nodeTypes = (JcrNodeTypeManager)session.getWorkspace().getNodeTypeManager(); /* * Register a one-off node type with a reference property that has a constraint on it */ NodeTypeTemplate ntt = nodeTypes.createNodeTypeTemplate(); ntt.setName("modetest:typeWithWeakReference"); PropertyDefinitionTemplate pdt = nodeTypes.createPropertyDefinitionTemplate(); pdt.setName("modetest:weakRefProp"); pdt.setRequiredType(PropertyType.WEAKREFERENCE); pdt.setValueConstraints(new String[] {"nt:unstructured"}); ntt.getPropertyDefinitionTemplates().add(pdt); nodeTypes.registerNodeType(ntt, false); /* * Add a node that would satisfy the constraint */ Node root = session.getRootNode(); Node parentNode = root.addNode("weakReferenceTest", "nt:unstructured"); Node targetNode = parentNode.addNode("target", "nt:unstructured"); targetNode.addMixin("mix:referenceable"); /* * Now add a node with the one-off type. */ Node referringNode = parentNode.addNode("referer", "modetest:typeWithWeakReference"); referringNode.setProperty("modetest:weakRefProp", targetNode); session.save(); // Now fetch the referring node again, and verify that the reference is still a weak reference Node referringNode2 = session.getNode("/weakReferenceTest/referer"); Property property = referringNode2.getProperty("modetest:weakRefProp"); assertThat(property.getType(), is(PropertyType.WEAKREFERENCE)); Node referredNode = property.getNode(); assertThat(referredNode, is(targetNode)); } @FixFor( "MODE-701" ) public void testShouldBeAbleToImportAutocreatedChildNodeWithoutDuplication() throws Exception { session = getHelper().getSuperuserSession(); /* * Add a node that would satisfy the constraint */ Node root = getTestRoot(session); Node parentNode = root.addNode("autocreatedChildRoot", "nt:unstructured"); session.save(); Node targetNode = parentNode.addNode("nodeWithAutocreatedChild", "modetest:nodeWithAutocreatedChild"); assertThat(targetNode.getNode("modetest:autocreatedChild"), is(notNullValue())); // Don't save this yet session.refresh(false); InputStream in = getClass().getResourceAsStream("/io/autocreated-node-test.xml"); session.importXML(root.getPath() + "/autocreatedChildRoot", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); } public void testShouldAllowCheckoutAfterMove() throws Exception { // q.v., MODE-??? session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node sourceNode = root.addNode("versionableSource", "nt:unstructured"); sourceNode.addMixin("mix:versionable"); Node targetNode = root.addNode("versionableTarget", "nt:unstructured"); session.save(); String sourceName = sourceNode.getName(); session.move(sourceNode.getPath(), targetNode.getPath() + "/" + sourceName); sourceNode = targetNode.getNode(sourceName); checkout(sourceNode); } @SuppressWarnings( "deprecation" ) public void testAdminUserCanBreakOthersLocksUsingDeprecatedNodeLockAndUnlockMethods() throws Exception { String lockNodeName = "lockTestNode"; session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node lockNode = root.addNode(lockNodeName); lockNode.addMixin("mix:lockable"); session.save(); lockNode.lock(false, false); assertThat(lockNode.isLocked(), is(true)); Session superuser = getHelper().getSuperuserSession(); root = getTestRoot(superuser); lockNode = root.getNode(lockNodeName); assertThat(lockNode.isLocked(), is(true)); lockNode.unlock(); assertThat(lockNode.isLocked(), is(false)); superuser.logout(); } public void testAdminUserCanBreakOthersLocks() throws Exception { String lockNodeName = "lockTestNode2"; session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node lockNode = root.addNode(lockNodeName); lockNode.addMixin("mix:lockable"); session.save(); session.getWorkspace().getLockManager().lock(lockNode.getPath(), false, false, 1L, "me"); assertThat(lockNode.isLocked(), is(true)); Session superuser = getHelper().getSuperuserSession(); root = getTestRoot(superuser); lockNode = root.getNode(lockNodeName); assertThat(lockNode.isLocked(), is(true)); session.getWorkspace().getLockManager().unlock(lockNode.getPath()); assertThat(lockNode.isLocked(), is(false)); superuser.logout(); } public void testShouldNotAllowLockedNodeToBeRemoved() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("lockedParent"); parentNode.addMixin("mix:lockable"); Node targetNode = parentNode.addNode("lockedTarget"); session.save(); lock(parentNode, true, true); Session session2 = getHelper().getReadWriteSession(); Node targetNode2 = (Node)session2.getItem(root.getPath() + "/lockedParent/lockedTarget"); try { targetNode2.remove(); fail("Locked nodes should not be able to be removed"); } catch (LockException le) { // Success } targetNode.remove(); session.save(); } public void testShouldNotAllowPropertyOfLockedNodeToBeRemoved() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("lockedPropParent"); parentNode.addMixin("mix:lockable"); Node targetNode = parentNode.addNode("lockedTarget"); targetNode.setProperty("foo", "bar"); session.save(); lock(parentNode, true, true); Session session2 = getHelper().getReadWriteSession(); Property targetProp2 = (Property)session2.getItem(root.getPath() + "/lockedPropParent/lockedTarget/foo"); try { targetProp2.remove(); fail("Properties of locked nodes should not be able to be removed"); } catch (LockException le) { // Success } targetNode.getProperty("foo").remove(); session.save(); } public void testShouldNotAllowCheckedInNodeToBeRemoved() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("checkedInParent"); parentNode.addMixin("mix:versionable"); Node targetNode = parentNode.addNode("checkedInTarget"); session.save(); checkin(parentNode); try { targetNode.remove(); fail("Checked in nodes should not be able to be removed"); } catch (VersionException ve) { // Success } checkout(parentNode); targetNode.remove(); session.save(); } public void testShouldNotAllowPropertyOfCheckedInNodeToBeRemoved() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("checkedInPropParent"); parentNode.addMixin("mix:versionable"); Node targetNode = parentNode.addNode("checkedInTarget"); Property targetProp = targetNode.setProperty("foo", "bar"); session.save(); checkin(parentNode); try { targetProp.remove(); fail("Properties of checked in nodes should not be able to be removed"); } catch (VersionException ve) { // Success } checkout(parentNode); targetProp.remove(); session.save(); } public void testGetPathOnRemovedNodeShouldThrowException() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node parentNode = root.addNode("invalidItemStateTest"); session.save(); parentNode.remove(); try { parentNode.getPath(); fail("getPath on removed node should throw InvalidItemStateException per section 7.1.3.3 of 1.0.1 spec"); } catch (InvalidItemStateException iise) { // Success } } @FixFor( "MODE-792" ) public void testCheckingOutAnAlreadyCheckedOutNodeShouldHaveNoEffect() throws Exception { session = getHelper().getReadWriteSession(); VersionManager versionManager = session.getWorkspace().getVersionManager(); Node root = getTestRoot(session); Node parentNode = root.addNode("checkedOutNodeNopTest"); parentNode.addMixin("mix:versionable"); session.save(); versionManager.checkin(parentNode.getPath()); versionManager.checkout(parentNode.getPath()); parentNode.setProperty("foo", "bar"); versionManager.checkout(parentNode.getPath()); assertEquals(parentNode.getProperty("foo").getString(), "bar"); } @FixFor( "MODE-793" ) public void testPropertyCardinalityShouldPropagateToFrozenNode() throws Exception { session = getHelper().getReadWriteSession(); VersionManager versionManager = session.getWorkspace().getVersionManager(); Node root = getTestRoot(session); Node parentNode = root.addNode("checkedOutNodeNopTest"); parentNode.addMixin("mix:versionable"); parentNode.setProperty("foo", new String[] {"bar", "baz"}); session.save(); assertEquals(true, parentNode.getProperty("foo").getDefinition().isMultiple()); Version version = versionManager.checkin(parentNode.getPath()); Node frozenNode = version.getFrozenNode(); assertEquals(true, frozenNode.getProperty("foo").getDefinition().isMultiple()); } @FixFor( "MODE-799" ) public void testNodeWithoutETagMixinShouldNotHaveETagProperty() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); root.addNode("someNewNode"); session.save(); assertThat(root.getNode("someNewNode").hasProperty("jcr:etag"), is(false)); } @FixFor( "MODE-799" ) public void testAutomaticCreationUponSaveOfETagPropertyWhenETagMixinIsAddedToNodeWithoutBinaryProperties() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node newNode = root.addNode("someNewNode4"); newNode.addMixin("mix:etag"); session.save(); String etagValue = root.getNode("someNewNode4").getProperty("jcr:etag").getString(); assertThat(etagValue, is("")); } @FixFor( "MODE-799" ) public void testAutomaticCreationUponSaveOfETagPropertyWhenETagMixinIsAddedToNodeWithExistingBinaryProperties() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node newNode = root.addNode("someNewNode2"); Binary binary = session.getValueFactory().createBinary(new ByteArrayInputStream("This is the value".getBytes())); newNode.setProperty("binaryProperty", binary); session.save(); root.getNode("someNewNode2").addMixin("mix:etag"); session.save(); String etagValue = root.getNode("someNewNode2").getProperty("jcr:etag").getString(); assertThat(etagValue.trim().length() != 0, is(true)); } @FixFor( "MODE-799" ) public void testAutomaticCreationUponSaveOfETagPropertyWhenNodeWithETagMixinHasNewBinaryProperty() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node newNode = root.addNode("someNewNode3"); newNode.addMixin("mix:etag"); session.save(); Binary binary = session.getValueFactory().createBinary(new ByteArrayInputStream("This is the value".getBytes())); root.getNode("someNewNode3").setProperty("binaryProperty", binary); session.save(); String etagValue = root.getNode("someNewNode3").getProperty("jcr:etag").getString(); assertThat(etagValue.trim().length() != 0, is(true)); } @FixFor( "MODE-796" ) public void testNodeReferenceRemainsValidAfterSave() throws Exception { session = getHelper().getReadWriteSession(); Node root = getTestRoot(session); Node nodeA = root.addNode("nodeA", "nt:unstructured"); nodeA.setProperty("foo", "bar A"); Node nodeB = root.addNode("nodeB", "nt:unstructured"); nodeB.setProperty("foo", "bar B"); session.save(); // Verify that the node references still have a path ... assertThat(nodeA.getPath(), is(root.getPath() + "/nodeA")); assertThat(nodeB.getPath(), is(root.getPath() + "/nodeB")); // Move 'nodeA' under 'nodeB' ... session.move(nodeA.getPath(), root.getPath() + "/nodeB/nodeA"); assertThat(nodeA.getPath(), is(root.getPath() + "/nodeB/nodeA")); assertThat(nodeB.getPath(), is(root.getPath() + "/nodeB")); session.save(); assertThat(nodeA.getPath(), is(root.getPath() + "/nodeB/nodeA")); assertThat(nodeB.getPath(), is(root.getPath() + "/nodeB")); } @FixFor( "MODE-956" ) public void testShouldBeAbleToSetNonexistingPropertyToNull() throws Exception { Node rootNode = getTestRoot(superuser); Node child = rootNode.addNode("child", "nt:unstructured"); rootNode.getSession().save(); child.setProperty("foo", (Calendar)null); } @FixFor( "MODE-1005" ) public void testShouldThrowRepositoryExceptionForRelativePathsInSessionGetNode() throws Exception { try { Node root = getTestRoot(superuser); root.addNode("nodeForRelativePathTest", "nt:unstructured"); superuser.getNode("nodeForRelativePathTest"); fail("Should throw RepositoryException when attempting to call Session.getNode(String) with a relative path"); } catch (RepositoryException re) { // Expected } } @FixFor( "MODE-1005" ) public void testShouldThrowRepositoryExceptionForRelativePathsInSessionGetProperty() throws Exception { try { Node root = getTestRoot(superuser); root.addNode("propertyNodeForRelativePathTest", "nt:unstructured"); superuser.getProperty("propertyNodeForRelativePathTest/jcr:primaryType"); fail("Should throw RepositoryException when attempting to call Session.getProperty(String) with a relative path"); } catch (RepositoryException re) { // Expected } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockPreventsOtherSessionsFromChangingPropertiesOnLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // subgraph(root1); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toChange = session2.getNode(node2.getPath()); try { toChange.addMixin("mix:versionable"); fail("Expected to see LockException"); } catch (LockException e) { // expected } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockAllowsOtherSessionsToDeleteLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toDelete = session2.getNode(node2.getPath()); try { // This is possible because removing node2 is an alteration of node1, upon which there is no lock toDelete.remove(); session2.save(); } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockAllowsSameSessionToDeleteLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 try { node3.remove(); } finally { tmp.remove(); session1.save(); session1.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockPreventsOtherSessionsFromDeletingChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toDelete = session2.getNode(node3.getPath()); try { toDelete.remove(); fail("Expected to see LockException"); } catch (LockException e) { // expected } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockAllowsSameSessionToDeleteChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 try { node3.remove(); } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockAllowOtherSessionsToChangePropertiesOfChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toChange = session2.getNode(node3.getPath()); try { toChange.addMixin("mix:referenceable"); session2.save(); } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyShallowLockAllowsSameSessionToChangeChildProperties() throws Exception { Session session1 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:unstructured"); Node node1 = tmp.addNode("node1", "nt:unstructured"); Node node2 = tmp.addNode("node1/node2", "nt:unstructured"); Node node3 = tmp.addNode("node1/node2/node3", "nt:unstructured"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), false, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 Node toChange = node2; try { toChange.setProperty("newProp", "newValue"); session1.save(); } finally { tmp.remove(); session1.save(); session1.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyAddingMixinAndSavingRequiredBeforeLockingNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 try { node2.addMixin("mix:lockable"); lm1.lock(node2.getPath(), false, false, 10000, "Locked"); session1.save(); } catch (InvalidItemStateException e) { // expected??, since the lock manager is owned by the workspace } finally { tmp.remove(); session1.save(); session1.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockPreventsOtherSessionsFromChangingPropertiesOnLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toChange = session2.getNode(node2.getPath()); try { toChange.addMixin("mix:versionable"); fail("Expected to see LockException"); } catch (LockException e) { // expected } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockAllowsOtherSessionsToDeleteLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toDelete = session2.getNode(node2.getPath()); try { // This is possible because removing node2 is an alteration of node1, upon which there is no lock toDelete.remove(); session2.save(); } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockAllowsSameSessionToDeleteLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 try { node3.remove(); } finally { tmp.remove(); session1.save(); session1.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockPreventsOtherSessionsFromDeletingChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toDelete = session2.getNode(node3.getPath()); try { toDelete.remove(); fail("Expected to see LockException"); } catch (LockException e) { // expected } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockAllowsSameSessionToDeleteChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 10000, "Locked"); session1.save(); // Attempt to a child node of node2 try { node3.remove(); } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockPreventsOtherSessionsFromChangePropertiesOfChildOfLockedNode() throws Exception { Session session1 = getHelper().getSuperuserSession(); Session session2 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:folder"); Node node1 = tmp.addNode("node1", "nt:folder"); Node node2 = tmp.addNode("node1/node2", "nt:folder"); Node node3 = tmp.addNode("node1/node2/node3", "nt:folder"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 session2.refresh(true); Node toChange = session2.getNode(node3.getPath()); try { toChange.addMixin("mix:referenceable"); session2.save(); fail("Expected to see LockException"); } catch (LockException e) { // expected } finally { tmp.remove(); session1.save(); session1.logout(); session2.logout(); } } @FixFor( "MODE-1040" ) @SuppressWarnings( "unused" ) public void testShouldVerifyDeepLockAllowsSameSessionToChangeChildProperties() throws Exception { Session session1 = getHelper().getSuperuserSession(); LockManager lm1 = session1.getWorkspace().getLockManager(); // Create node structure Node root1 = getTestRoot(session1); Node tmp = root1.addNode("tmp", "nt:unstructured"); Node node1 = tmp.addNode("node1", "nt:unstructured"); Node node2 = tmp.addNode("node1/node2", "nt:unstructured"); Node node3 = tmp.addNode("node1/node2/node3", "nt:unstructured"); session1.save(); // Create an open-scoped shallow lock on node2 node2.addMixin("mix:lockable"); session1.save(); lm1.lock(node2.getPath(), true, false, 100000, "Locked"); session1.save(); // Attempt to a child node of node2 Node toChange = node2; try { toChange.setProperty("newProp", "newValue"); session1.save(); } finally { tmp.remove(); session1.save(); session1.logout(); } } public void testShouldVerifyNtFileNodesHavePrimaryItem() throws Exception { Session session1 = getHelper().getSuperuserSession(); // Create node structure Node root1 = getTestRoot(session1); Node folder1 = root1.addNode("folder1", "nt:folder"); String fileName = "simple.json"; String filePath = folder1.getPath() + "/" + fileName; new JcrTools().uploadFile(session1, filePath, getClass().getResourceAsStream("/data/" + fileName)); session1.save(); // Find the primary item ... Node file1 = folder1.getNode(fileName); Node content = file1.getNode("jcr:content"); assertNotNull(file1); Item primary = file1.getPrimaryItem(); assertThat(primary, is(sameInstance((Item)content))); // Change the primary type of the "jcr:content" node to "nt:unstructured" ... content.setPrimaryType("nt:unstructured"); session1.save(); // Find the primary item (again) ... Node content2 = file1.getNode("jcr:content"); assertNotNull(file1); Item primary2 = file1.getPrimaryItem(); assertThat(primary2, is(sameInstance((Item)content2))); } @FixFor( "MODE-1696" ) public void testShouldVerifyNtResourceNodesHavePrimaryItem() throws Exception { Session session1 = getHelper().getSuperuserSession(); // Create node structure Node root1 = getTestRoot(session1); Node resource = root1.addNode("resource", "nt:resource"); Binary binary = session1.getValueFactory().createBinary(getClass().getResourceAsStream("/data/simple.json")); resource.setProperty("jcr:data", binary); session1.save(); // Find the primary item ... resource = root1.getNode("resource"); Item primary = resource.getPrimaryItem(); assertNotNull(primary); assertThat(resource.getProperty("jcr:data"), is(sameInstance(primary))); } boolean isVersionable( VersionManager vm, Node node ) throws RepositoryException { try { vm.getVersionHistory(node.getPath()); return true; } catch (UnsupportedRepositoryOperationException e) { return false; } } protected void checkinRecursively( Node node, VersionManager vm ) throws RepositoryException { String path = node.getPath(); if (node.isNodeType("mix:versionable") && vm.isCheckedOut(path)) { try { vm.checkin(path); } catch (VersionException e) { // Something on this node can't be checked in, so remove it .. node.remove(); return; } } NodeIterator iter = node.getNodes(); while (iter.hasNext()) { checkinRecursively(iter.nextNode(), vm); } } @FixFor( "MODE-1234" ) public void testShouldFindCheckedOutNodesByQuerying() throws Exception { Session session1 = getHelper().getSuperuserSession(); VersionManager vm = session1.getWorkspace().getVersionManager(); // Check in all nodes that are still checked out ... Node root = session1.getRootNode(); checkinRecursively(root, vm); // Create node structure Node area = root.addNode("tmp2", "nt:unstructured"); Node outer = area.addNode("outerFolder"); Node inner = outer.addNode("innerFolder"); Node file = inner.addNode("testFile.dat"); file.setProperty("jcr:mimeType", "text/plain"); file.setProperty("jcr:data", "Original content"); session1.save(); file.addMixin("mix:versionable"); session1.save(); assertTrue(isVersionable(vm, file)); Version v1 = vm.checkin(file.getPath()); assertThat(v1, is(notNullValue())); // Register a listener so that we only have to wait until the change is made ... CountDownListener listener = registerCountDownListener(session1, file.getPath(), 1); // Query for versionables ... queryForCheckedOutVersionables(session1, 0, false); // Now check out a node ... vm.checkout(file.getPath()); // and wait a bit ... listener.await(2, TimeUnit.SECONDS); listener.unregister(); // And re-query ... queryForCheckedOutVersionables(session1, 1, false); // Register a listener so that we only have to wait until the change is made ... listener = registerCountDownListener(session1, file.getPath(), 1); // Check it back in ... vm.checkin(file.getPath()); // and wait a bit ... listener.await(2, TimeUnit.SECONDS); listener.unregister(); // And re-query ... queryForCheckedOutVersionables(session1, 0, false); } protected int queryForCheckedOutVersionables( Session session, int expected, boolean print ) throws RepositoryException { String queryStr = "SELECT * FROM [mix:versionable] AS versionable WHERE versionable.[jcr:isCheckedOut] = true"; QueryResult result = query(session, queryStr); int actual = (int)result.getRows().getSize(); if (print) { System.out.println("Result = \n" + result); } assertThat(actual, is(expected)); return actual; } protected QueryResult query( Session session, String queryStr ) throws RepositoryException { Query query = session.getWorkspace().getQueryManager().createQuery(queryStr, Query.JCR_SQL2); QueryResult result = query.execute(); return result; } protected CountDownListener registerCountDownListener( Session session, String path, int counts ) throws RepositoryException { CountDownListener listener = new CountDownListener(session, counts); session.getWorkspace().getObservationManager() .addEventListener(listener, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, path, true, null, null, false); return listener; } protected static class CountDownListener implements EventListener { final CountDownLatch latch; final Session session; public CountDownListener( Session session, int counts ) { this.session = session; this.latch = new CountDownLatch(counts); } @Override public void onEvent( EventIterator events ) { while (events.hasNext()) { try { latch.countDown(); events.nextEvent(); } catch (Throwable e) { latch.countDown(); fail(e.getMessage()); } } } public void await( int time, TimeUnit unit ) { try { latch.await(time, unit); } catch (InterruptedException e) { fail(e.getMessage()); } } public void unregister() throws RepositoryException { this.session.getWorkspace().getObservationManager().removeEventListener(this); } } @SuppressWarnings( "unchecked" ) @FixFor( "MODE-1050" ) public void testShouldSuccessfullyRegisterChildAndParentSameTypes() throws RepositoryException { Session session = getHelper().getSuperuserSession(); session.getWorkspace().getNamespaceRegistry().registerNamespace("mx", "urn:test"); final NodeTypeManager nodeTypeMgr = session.getWorkspace().getNodeTypeManager(); final NodeTypeTemplate nodeType = nodeTypeMgr.createNodeTypeTemplate(); nodeType.setName("mx:type"); nodeType.setDeclaredSuperTypeNames(new String[] {"nt:folder"}); final NodeDefinitionTemplate subTypes = nodeTypeMgr.createNodeDefinitionTemplate(); subTypes.setRequiredPrimaryTypeNames(new String[] {"mx:type"}); nodeType.getNodeDefinitionTemplates().add(subTypes); nodeTypeMgr.registerNodeType(nodeType, false); } /** * Restore a shareable node that automatically removes an existing shareable node (6.13.19). In this case the particular * shared node is removed but its descendants continue to exist below the remaining members of the shared set. * <p> * NOTE: This is a copy of the {@link ShareableNodeTest#testRestoreRemoveExisting()} method in the TCK test, useful for * debugging. * </p> * * @throws Exception * @see ShareableNodeTest#testRestoreRemoveExisting() */ @FixFor( "MODE-1458" ) @SuppressWarnings( "deprecation" ) public void testRestoreRemoveExisting() throws Exception { testRootNode = testRootNode.addNode("restoreTest"); int eventTypes = Event.NODE_ADDED | Event.NODE_MOVED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; EventListener listener = new EventListener() { @Override public void onEvent( EventIterator events ) { while (events.hasNext()) { Event event = events.nextEvent(); if (print) { System.out.println(event); System.out.flush(); } } } }; testRootNode.getSession().getWorkspace().getObservationManager() .addEventListener(listener, eventTypes, testRootNode.getPath(), true, null, null, false); // setup parent nodes and first child Node a1 = testRootNode.addNode("a1"); Node a2 = testRootNode.addNode("a2"); Node b1 = a1.addNode("b1"); testRootNode.getSession().save(); // make b1 shareable ensureMixinType(b1, mixShareable); b1.save(); // clone Workspace workspace = b1.getSession().getWorkspace(); workspace.clone(workspace.getName(), b1.getPath(), a2.getPath() + "/b2", false); // add child c b1.addNode("c"); b1.save(); // make a2 versionable ensureMixinType(a2, mixVersionable); a2.save(); // check in version and check out again Version v = a2.checkin(); a2.checkout(); print("After checkout: "); printSubgraph(testRootNode); // delete b2 and save a2.getNode("b2").remove(); a2.save(); print("After remove/save: "); printSubgraph(testRootNode); // verify shareable set contains one elements only Node[] shared = getSharedSet(b1); assertEquals(1, shared.length); // restore version and remove existing (i.e. b1) a2.restore(v, true); print("After restore: "); printSubgraph(testRootNode); // verify shareable set contains still one element shared = getSharedSet(a2.getNode("b2")); assertEquals(1, shared.length); // verify child c still exists Node[] children = toArray(a2.getNode("b2").getNodes()); assertEquals(1, children.length); } @FixFor( "MODE-1469" ) public void testShouldReturnAllNodesUnderRootUsingPathCriteria() throws Exception { Session session = testRootNode.getSession(); List<String> rootNodesPaths = new ArrayList<String>(); rootNodesPaths.add(session.getRootNode().getPath()); for (NodeIterator it = session.getRootNode().getNodes(); it.hasNext();) { rootNodesPaths.add(it.nextNode().getPath()); } String query = "SELECT * FROM [nt:base] WHERE [jcr:path] LIKE '/%' AND NOT [jcr:path] LIKE '/%/%'"; javax.jcr.query.QueryManager queryManager = session.getWorkspace().getQueryManager(); QueryResult result = queryManager.createQuery(query, Query.JCR_SQL2).execute(); for (NodeIterator it = result.getNodes(); it.hasNext();) { Node queryNode = it.nextNode(); String queryNodePath = queryNode.getPath(); assertTrue(queryNodePath + " not part of the original nodes list", rootNodesPaths.remove(queryNodePath)); } if (!rootNodesPaths.isEmpty()) { StringBuilder nodesNotFound = new StringBuilder("Nodes: "); for (String nodePath : rootNodesPaths) { nodesNotFound.append(nodePath).append(" "); } nodesNotFound.append(" not found by query"); fail(nodesNotFound.toString()); } } @FixFor( "MODE-1883" ) public void testShouldRestoreDeletedNode() throws Exception { session = getHelper().getReadWriteSession(); Node node = getTestRoot(session).addNode("checkInTest", "nt:folder"); session.save(); Node n = node.addNode("removeNode", "nt:folder"); n.addMixin("mix:versionable"); session.save(); VersionManager vm = session.getWorkspace().getVersionManager(); // Version 1.0 vm.checkout(n.getPath()); Version version1 = vm.checkin(n.getPath()); assertEquals("1.0", version1.getName()); n.remove(); session.save(); try { vm.restore(version1, false); fail("An exception should be thrown, because a removed not cannot be restored"); } catch (VersionException e) { // expected } } /** * Return a shared set as an array of nodes. * * @param n node * @return array of nodes in shared set * @throws RepositoryException */ private static Node[] getSharedSet( Node n ) throws RepositoryException { return toArray(n.getSharedSet()); } /** * Return an array of nodes given a <code>NodeIterator</code>. * * @param iter node iterator * @return node array */ private static Node[] toArray( NodeIterator iter ) { List<Node> list = new ArrayList<Node>(); while (iter.hasNext()) { list.add(iter.nextNode()); } Node[] result = new Node[list.size()]; list.toArray(result); return result; } }