/* * 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.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NodeType; 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.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.jcr.api.JcrConstants; import org.modeshape.jcr.api.nodetype.NodeTypeManager; /** * Unit test for versioning behaviour (see JSR_285#15) */ public class JcrVersioningTest extends SingleUseAbstractTest { private VersionManager versionManager; @Override @Before public void beforeEach() throws Exception { super.beforeEach(); versionManager = session.getWorkspace().getVersionManager(); } @Test @FixFor( "MODE-1302" ) public void shouldHaveVersionHistoryWhenRefreshIsCalled() throws Exception { Node outerNode = session.getRootNode().addNode("outerFolder"); Node innerNode = outerNode.addNode("innerFolder"); Node fileNode = innerNode.addNode("testFile.dat"); fileNode.setProperty("jcr:mimeType", "text/plain"); fileNode.setProperty("jcr:data", "Original content"); session.save(); assertFalse(hasVersionHistory(fileNode)); fileNode.addMixin("mix:versionable"); // Version history is not created until save assertFalse(hasVersionHistory(fileNode)); session.refresh(true); // Version history is not created until save assertFalse(hasVersionHistory(fileNode)); session.save(); assertTrue(hasVersionHistory(fileNode)); } @Test @FixFor( "MODE-1401" ) public void shouldAllowAddingUnderCheckedInNodeNewChildNodeWithOpvOfIgnore() throws Exception { registerNodeTypes(session, "cnd/versioning.cnd"); // Set up parent node and check it in ... Node parent = session.getRootNode().addNode("versionableNode", "ver:versionable"); parent.setProperty("versionProp", "v"); parent.setProperty("copyProp", "c"); parent.setProperty("ignoreProp", "i"); session.save(); versionManager.checkin(parent.getPath()); // Try to add child with OPV of ignore ... Node child = parent.addNode("nonVersionedIgnoredChild", "ver:nonVersionableChild"); child.setProperty("copyProp", "c"); child.setProperty("ignoreProp", "i"); session.save(); // Try to update the properties on the child with OPV of 'ignore' child.setProperty("copyProp", "c2"); child.setProperty("ignoreProp", "i2"); session.save(); // Try to add versionable child with OPV of ignore ... Node child2 = parent.addNode("versionedIgnoredChild", "ver:versionableChild"); child2.setProperty("copyProp", "c"); child2.setProperty("ignoreProp", "i"); session.save(); // Try to update the properties on the child with OPV of 'ignore' child2.setProperty("copyProp", "c2"); child2.setProperty("ignoreProp", "i2"); session.save(); } @Test @FixFor( "MODE-1401" ) public void shouldNotAllowAddingUnderCheckedInNodeNewChildNodeWithOpvOfSomethingOtherThanIgnore() throws Exception { registerNodeTypes(session, "cnd/versioning.cnd"); // Set up parent node and check it in ... Node parent = session.getRootNode().addNode("versionableNode", "ver:versionable"); parent.setProperty("versionProp", "v"); parent.setProperty("copyProp", "c"); parent.setProperty("ignoreProp", "i"); session.save(); versionManager.checkin(parent.getPath()); // Try to add versionable child with OPV of not ignore ... try { parent.addNode("versionedChild", "ver:versionableChild"); fail("should have failed"); } catch (VersionException e) { // expected } // Try to add non-versionable child with OPV of not ignore ... try { parent.addNode("nonVersionedChild", "ver:nonVersionableChild"); fail("should have failed"); } catch (VersionException e) { // expected } } @Test @FixFor( "MODE-1401" ) public void shouldAllowRemovingFromCheckedInNodeExistingChildNodeWithOpvOfIgnore() throws Exception { registerNodeTypes(session, "cnd/versioning.cnd"); // Set up parent node and check it in ... Node parent = session.getRootNode().addNode("versionableNode", "ver:versionable"); parent.setProperty("versionProp", "v"); parent.setProperty("copyProp", "c"); parent.setProperty("ignoreProp", "i"); Node child1 = parent.addNode("nonVersionedIgnoredChild", "ver:nonVersionableChild"); child1.setProperty("copyProp", "c"); child1.setProperty("ignoreProp", "i"); Node child2 = parent.addNode("versionedIgnoredChild", "ver:versionableChild"); child2.setProperty("copyProp", "c"); child2.setProperty("ignoreProp", "i"); session.save(); versionManager.checkin(parent.getPath()); // Should be able to change the properties on the ignored children child1.setProperty("copyProp", "c2"); child1.setProperty("ignoreProp", "i2"); child2.setProperty("copyProp", "c2"); child2.setProperty("ignoreProp", "i2"); session.save(); // Try to remove the two child nodes that have an OPV of 'ignore' ... child1.remove(); child2.remove(); session.save(); // Should be able to change the ignored properties on the checked-in parent ... parent.setProperty("ignoreProp", "i"); // Should not be able to set any non-ignored properties on the checked in parent ... try { parent.setProperty("copyProp", "c2"); fail("not allowed"); } catch (VersionException e) { // expected } try { parent.setProperty("versionProp", "v2"); fail("not allowed"); } catch (VersionException e) { // expected } } @Test @FixFor( "MODE-1401" ) public void shouldNotAllowRemovingFromCheckedInNodeExistingChildNodeWithOpvOfSomethingOtherThanIgnore() throws Exception { registerNodeTypes(session, "cnd/versioning.cnd"); // Set up parent node and check it in ... Node parent = session.getRootNode().addNode("versionableNode", "ver:versionable"); parent.setProperty("versionProp", "v"); parent.setProperty("copyProp", "c"); parent.setProperty("ignoreProp", "i"); Node child1 = parent.addNode("nonVersionedChild", "ver:nonVersionableChild"); child1.setProperty("copyProp", "c"); child1.setProperty("ignoreProp", "i"); Node child2 = parent.addNode("versionedChild", "ver:versionableChild"); child2.setProperty("copyProp", "c"); child2.setProperty("ignoreProp", "i"); session.save(); versionManager.checkin(parent.getPath()); versionManager.checkin(child2.getPath()); // Should not be able to set any non-ignored properties on the checked in parent ... try { parent.setProperty("copyProp", "c2"); fail("not allowed"); } catch (VersionException e) { // expected } try { parent.setProperty("versionProp", "v2"); fail("not allowed"); } catch (VersionException e) { // expected } // Should not be able to set any non-ignored properties on the non-ignored children ... try { child2.setProperty("copyProp", "c2"); fail("not allowed"); } catch (VersionException e) { // expected } try { child1.setProperty("copyProp", "c2"); fail("not allowed"); } catch (VersionException e) { // expected } // Check out the versionable child node, and we should be able to edit it ... versionManager.checkout(child2.getPath()); child2.setProperty("copyProp", "c3"); session.save(); versionManager.checkin(child2.getPath()); // But we still cannot edit a property on the nonVersionable child node when the parent is still checked in ... try { child1.setProperty("copyProp", "c2"); fail("not allowed"); } catch (VersionException e) { // expected } // Check out the parent ... versionManager.checkout(parent.getPath()); // Now we can change the properties on the non-versionable children ... child1.setProperty("copyProp", "c2"); session.save(); // And even remove it ... child1.remove(); session.save(); // Check in the parent ... versionManager.checkin(parent.getPath()); // and we cannot remove the child versionable node ... try { child2.remove(); fail("not allowed"); } catch (VersionException e) { // expected } // But once the parent is checked out ... versionManager.checkout(parent.getPath()); // We can remove the versionable child that is checked in (!), since the parent is checked out ... // See Section 15.2.2: // "Note that remove of a read-only node is possible, as long as its parent is not read-only, // since removal is an alteration of the parent node." assertThat(versionManager.isCheckedOut(child2.getPath()), is(false)); child2.remove(); session.save(); } @FixFor( "MODE-1624" ) @Test public void shouldAllowRemovingVersionFromVersionHistory() throws Exception { print = false; Node outerNode = session.getRootNode().addNode("outerFolder"); Node innerNode = outerNode.addNode("innerFolder"); Node fileNode = innerNode.addNode("testFile.dat"); fileNode.setProperty("jcr:mimeType", "text/plain"); fileNode.setProperty("jcr:data", "Original content"); session.save(); fileNode.addMixin("mix:versionable"); session.save(); // Make several changes ... String path = fileNode.getPath(); for (int i = 2; i != 7; ++i) { versionManager.checkout(path); fileNode.setProperty("jcr:data", "Original content " + i); session.save(); versionManager.checkin(path); } // Get the version history ... VersionHistory history = versionManager.getVersionHistory(path); if (print) System.out.println("Before: \n" + history); assertThat(history, is(notNullValue())); assertThat(history.getAllLinearVersions().getSize(), is(6L)); // Get the versions ... VersionIterator iter = history.getAllLinearVersions(); Version v1 = iter.nextVersion(); Version v2 = iter.nextVersion(); Version v3 = iter.nextVersion(); Version v4 = iter.nextVersion(); Version v5 = iter.nextVersion(); Version v6 = iter.nextVersion(); assertThat(iter.hasNext(), is(false)); String versionName = v3.getName(); assertThat(v1, is(notNullValue())); assertThat(v2, is(notNullValue())); assertThat(v3, is(notNullValue())); assertThat(v4, is(notNullValue())); assertThat(v5, is(notNullValue())); assertThat(v6, is(notNullValue())); // Remove the 3rd version (that is, i=3) ... history.removeVersion(versionName); if (print) System.out.println("After (same history used to remove): \n" + history); assertThat(history.getAllLinearVersions().getSize(), is(5L)); // Get the versions using the history node we already have ... iter = history.getAllLinearVersions(); Version v1a = iter.nextVersion(); Version v2a = iter.nextVersion(); Version v4a = iter.nextVersion(); Version v5a = iter.nextVersion(); Version v6a = iter.nextVersion(); assertThat(iter.hasNext(), is(false)); assertThat(v1a.getName(), is(v1.getName())); assertThat(v2a.getName(), is(v2.getName())); assertThat(v4a.getName(), is(v4.getName())); assertThat(v5a.getName(), is(v5.getName())); assertThat(v6a.getName(), is(v6.getName())); // Get the versions using a fresh history node ... VersionHistory history2 = versionManager.getVersionHistory(path); if (print) System.out.println("After (fresh history): \n" + history2); assertThat(history.getAllLinearVersions().getSize(), is(5L)); iter = history2.getAllLinearVersions(); Version v1b = iter.nextVersion(); Version v2b = iter.nextVersion(); Version v4b = iter.nextVersion(); Version v5b = iter.nextVersion(); Version v6b = iter.nextVersion(); assertThat(iter.hasNext(), is(false)); assertThat(v1b.getName(), is(v1.getName())); assertThat(v2b.getName(), is(v2.getName())); assertThat(v4b.getName(), is(v4.getName())); assertThat(v5b.getName(), is(v5.getName())); assertThat(v6b.getName(), is(v6.getName())); } @FixFor( "MODE-1624" ) @Test public void shouldAllowRemovingVersionFromVersionHistoryByRemovingVersionNode() throws Exception { print = false; Node outerNode = session.getRootNode().addNode("outerFolder"); Node innerNode = outerNode.addNode("innerFolder"); Node fileNode = innerNode.addNode("testFile.dat"); fileNode.setProperty("jcr:mimeType", "text/plain"); fileNode.setProperty("jcr:data", "Original content"); session.save(); fileNode.addMixin("mix:versionable"); session.save(); // Make several changes ... String path = fileNode.getPath(); for (int i = 2; i != 7; ++i) { versionManager.checkout(path); fileNode.setProperty("jcr:data", "Original content " + i); session.save(); versionManager.checkin(path); } // Get the version history ... VersionHistory history = versionManager.getVersionHistory(path); if (print) System.out.println("Before: \n" + history); assertThat(history, is(notNullValue())); assertThat(history.getAllLinearVersions().getSize(), is(6L)); // Get the versions ... VersionIterator iter = history.getAllLinearVersions(); Version v1 = iter.nextVersion(); Version v2 = iter.nextVersion(); Version v3 = iter.nextVersion(); Version v4 = iter.nextVersion(); Version v5 = iter.nextVersion(); Version v6 = iter.nextVersion(); assertThat(iter.hasNext(), is(false)); assertThat(v1, is(notNullValue())); assertThat(v2, is(notNullValue())); assertThat(v3, is(notNullValue())); assertThat(v4, is(notNullValue())); assertThat(v5, is(notNullValue())); assertThat(v6, is(notNullValue())); // Remove the 3rd version (that is, i=3) ... // history.removeVersion(versionName); try { v3.remove(); fail("Should not allow removing a protected node"); } catch (ConstraintViolationException e) { // expected } } @SuppressWarnings( "deprecation" ) @Test @FixFor( "MODE-1775" ) public void shouldFindVersionNodeByIdentifierAndByUuid() throws Exception { registerNodeTypes(session, "cnd/versioning.cnd"); // Set up parent node and check it in ... Node parent = session.getRootNode().addNode("versionableNode", "ver:versionable"); parent.setProperty("versionProp", "v"); parent.setProperty("copyProp", "c"); parent.setProperty("ignoreProp", "i"); session.save(); Version version = versionManager.checkin(parent.getPath()); // Now look for the version node by identifier, using the different ways to get an identifier ... assertThat(session.getNodeByIdentifier(version.getIdentifier()), is((Node)version)); assertThat(session.getNodeByIdentifier(version.getProperty("jcr:uuid").getString()), is((Node)version)); assertThat(session.getNodeByUUID(version.getProperty("jcr:uuid").getString()), is((Node)version)); } @Test @FixFor( "MODE-1887" ) public void shouldMergeWorkspaces() throws Exception { registerNodeTypes(session, "cnd/mode-1887.cnd"); Session session = repository.login("default"); session.getWorkspace().createWorkspace("original"); session = repository.login("original"); Node root = session.getRootNode(); Node parent1 = root.addNode("parent1", "Parent"); session.save(); Node child1 = parent1.addNode("child1", "Child"); assertThat(child1, is(notNullValue())); session.save(); session.getWorkspace().createWorkspace("clone", "original"); session = repository.login("clone"); Node child2 = session.getNode("/parent1").addNode("child2", "Child"); assertThat(child2, is(notNullValue())); session.save(); session = repository.login("original"); VersionManager vm = session.getWorkspace().getVersionManager(); NodeIterator ni = vm.merge("/", "clone", true); session.save(); if (print) { System.out.println("Failed nodes------------------"); while (ni.hasNext()) { System.out.println(ni.nextNode()); } } session.getNode("/parent1/child2"); } @Test @FixFor( "MODE-1912" ) public void shouldRemoveMixVersionablePropertiesWhenRemovingMixin() throws Exception { Node node = session.getRootNode().addNode("testNode"); node.addMixin(JcrMixLexicon.VERSIONABLE.getString()); session.save(); // mix:referenceable assertNotNull(node.getProperty(JcrLexicon.UUID.getString())); // mix:simpleVersionable assertTrue(node.getProperty(JcrLexicon.IS_CHECKED_OUT.getString()).getBoolean()); // mix:versionable assertNotNull(node.getProperty(JcrLexicon.BASE_VERSION.getString())); assertNotNull(node.getProperty(JcrLexicon.VERSION_HISTORY.getString())); assertNotNull(node.getProperty(JcrLexicon.PREDECESSORS.getString())); node.removeMixin(JcrMixLexicon.VERSIONABLE.getString()); session.save(); // mix:referenceable assertPropertyIsAbsent(node, JcrLexicon.UUID.getString()); // mix:simpleVersionable assertPropertyIsAbsent(node, JcrLexicon.IS_CHECKED_OUT.getString()); // mix:versionable assertPropertyIsAbsent(node, JcrLexicon.VERSION_HISTORY.getString()); assertPropertyIsAbsent(node, JcrLexicon.BASE_VERSION.getString()); assertPropertyIsAbsent(node, JcrLexicon.PREDECESSORS.getString()); } @Test @FixFor( "MODE-1912" ) public void shouldRelinkVersionablePropertiesWhenRemovingAndReaddingMixVersionable() throws Exception { JcrVersionManager jcrVersionManager = (JcrVersionManager)versionManager; Node node = session.getRootNode().addNode("testNode"); node.addMixin(JcrMixLexicon.VERSIONABLE.getString()); session.save(); // create a new version jcrVersionManager.checkin("/testNode"); jcrVersionManager.checkout("/testNode"); jcrVersionManager.checkin("/testNode"); JcrVersionHistoryNode originalVersionHistory = jcrVersionManager.getVersionHistory("/testNode"); Version originalBaseVersion = jcrVersionManager.getBaseVersion("/testNode"); // remove the mixin jcrVersionManager.checkout("/testNode"); node.removeMixin(JcrMixLexicon.VERSIONABLE.getString()); session.save(); // re-create the mixin and check the previous version history & versionable properties have been relinked. node.addMixin(JcrMixLexicon.VERSIONABLE.getString()); session.save(); // mix:referenceable assertNotNull(node.getProperty(JcrLexicon.UUID.getString())); // mix:simpleVersionable assertTrue(node.getProperty(JcrLexicon.IS_CHECKED_OUT.getString()).getBoolean()); // mix:versionable assertNotNull(node.getProperty(JcrLexicon.BASE_VERSION.getString())); assertNotNull(node.getProperty(JcrLexicon.VERSION_HISTORY.getString())); assertNotNull(node.getProperty(JcrLexicon.PREDECESSORS.getString())); JcrVersionHistoryNode versionHistory = jcrVersionManager.getVersionHistory("/testNode"); Version baseVersion = jcrVersionManager.getBaseVersion("/testNode"); // check the actual assertEquals(originalVersionHistory.key(), versionHistory.key()); assertEquals(originalBaseVersion.getCreated(), baseVersion.getCreated()); assertEquals(originalBaseVersion.getPath(), baseVersion.getPath()); } @Test @FixFor( "MODE-2002" ) public void shouldMergeEventualSuccessorVersions() throws Exception { // Create a "/record", make it versionable and check it in session.getRootNode().addNode("record").addMixin("mix:versionable"); session.save(); VersionManager versionManager = session.getWorkspace().getVersionManager(); versionManager.checkin("/record"); // Clone QA version of data session.getWorkspace().createWorkspace("QA", session.getWorkspace().getName()); Session sessionQa = repository.login("QA"); VersionManager versionManagerQa = sessionQa.getWorkspace().getVersionManager(); // Change QA node first time versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("111", "111"); sessionQa.save(); versionManagerQa.checkin("/record"); // Change QA node second time versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("222", "222"); sessionQa.save(); versionManagerQa.checkin("/record"); // Checks before merge // Check basic node - should not have any properties assertFalse(session.getNode("/record").hasProperty("111")); assertFalse(session.getNode("/record").hasProperty("222")); // Check QA node - should have properties 111=111 and 222=222 assertTrue(sessionQa.getNode("/record").hasProperty("111")); assertTrue(sessionQa.getNode("/record").hasProperty("222")); assertEquals("111", sessionQa.getNode("/record").getProperty("111").getString()); assertEquals("222", sessionQa.getNode("/record").getProperty("222").getString()); // Merge versionManager.merge("/record", sessionQa.getWorkspace().getName(), true); // Checks after merge - basic node should have properties 111=111 and 222=222 assertTrue(session.getNode("/record").hasProperty("111")); assertTrue(session.getNode("/record").hasProperty("222")); assertEquals("111", session.getNode("/record").getProperty("111").getString()); assertEquals("222", session.getNode("/record").getProperty("222").getString()); sessionQa.logout(); } @Test @FixFor( "MODE-2005" ) public void shouldSetMergeFailedPropertyIfNodeIsCheckedIn() throws Exception { // Create a record, make it versionable and check it in session.getRootNode().addNode("record").addMixin("mix:versionable"); session.save(); VersionManager versionManager = session.getWorkspace().getVersionManager(); versionManager.checkin("/record"); // Clone QA version of data session.getWorkspace().createWorkspace("QA", session.getWorkspace().getName()); Session sessionQa = repository.login("QA"); try { VersionManager versionManagerQa = sessionQa.getWorkspace().getVersionManager(); // Change QA node first time versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("111", "111"); sessionQa.save(); versionManagerQa.checkin("/record"); // Change QA node second time, store version versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("222", "222"); sessionQa.save(); Version offendingVersion = versionManagerQa.checkin("/record"); // Change original node one time to make versions in this workspace and other workspace be on // divergent branches, causing merge() to fail versionManager.checkout("/record"); session.getNode("/record").setProperty("333", "333"); session.save(); versionManager.checkin("/record"); // Try to merge NodeIterator nodeIterator = versionManager.merge("/record", sessionQa.getWorkspace().getName(), true); assertTrue(nodeIterator.hasNext()); while (nodeIterator.hasNext()) { Node record = nodeIterator.nextNode(); Version mergeFailedVersion = (Version)session.getNodeByIdentifier(record.getProperty("jcr:mergeFailed") .getValues()[0].getString()); assertEquals(offendingVersion.getIdentifier(), mergeFailedVersion.getIdentifier()); versionManager.cancelMerge("/record", mergeFailedVersion); assertFalse(record.hasProperty("jcr:mergeFailed")); } } finally { sessionQa.logout(); } } @Test @FixFor( "MODE-2005" ) public void shouldSetMergeFailedPropertyIfNodeIsCheckedIn2() throws Exception { // Create a record, make it versionable and check it in session.getRootNode().addNode("record").addMixin("mix:versionable"); session.save(); VersionManager versionManager = session.getWorkspace().getVersionManager(); versionManager.checkin("/record"); // Clone QA version of data session.getWorkspace().createWorkspace("QA", session.getWorkspace().getName()); Session sessionQa = repository.login("QA"); try { VersionManager versionManagerQa = sessionQa.getWorkspace().getVersionManager(); // Change QA node first time, store version versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("111", "111"); sessionQa.save(); Version offendingVersion1 = versionManagerQa.checkin("/record"); // Change original node one time to make versions in this workspace and other workspace be on // divergent branches, causing merge() to fail versionManager.checkout("/record"); session.getNode("/record").setProperty("333", "333"); session.save(); versionManager.checkin("/record"); // Try to merge with offendingVersion1 // This should create a new jcr:mergeFailed property versionManager.merge("/record", sessionQa.getWorkspace().getName(), true); // Change QA node second time, store version versionManagerQa.checkout("/record"); sessionQa.getNode("/record").setProperty("222", "222"); sessionQa.save(); Version offendingVersion2 = versionManagerQa.checkin("/record"); // Try to merge with offendingVersion2 // This should add to existing jcr:mergeFailed property NodeIterator nodeIterator = versionManager.merge("/record", sessionQa.getWorkspace().getName(), true); assertTrue(nodeIterator.hasNext()); while (nodeIterator.hasNext()) { Node record = nodeIterator.nextNode(); Version mergeFailedVersion1 = (Version)session.getNodeByIdentifier(record.getProperty("jcr:mergeFailed") .getValues()[0].getString()); assertEquals(offendingVersion1.getIdentifier(), mergeFailedVersion1.getIdentifier()); Version mergeFailedVersion2 = (Version)session.getNodeByIdentifier(record.getProperty("jcr:mergeFailed") .getValues()[1].getString()); assertEquals(offendingVersion2.getIdentifier(), mergeFailedVersion2.getIdentifier()); versionManager.cancelMerge("/record", mergeFailedVersion1); versionManager.cancelMerge("/record", mergeFailedVersion2); assertFalse(record.hasProperty("jcr:mergeFailed")); } } finally { sessionQa.logout(); } } @Test @FixFor( "MODE-2006" ) public void shouldMergeNodesWithSameNamesById() throws Exception { // Create a parent and two children, make them versionable and check them in Node parent = session.getRootNode().addNode("parent"); parent.addMixin("mix:versionable"); Node child1 = parent.addNode("child"); child1.addMixin("mix:versionable"); child1.setProperty("myproperty", "CHANGEME"); Node child2 = parent.addNode("child"); child2.addMixin("mix:versionable"); child2.setProperty("myproperty", "222"); session.save(); VersionManager versionManager = session.getWorkspace().getVersionManager(); versionManager.checkin(parent.getPath()); versionManager.checkin(child1.getPath()); versionManager.checkin(child2.getPath()); // Clone QA version of data session.getWorkspace().createWorkspace("QA", session.getWorkspace().getName()); Session sessionQa = repository.login("QA"); VersionManager versionManagerQa = sessionQa.getWorkspace().getVersionManager(); try { // QA: change child1's property Node qaParent = sessionQa.getNode("/parent"); versionManagerQa.checkout(qaParent.getPath()); Node qaChild1 = sessionQa.getNodeByIdentifier(child1.getIdentifier()); versionManagerQa.checkout(qaChild1.getPath()); qaChild1.setProperty("myproperty", "111"); // QA: Add three new children with same name/path to parent Node qaChild3 = qaParent.addNode("child"); qaChild3.addMixin("mix:versionable"); qaChild3.setProperty("myproperty", "333"); Node qaChild4 = qaParent.addNode("child"); qaChild4.addMixin("mix:versionable"); qaChild4.setProperty("myproperty", "444"); Node qaChild5 = qaParent.addNode("child"); qaChild5.addMixin("mix:versionable"); qaChild5.setProperty("myproperty", "555"); // QA: drop child2 Node qaChild2 = sessionQa.getNodeByIdentifier(child2.getIdentifier()); qaChild2.remove(); sessionQa.save(); Version qaChild1Version = versionManagerQa.checkin(qaChild1.getPath()); Version qaChild3Version = versionManagerQa.checkin(qaChild3.getPath()); Version qaChild4Version = versionManagerQa.checkin(qaChild4.getPath()); Version qaChild5Version = versionManagerQa.checkin(qaChild5.getPath()); Version qaParentVersion = versionManagerQa.checkin(qaParent.getPath()); // Merge NodeIterator nodeIterator = versionManager.merge("/parent", sessionQa.getWorkspace().getName(), true); parent = session.getNodeByIdentifier(parent.getIdentifier()); child1 = session.getNodeByIdentifier(qaChild1.getIdentifier()); try { session.getNodeByIdentifier(child2.getIdentifier()); // this one got removed fail("Deleted child should not be retrieved"); } catch (ItemNotFoundException e) { //continue } Node child3 = session.getNodeByIdentifier(qaChild3.getIdentifier()); Node child4 = session.getNodeByIdentifier(qaChild4.getIdentifier()); Node child5 = session.getNodeByIdentifier(qaChild5.getIdentifier()); // Run some checks using default workspace's versionManager and session assertFalse(nodeIterator.hasNext()); assertEquals(qaParentVersion.getIdentifier(), versionManager.getBaseVersion(qaParent.getPath()).getIdentifier()); assertEquals(qaChild1Version.getIdentifier(), versionManager.getBaseVersion(child1.getPath()).getIdentifier()); assertEquals(qaChild3Version.getIdentifier(), versionManager.getBaseVersion(child3.getPath()).getIdentifier()); assertEquals(qaChild4Version.getIdentifier(), versionManager.getBaseVersion(child4.getPath()).getIdentifier()); assertEquals(qaChild5Version.getIdentifier(), versionManager.getBaseVersion(child5.getPath()).getIdentifier()); // Check that parent no longer has child2 for (NodeIterator childIterator = parent.getNodes(); childIterator.hasNext();) { Node child = childIterator.nextNode(); assertFalse(child.getIdentifier().equals(child2.getIdentifier())); } assertEquals(1, child1.getIndex()); assertEquals(2, child3.getIndex()); assertEquals(3, child4.getIndex()); assertEquals(4, child5.getIndex()); assertEquals("111", child1.getProperty("myproperty").getString()); assertEquals("333", child3.getProperty("myproperty").getString()); assertEquals("444", child4.getProperty("myproperty").getString()); assertEquals("555", child5.getProperty("myproperty").getString()); } finally { sessionQa.logout(); } } @Test @FixFor( "MODE-2034" ) public void shouldRestoreNodeWithVersionedChildrenUsingCheckpoints() throws Exception { // Create a parent and two children, make them versionable and check them in Node parent = session.getRootNode().addNode("parent"); parent.addMixin("mix:versionable"); Node child1 = parent.addNode("child1"); child1.addMixin("mix:versionable"); child1.setProperty("myproperty", "v1_1"); Node child2 = parent.addNode("child2"); child2.addMixin("mix:versionable"); child2.setProperty("myproperty", "v2_1"); session.save(); Version v1 = versionManager.checkpoint(parent.getPath()); assertEquals("1.0", v1.getName()); child1.setProperty("myproperty", "v1_2"); child2.setProperty("myproperty", "v2_2"); session.save(); Version v2 = versionManager.checkpoint(parent.getPath()); assertEquals("1.1", v2.getName()); versionManager.restore(parent.getPath(), "1.0", true); parent = session.getNode("/parent"); assertEquals("v1_2", parent.getNode("child1").getProperty("myproperty").getString()); assertEquals("v2_2", parent.getNode("child2").getProperty("myproperty").getString()); } @Test @FixFor( "MODE-2034" ) public void shouldRestoreNodeWithoutVersionedChildrenUsingCheckpoints() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node node = session.getRootNode().addNode("revert", "jj:page"); node.addNode("child1", "jj:content"); node.addNode("child2", "jj:content"); session.save(); //create two versions versionManager.checkpoint(node.getPath()); versionManager.checkpoint(node.getPath()); versionManager.restore(node.getPath(), "1.0", true); } @Test @FixFor( "MODE-2055" ) public void shouldNotReturnTheVersionHistoryNode() throws Exception { Node node = session.getRootNode().addNode("outerFolder"); node.setProperty("jcr:mimeType", "text/plain"); node.addMixin("mix:versionable"); session.save(); versionManager.checkpoint("/outerFolder"); node.remove(); session.save(); try { session.getNodeByIdentifier(node.getIdentifier()); fail("Removed versionable node should not be retrieved "); } catch (ItemNotFoundException e) { //expected } } @Test @FixFor( "MODE-2089" ) public void shouldKeepOrderWhenRestoring() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node parent = session.getRootNode().addNode("parent", "jj:page"); parent.addNode("child1", "jj:content"); parent.addNode("child2", "jj:content"); parent.addNode("child3", "jj:content"); parent.addNode("child4", "jj:content"); session.save(); versionManager.checkpoint(parent.getPath()); parent = session.getNode("/parent"); parent.orderBefore("child4", "child3"); parent.orderBefore("child3", "child2"); parent.orderBefore("child2", "child1"); session.save(); Version version = versionManager.getBaseVersion(parent.getPath()); versionManager.restore(version, true); parent = session.getNode("/parent"); List<String> children = new ArrayList<String>(); NodeIterator nodeIterator = parent.getNodes(); while (nodeIterator.hasNext()) { children.add(nodeIterator.nextNode().getPath()); } assertEquals(Arrays.asList("/parent/child1", "/parent/child2", "/parent/child3", "/parent/child4"), children); } @Test @FixFor( "MODE-2096" ) public void shouldRestoreAfterRemovingAndReaddingNodesWithSameName() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node parent = session.getRootNode().addNode("parent", "jj:page"); Node child = parent.addNode("child", "jj:content"); child.addNode("descendant", "jj:content"); child.addNode("descendant", "jj:content"); session.save(); versionManager.checkpoint(parent.getPath()); child = session.getNode("/parent/child"); NodeIterator childNodes = child.getNodes(); while (childNodes.hasNext()) { childNodes.nextNode().remove(); } child.addNode("descendant", "jj:content"); child.addNode("descendant", "jj:content"); session.save(); Version version = versionManager.getBaseVersion(parent.getPath()); versionManager.restore(version, true); parent = session.getNode("/parent"); assertEquals(Arrays.asList("/parent/child", "/parent/child/descendant", "/parent/child/descendant[2]"), allChildrenPaths(parent)); } @Test @FixFor( "MODE-2104" ) public void shouldRestoreMovedNode() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node parent = session.getRootNode().addNode("parent", "jj:page"); parent.addNode("child", "jj:content"); session.save(); versionManager.checkpoint(parent.getPath()); parent.addNode("_context", "jj:context"); // move to a location under a new parent session.move("/parent/child", "/parent/_context/child"); session.save(); // restore versionManager.restore(parent.getPath(), "1.0", true); //the default OPV is COPY, so we expect the restore to have removed _context assertNoNode("/parent/_context"); assertNode("/parent/child"); } @Test @FixFor( "MODE-2096" ) public void shouldRestoreToMultipleVersionsWhenEachVersionHasDifferentChild() throws Exception { registerNodeTypes("cnd/jj.cnd"); // Add a page node with one child then make a version 1.0 Node node = session.getRootNode().addNode("page", "jj:page"); node.addNode("child1", "jj:content"); session.save(); versionManager.checkpoint(node.getPath()); // add second child then make version 1.1 node.addNode("child2", "jj:content"); session.save(); versionManager.checkpoint(node.getPath()); // restore to 1.0 versionManager.restore(node.getPath(), "1.0", true); assertNode("/page/child1"); assertNoNode("/page/child2"); // then restore to 1.1, it will throw the NullPointException versionManager.restore(node.getPath(), "1.1", true); assertNode("/page/child1"); assertNode("/page/child2"); } @Test @FixFor( "MODE-2112" ) public void shouldRestoreMovedNode2() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node parent = session.getRootNode().addNode("parent", "jj:page"); parent.addNode("_context", "jj:context"); parent.addNode("child", "jj:content"); session.save(); versionManager.checkpoint(parent.getPath()); // move to a location under a new parent session.move("/parent/child", "/parent/_context/child"); session.save(); // restore versionManager.restore(parent.getPath(), "1.0", true); assertNoNode("/parent/_context/child"); assertNode("/parent/child"); } @Test @FixFor( "MODE-2112" ) public void shouldRestoreMovedNode3() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node parent = session.getRootNode().addNode("parent", "jj:page"); parent.addNode("child1", "jj:content"); parent.addNode("_context", "jj:context"); parent.addNode("child2", "jj:content"); session.save(); versionManager.checkpoint(parent.getPath()); // move to a location under a new parent session.move("/parent/child1", "/parent/_context/child1"); session.save(); // restore versionManager.restore(parent.getPath(), "1.0", true); assertNoNode("/parent/_context/child1"); assertNoNode("/parent/_context/child2"); assertNode("/parent/child1"); assertNode("/parent/child2"); assertNode("/parent/_context"); } @Test @FixFor( "MODE-2153" ) public void shouldRemoveVersionInVersionGraphWithBranches1() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node node = session.getRootNode().addNode("node", "jj:page"); session.save(); Version v1 = versionManager.checkpoint(node.getPath()); assertEquals("1.0", v1.getName()); assertEquals(2, versionManager.getVersionHistory("/node").getAllVersions().getSize()); Version v2 = versionManager.checkin("/node"); assertEquals("1.1", v2.getName()); assertEquals(3, versionManager.getVersionHistory("/node").getAllVersions().getSize()); versionManager.restore(v1, true); assertEquals(3, versionManager.getVersionHistory("/node").getAllVersions().getSize()); Version baseVersion = versionManager.checkpoint(node.getPath()); assertEquals("1.0", baseVersion.getName()); assertEquals(3, versionManager.getVersionHistory("/node").getAllVersions().getSize()); Version v4 = versionManager.checkin("/node"); assertEquals("1.1.0", v4.getName()); assertEquals(4, versionManager.getVersionHistory("/node").getAllVersions().getSize()); versionManager.getVersionHistory("/node").removeVersion(v1.getName()); } @Test @FixFor( "MODE-2152" ) public void shouldRemoveVersionInVersionGraphWithBranches2() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node node = session.getRootNode().addNode("node", "jj:page"); session.save(); String nodePath = node.getPath(); //create two versions versionManager.checkpoint(nodePath); // version 1.0 versionManager.checkpoint(nodePath); // version 1.1 versionManager.restore(nodePath, "1.0", true); versionManager.checkout(nodePath); versionManager.checkpoint(nodePath); // version 1.1.0 versionManager.getVersionHistory(nodePath).removeVersion("1.1"); } @Test @FixFor( "MODE-1839" ) public void shouldRemoveEmptyVersionHistories() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node node1 = session.getRootNode().addNode("node1", "jj:page"); node1.addNode("node11", "jj:page"); node1.addNode("node12", "jj:content"); session.save(); ((org.modeshape.jcr.api.version.VersionManager) versionManager).remove("/node1"); try { versionManager.getVersionHistory("/node1"); fail("Version history has not been removed"); } catch (PathNotFoundException e) { //expected } try { versionManager.getVersionHistory("/node1/node11"); fail("Version history has not been removed"); } catch (PathNotFoundException e) { //expected } } @Test @FixFor( "MODE-1839" ) public void shouldNotRemoveNonEmptyVersionHistories() throws Exception { registerNodeTypes("cnd/jj.cnd"); Node node1 = session.getRootNode().addNode("node1", "jj:page"); node1.addNode("node11", "jj:page"); session.save(); versionManager.checkpoint("/node1/node11"); try { ((org.modeshape.jcr.api.version.VersionManager) versionManager).remove("/node1"); fail("Should not allow removal of non empty version history"); } catch (UnsupportedRepositoryOperationException e) { //expected } } @Test @FixFor( "MODE-2560" ) public void shouldCreateSeparateVersionHistoryForCopiedNode() throws Exception { VersionManager versionManager = session.getWorkspace().getVersionManager(); // create original node Node node = session.getNode("/").addNode("uploads"); Node originalNode = node.addNode("originalNode", NodeType.NT_FILE); originalNode.addMixin(NodeType.MIX_VERSIONABLE); originalNode.addMixin(NodeType.MIX_TITLE); Node contentNode = originalNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE); contentNode.setProperty(JcrConstants.JCR_DATA, session.getValueFactory().createBinary(new ByteArrayInputStream("test".getBytes()))); session.save(); // update original node versionManager.checkout(originalNode.getPath()); originalNode.setProperty(Property.JCR_TITLE, "originalNode"); session.save(); versionManager.checkin(originalNode.getPath()); VersionHistory originalHistory = versionManager.getVersionHistory(originalNode.getPath()); Version originalBaseVersion = versionManager.getBaseVersion(originalNode.getPath()); // copy original node session.getWorkspace().copy(originalNode.getPath(), "/uploads/copiedNode"); // check that the copied node is checked out and has its own version history Node copiedNode = session.getNode("/uploads/copiedNode"); assertTrue(versionManager.isCheckedOut(copiedNode.getPath())); VersionHistory copiedHistory = versionManager.getVersionHistory(copiedNode.getPath()); assertNotEquals(originalHistory.getVersionableIdentifier(), copiedHistory.getVersionableIdentifier()); Version copiedBaseVersion = versionManager.getBaseVersion(copiedNode.getPath()); assertNotEquals(originalBaseVersion.getPath(), copiedBaseVersion.getPath()); // delete all versions for the original node VersionIterator it = originalHistory.getAllVersions(); while (it.hasNext()) { Version version = it.nextVersion(); originalHistory.removeVersion(version.getName()); } // delete all versions for the copied node it = copiedHistory.getAllVersions(); while (it.hasNext()) { Version version = it.nextVersion(); copiedHistory.removeVersion(version.getName()); } // delete original node originalNode.remove(); session.save(); // delete the copied node copiedNode.remove(); session.save(); } @Test @FixFor( "MODE-2609" ) public void shouldReadChildVersionHistoryProperty() throws Exception { // Create a versionable parent node and add a single version. Node parentNode = session.getRootNode().addNode("parent"); parentNode.addMixin("mix:versionable"); session.save(); VersionManager vm = parentNode.getSession().getWorkspace().getVersionManager(); vm.checkout(parentNode.getPath()); vm.checkin(parentNode.getPath()); vm.checkout(parentNode.getPath()); // Create a versionable child node for a previously created parent and add a single version. Node childNode = parentNode.addNode("child"); childNode.addMixin("mix:versionable"); session.save(); vm.checkout(childNode.getPath()); vm.checkin(childNode.getPath()); vm.checkin(parentNode.getPath()); // Obtain the base version of the parent node. Version baseParentVersion = vm.getBaseVersion(parentNode.getPath()); Node frozenNode = baseParentVersion.getFrozenNode(); NodeIterator nodeIterator = frozenNode.getNodes(); while (nodeIterator.hasNext()) { Node child = nodeIterator.nextNode(); assertThat(child.getProperty("jcr:primaryType").getString(), is("nt:versionedChild")); Property property = child.getProperty("jcr:childVersionHistory"); VersionHistory versionHistory = (VersionHistory) property.getNode(); // Do something about obtained version history... assertNotNull(versionHistory); } } @Test @FixFor("MODE-2676") public void shouldRemoveVersionableNodeWithMultiversionParent() throws Exception { Node root = session.getRootNode(); Node parentNode = root.addNode("parent"); parentNode.addMixin("mix:versionable"); session.save(); Node testNode = parentNode.addNode("testNew"); testNode.addMixin("mix:versionable"); session.save(); // multi-version the parent and child rename(session, testNode, "testUpdate1", parentNode ); rename(session, testNode, "testUpdate2", parentNode ); String testNodePath = testNode.getPath(); VersionHistory history = versionManager.getVersionHistory(testNodePath); // remove all versions in the version history removeAllVersions(history); versionManager.checkout(parentNode.getPath()); // clean up everything ((org.modeshape.jcr.api.version.VersionManager) versionManager).remove(testNode.getPath()); versionManager.checkin(parentNode.getPath()); session.save(); } private List<String> allChildrenPaths( Node root ) throws Exception { List<String> paths = new ArrayList<String>(); NodeIterator nodeIterator = root.getNodes(); while (nodeIterator.hasNext()) { Node child = nodeIterator.nextNode(); paths.add(child.getPath()); paths.addAll(allChildrenPaths(child)); } return paths; } private void assertPropertyIsAbsent( Node node, String propertyName ) throws Exception { try { node.getProperty(propertyName); fail("Property: " + propertyName + " was expected to be missing on node:" + node); } catch (PathNotFoundException e) { // expected } } private void registerNodeTypes( Session session, String resourcePathToCnd ) throws Exception { NodeTypeManager nodeTypes = (NodeTypeManager)session.getWorkspace().getNodeTypeManager(); URL url = getClass().getClassLoader().getResource(resourcePathToCnd); assertThat(url, is(notNullValue())); nodeTypes.registerNodeTypes(url, true); } private boolean hasVersionHistory( Node node ) throws RepositoryException { try { VersionHistory history = versionManager.getVersionHistory(node.getPath()); assertNotNull(history); return true; } catch (UnsupportedRepositoryOperationException e) { return false; } } private void removeAllVersions(VersionHistory history) throws RepositoryException { // delete all versions of node (to delete references to // files) VersionIterator it = history.getAllVersions(); while (it.hasNext()) { Version version = it.nextVersion(); history.removeVersion(version.getName()); } } private void rename(Session session,Node testNode, String newName, Node parentNode) throws Exception { versionManager.checkout(parentNode.getPath()); versionManager.checkout(testNode.getPath()); testNode.setProperty(Property.JCR_NAME, newName); session.save(); versionManager.checkin(testNode.getPath()); versionManager.checkin(parentNode.getPath()); } }