/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.core; import org.apache.jackrabbit.test.AbstractJCRTest; import javax.jcr.Repository; import javax.jcr.Node; import javax.jcr.ItemNotFoundException; import javax.jcr.Session; import javax.jcr.RepositoryException; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.InvalidItemStateException; import javax.jcr.version.VersionException; import javax.jcr.version.Version; import javax.jcr.version.VersionManager; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.nodetype.NodeType; import javax.transaction.UserTransaction; import javax.transaction.RollbackException; import java.util.StringTokenizer; /** * <code>XATest</code> contains the test cases for the methods * inside {@link XASessionImpl}. */ public class XATest extends AbstractJCRTest { /** * Other superuser. */ private Session otherSuperuser; /** * {@inheritDoc} */ protected void setUp() throws Exception { super.setUp(); otherSuperuser = getHelper().getSuperuserSession(); // clean testroot on second workspace Session s2 = getHelper().getSuperuserSession(workspaceName); try { Node root = s2.getRootNode(); if (root.hasNode(testPath)) { // clean test root Node testRootNode = root.getNode(testPath); for (NodeIterator children = testRootNode.getNodes(); children.hasNext();) { children.nextNode().remove(); } } else { // create nodes to testPath StringTokenizer names = new StringTokenizer(testPath, "/"); Node currentNode = root; while (names.hasMoreTokens()) { String name = names.nextToken(); if (currentNode.hasNode(name)) { currentNode = currentNode.getNode(name); } else { currentNode = currentNode.addNode(name, testNodeType); } } } root.save(); } finally { s2.logout(); } } /** * {@inheritDoc} */ protected void tearDown() throws Exception { if (otherSuperuser != null) { otherSuperuser.logout(); otherSuperuser = null; } super.tearDown(); } /** * @see junit.framework.TestCase#runTest() * * Make sure that tested repository supports transactions */ protected void runTest() throws Throwable { if (isSupported(Repository.OPTION_TRANSACTIONS_SUPPORTED)) { super.runTest(); } } /** * Test case for * <a href="https://issues.apache.org/jira/browse/JCR-2796">JCR-2796</a>. */ public void testRestore() throws Exception { Session session = getHelper().getSuperuserSession(); try { VersionManager vm = session.getWorkspace().getVersionManager(); // make sure that 'testNode' does not exist at the beginning // of the test while (session.nodeExists("/testNode")) { session.getNode("/testNode").remove(); session.save(); } // 1) create 'testNode' that has a child and a grandchild Node node = session.getRootNode().addNode("testNode"); node.addMixin(NodeType.MIX_VERSIONABLE); node.addNode("child").addNode("grandchild"); session.save(); // 2) check in 'testNode' and give a version-label Version version = vm.checkin(node.getPath()); vm.getVersionHistory(node.getPath()).addVersionLabel( version.getName(), "testLabel", false); // 3) do restore by label UserTransaction utx = new UserTransactionImpl(session); utx.begin(); vm.restoreByLabel(node.getPath(), "testLabel", true); utx.commit(); // 4) try to get the grandchild (fails if the restoring has // been done within a transaction) assertTrue(node.hasNode("child/grandchild")); } finally { session.logout(); } } /** * Test case for * <a href="https://issues.apache.org/jira/browse/JCR-2712">JCR-2712</a>. */ public void testVersioningRollbackWithoutPrepare() throws Exception { Session session = getHelper().getSuperuserSession(); try { if (session.getRootNode().hasNode("testNode")) { session.getRootNode().getNode("testNode").remove(); session.save(); } UserTransaction utx; for (int i = 0; i < 50; i++) { utx = new UserTransactionImpl(session); utx.begin(); session.getRootNode().addNode("testNode").addMixin( NodeType.MIX_VERSIONABLE); session.save(); utx.rollback(); } } finally { session.logout(); } } /** * Add a node inside a transaction and commit changes. Make sure * node exists for other sessions only after commit. * @throws Exception */ public void testAddNodeCommit() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixReferenceable); testRootNode.save(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("New node not visible after save()"); } // assertion: node does not exist in other session Session otherSuperuser = getHelper().getSuperuserSession(); try { otherSuperuser.getNodeByUUID(n.getUUID()); fail("Uncommitted node visible for other session"); } catch (ItemNotFoundException e) { /* expected */ } // commit utx.commit(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in this session"); } // assertion: node also exists in other session try { otherSuperuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in other session"); } // logout otherSuperuser.logout(); } /** * Set a property inside a transaction and commit changes. Make sure * property exists for other sessions only after commit. * @throws Exception */ public void testSetPropertyCommit() throws Exception { // prerequisite: non-existing property if (testRootNode.hasProperty(propertyName1)) { testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // set property and save testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // assertion: property exists in this session assertTrue(testRootNode.hasProperty(propertyName1)); // assertion: property does not exist in other session Session otherSuperuser = getHelper().getSuperuserSession(); Node otherRootNode = otherSuperuser.getRootNode().getNode(testPath); assertFalse(otherRootNode.hasProperty(propertyName1)); // commit utx.commit(); // assertion: property exists in this session assertTrue(testRootNode.hasProperty(propertyName1)); // assertion: property also exists in other session assertTrue(otherRootNode.hasProperty(propertyName1)); // logout otherSuperuser.logout(); } /** * @throws Exception */ public void testAddAndSetProperty() throws Exception { // prerequisite: non-existing property if (testRootNode.hasProperty(propertyName1)) { testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'add' property and save testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // 'modify' property and save testRootNode.setProperty(propertyName1, "1"); testRootNode.save(); // commit utx.commit(); // check property value Session otherSuperuser = getHelper().getSuperuserSession(); Node n = (Node) otherSuperuser.getItem(testRootNode.getPath()); assertEquals(n.getProperty(propertyName1).getString(), "1"); otherSuperuser.logout(); } /** * @throws Exception */ public void testPropertyIsNew() throws Exception { // prerequisite: non-existing property if (testRootNode.hasProperty(propertyName1)) { testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'add' property and save testRootNode.setProperty(propertyName1, "0"); assertTrue("New property must be new.", testRootNode.getProperty(propertyName1).isNew()); testRootNode.save(); assertFalse("Saved property must not be new.", testRootNode.getProperty(propertyName1).isNew()); // commit utx.commit(); } /** * @throws Exception */ public void testNewNodeIsLocked() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); testRootNode.save(); assertFalse("New node must not be locked.", n.isLocked()); // commit utx.commit(); } /** * @throws Exception */ public void testPropertyIsModified() throws Exception { // prerequisite: existing property testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'add' property and save testRootNode.setProperty(propertyName1, "1"); assertTrue("Unsaved property must be modified.", testRootNode.getProperty(propertyName1).isModified()); testRootNode.save(); assertFalse("Saved property must not be modified.", testRootNode.getProperty(propertyName1).isModified()); // commit utx.commit(); } /** * @throws Exception */ public void testDeleteAndAddProperty() throws Exception { // prerequisite: existing property testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'delete' property and save testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); // 'add' property and save testRootNode.setProperty(propertyName1, "1"); testRootNode.save(); // commit utx.commit(); // check property value Session otherSuperuser = getHelper().getSuperuserSession(); Node n = (Node) otherSuperuser.getItem(testRootNode.getPath()); assertEquals(n.getProperty(propertyName1).getString(), "1"); otherSuperuser.logout(); } /** * @throws Exception */ public void testModifyAndDeleteProperty() throws Exception { // prerequisite: existing property testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'modify' property and save testRootNode.setProperty(propertyName1, "1"); testRootNode.save(); // 'delete' property and save testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); // commit utx.commit(); // check property value Session otherSuperuser = getHelper().getSuperuserSession(); Node n = (Node) otherSuperuser.getItem(testRootNode.getPath()); assertFalse("Property must be deleted.", n.hasProperty(propertyName1)); otherSuperuser.logout(); } /** * @throws Exception */ public void testAddAndDeleteProperty() throws Exception { // prerequisite: non-existing property if (testRootNode.hasProperty(propertyName1)) { testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // 'add' property and save testRootNode.setProperty(propertyName1, "1"); testRootNode.save(); // 'delete' property and save testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); // commit utx.commit(); // check property value Session otherSuperuser = getHelper().getSuperuserSession(); Node n = (Node) otherSuperuser.getItem(testRootNode.getPath()); assertFalse("Property must be deleted.", n.hasProperty(propertyName1)); otherSuperuser.logout(); } /** * Add a node inside a transaction and rollback changes. * @throws Exception */ public void testAddNodeRollback() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixReferenceable); testRootNode.save(); // assertion: node exists in this session String uuid = n.getUUID(); try { superuser.getNodeByUUID(uuid); } catch (ItemNotFoundException e) { fail("New node not visible after save()"); } // rollback utx.rollback(); // assertion: node does not exist in this session try { superuser.getNodeByUUID(uuid); fail("Node still visible after rollback()"); } catch (ItemNotFoundException e) { /* expected */ } } /** * Set a property inside a transaction and rollback changes. * @throws Exception */ public void testSetPropertyRollback() throws Exception { // prerequisite: non-existing property if (testRootNode.hasProperty(propertyName1)) { testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // set property and save testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); // assertion: property exists in this session assertTrue(testRootNode.hasProperty(propertyName1)); // rollback utx.rollback(); // assertion: property does not exist in this session assertFalse(testRootNode.hasProperty(propertyName1)); } /** * Remove a node inside a transaction and rollback changes. Check * that the node reference may again be used after having rolled * back changes. * @throws Exception */ public void testRemoveNodeRollback() throws Exception { // prerequisite: existing node Node n1 = testRootNode.addNode(nodeName1, testNodeType); n1.addMixin(mixReferenceable); testRootNode.save(); String uuid = n1.getUUID(); // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // remove node and save Node n2 = superuser.getNodeByUUID(uuid); n2.remove(); testRootNode.save(); // assertion: node no longer exists try { superuser.getNodeByUUID(uuid); fail("Removed node still exists after save()"); } catch (ItemNotFoundException e) { /* expected */ } // rollback utx.rollback(); // assertion: node exists again try { superuser.getNodeByUUID(uuid); } catch (ItemNotFoundException e) { fail("Removed node not visible after rollback()"); } } /** * Remove a property inside a transaction and rollback changes. * Check that the property reference may again be used after * having rolled back changes. * @throws Exception */ public void testRemovePropertyRollback() throws Exception { // prerequisite: existing property if (!testRootNode.hasProperty(propertyName1)) { testRootNode.setProperty(propertyName1, "0"); testRootNode.save(); } // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // remove property and save testRootNode.getProperty(propertyName1).remove(); testRootNode.save(); // assertion: property no longer exists assertFalse(testRootNode.hasProperty(propertyName1)); // rollback utx.rollback(); // assertion: property exists and reference valid assertTrue(testRootNode.hasProperty(propertyName1)); } /** * Add reference to some node in one session while removing * the node in another. * @throws Exception */ public void testAddReference() throws Exception { // add two nodes, second one referenceable Node n1 = testRootNode.addNode(nodeName1); Node n2 = testRootNode.addNode(nodeName2); n2.addMixin(mixReferenceable); testRootNode.save(); // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // start transaction utx.begin(); // add reference and save n1.setProperty(propertyName1, n2); testRootNode.save(); // remove referenced node in other session Session otherSuperuser = getHelper().getSuperuserSession(); Node otherRootNode = otherSuperuser.getRootNode().getNode(testPath); otherSuperuser.getNodeByUUID(n2.getUUID()).remove(); otherRootNode.save(); // assertion: commit must fail since integrity violated try { utx.commit(); fail("Commit succeeds with violated integrity"); } catch (RollbackException e) { /* expected */ } // logout otherSuperuser.logout(); } /** * Checks if getReferences() reflects an added reference property that has * been saved but not yet committed. * <p> * Spec say: * <p> * <i>Some level 2 implementations may only return properties that have been * saved (in a transactional setting this includes both those properties * that have been saved but not yet committed, as well as properties that * have been committed). Other level 2 implementations may additionally * return properties that have been added within the current Session but are * not yet saved.</i> * <p> * Jackrabbit does not support the latter, but at least has to support the * first. */ public void testGetReferencesAddedRef() throws Exception { // create one referenceable node Node target = testRootNode.addNode(nodeName1); target.addMixin(mixReferenceable); // second node, which will later reference the target node Node n = testRootNode.addNode(nodeName2); testRootNode.save(); UserTransactionImpl tx = new UserTransactionImpl(superuser); tx.begin(); try { // create reference n.setProperty(propertyName1, target); testRootNode.save(); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", target.getReferences().hasNext()); } finally { tx.rollback(); } } /** * Checks if getReferences() reflects a removed reference property that has * been saved but not yet committed. */ public void testGetReferencesRemovedRef() throws Exception { // create one referenceable node Node target = testRootNode.addNode(nodeName1); target.addMixin(mixReferenceable); // second node, which reference the target node Node n = testRootNode.addNode(nodeName2); // create reference n.setProperty(propertyName1, target); testRootNode.save(); UserTransactionImpl tx = new UserTransactionImpl(superuser); tx.begin(); try { n.getProperty(propertyName1).remove(); testRootNode.save(); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", !target.getReferences().hasNext()); } finally { tx.rollback(); } } /** * Checks if getReferences() reflects a modified reference property that has * been saved but not yet committed. */ public void testGetReferencesModifiedRef() throws Exception { // create two referenceable node Node target1 = testRootNode.addNode(nodeName1); target1.addMixin(mixReferenceable); // second node, which reference the target1 node Node target2 = testRootNode.addNode(nodeName2); target2.addMixin(mixReferenceable); Node n = testRootNode.addNode(nodeName3); // create reference n.setProperty(propertyName1, target1); testRootNode.save(); UserTransactionImpl tx = new UserTransactionImpl(superuser); tx.begin(); try { // change reference n.setProperty(propertyName1, target2); testRootNode.save(); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", !target1.getReferences().hasNext()); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", target2.getReferences().hasNext()); } finally { tx.rollback(); } } /** * Checks if getReferences() reflects a modified reference property that has * been saved but not yet committed. The old value is a reference, while * the new value is not. */ public void testGetReferencesModifiedRefOldValueReferenceable() throws Exception { // create one referenceable node Node target = testRootNode.addNode(nodeName1); target.addMixin(mixReferenceable); Node n = testRootNode.addNode(nodeName2); // create reference n.setProperty(propertyName1, target); testRootNode.save(); UserTransactionImpl tx = new UserTransactionImpl(superuser); tx.begin(); try { // change reference to a string value n.setProperty(propertyName1, "foo"); testRootNode.save(); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", !target.getReferences().hasNext()); } finally { tx.rollback(); } } /** * Checks if getReferences() reflects a modified reference property that has * been saved but not yet committed. The new value is a reference, while * the old value wasn't. */ public void testGetReferencesModifiedRefNewValueReferenceable() throws Exception { // create one referenceable node Node target = testRootNode.addNode(nodeName1); target.addMixin(mixReferenceable); Node n = testRootNode.addNode(nodeName2); // create string property n.setProperty(propertyName1, "foo"); testRootNode.save(); UserTransactionImpl tx = new UserTransactionImpl(superuser); tx.begin(); try { // change string into a reference n.setProperty(propertyName1, target); testRootNode.save(); assertTrue("Node.getReferences() must reflect references that have " + "been saved but not yet committed", target.getReferences().hasNext()); } finally { tx.rollback(); } } //--------------------------------------------------------------< locking > /** * Test locking a node in one session. Verify that node is not locked * in other session until commit. * @throws Exception */ public void testLockCommit() throws Exception { Session other = getHelper().getSuperuserSession(); try { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // reference node in second session Node nOther = other.getNodeByUUID(n.getUUID()); // verify node is not locked in either session assertFalse("Node not locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); n.lock(false, true); // verify node is locked in first session only assertTrue("Node locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); // commit in first session utx.commit(); // verify node is locked in both sessions assertTrue("Node locked in session 1", n.isLocked()); assertTrue("Node locked in session 2", nOther.isLocked()); } finally { // logout other.logout(); } } /** * Test locking and unlocking behavior in transaction * @throws Exception */ public void testLockUnlockCommit() throws Exception { Session other = getHelper().getSuperuserSession(); try { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // reference node in second session Node nOther = other.getNodeByUUID(n.getUUID()); // verify node is not locked in either session assertFalse("Node not locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); n.lock(false, true); // verify node is locked in first session only assertTrue("Node locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); n.unlock(); // commit in first session utx.commit(); // verify node is locked in both sessions assertFalse("Node locked in session 1", n.isLocked()); assertFalse("Node locked in session 2", nOther.isLocked()); } finally { // logout other.logout(); } } /** * Test locking and unlocking behavior in transaction * (see JCR-2356) * @throws Exception */ public void testCreateLockUnlockInDifferentTransactions() throws Exception { // create new node and lock it UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // add node that is both lockable and referenceable, save Node rootNode = superuser.getRootNode(); Node n = rootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); rootNode.save(); String uuid = n.getUUID(); // commit utx.commit(); // start new Transaction and try to add lock token utx = new UserTransactionImpl(superuser); utx.begin(); n = superuser.getNodeByUUID(uuid); // lock this new node Lock lock = n.lock(true, false); // verify node is locked assertTrue("Node not locked", n.isLocked()); String lockToken = lock.getLockToken(); // assert: session must get a non-null lock token assertNotNull("session must get a non-null lock token", lockToken); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(superuser, lockToken)); n.save(); superuser.removeLockToken(lockToken); String nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken)); // commit utx.commit(); nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken)); // start new Transaction and try to unlock utx = new UserTransactionImpl(superuser); utx.begin(); n = superuser.getNodeByUUID(uuid); // verify node is locked assertTrue("Node not locked", n.isLocked()); // assert: session must not hold lock token assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken)); superuser.addLockToken(lockToken); // assert: session must not hold lock token assertTrue("session must hold lock token", containsLockToken(superuser, lockToken)); n.unlock(); // commit utx.commit(); } /** * Test locking a node in one session. Verify that node is not locked * in session after rollback. * @throws Exception */ public void testLockRollback() throws Exception { Session other = getHelper().getSuperuserSession(); try { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // reference node in second session Node nOther = other.getNodeByUUID(n.getUUID()); // verify node is not locked in either session assertFalse("Node not locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); n.lock(false, true); // verify node is locked in first session only assertTrue("Node locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); assertFalse("Node not locked in session 2", nOther.hasProperty(jcrLockOwner)); // rollback in first session utx.rollback(); // verify node is not locked in either session assertFalse("Node not locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); assertFalse("Node not locked in session 2", nOther.hasProperty(jcrlockIsDeep)); } finally { // logout other.logout(); } } /** * Test locking a node inside a transaction that has been locked in another * session, which leads to a failure when committing. * @throws Exception */ public void testLockTwice() throws Exception { Session other = getHelper().getSuperuserSession(); try { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // reference node in second session Node nOther = other.getNodeByUUID(n.getUUID()); // verify node is not locked in either session assertFalse("Node not locked in session 1", n.isLocked()); assertFalse("Node not locked in session 2", nOther.isLocked()); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); n.lock(false, true); // lock node in non-transactional session, too nOther.lock(false, true); // verify node is locked in both sessions assertTrue("Node locked in session 1", n.isLocked()); assertTrue("Node locked in session 2", nOther.isLocked()); assertTrue("Node locked in session 2", nOther.hasProperty(jcrLockOwner)); // assertion: commit must fail since node has already been locked try { utx.commit(); fail("Commit succeeds with double locking"); } catch (RollbackException e) { /* expected */ } // verify node is locked in both sessions assertTrue("Node locked in session 1", n.isLocked()); assertTrue("Node locked in session 2", nOther.isLocked()); assertTrue("Node locked in session 2", nOther.hasProperty(jcrlockIsDeep)); } finally { // logout other.logout(); } } /** * Test locking a new node inside a transaction. * @throws Exception */ public void testLockNewNode() throws Exception { // get user transaction object, start UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // lock this new node n.lock(false, true); assertTrue("Node locked in transaction", n.isLocked()); // commit utx.commit(); // Check if it is locked in other session Session other = getHelper().getSuperuserSession(); Node nOther = other.getNodeByUUID(n.getUUID()); assertTrue(nOther.isLocked()); // Check if it is also locked in other transaction Session other2 = getHelper().getSuperuserSession(); // start new Transaction and try to add locktoken utx = new UserTransactionImpl(other2); utx.begin(); Node nOther2 = other2.getNodeByUUID(n.getUUID()); assertTrue(nOther2.isLocked()); utx.commit(); other.logout(); other2.logout(); } /** * Test add and remove lock tokens in a transaction * @throws Exception */ public void testAddRemoveLockToken() throws Exception { // create new node and lock it UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // add node that is both lockable and referenceable, save Node rootNode = superuser.getRootNode(); Node n = rootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); rootNode.save(); String uuid = n.getUUID(); // lock this new node Lock lock = n.lock(true, false); String lockToken = lock.getLockToken(); // assert: session must get a non-null lock token assertNotNull("session must get a non-null lock token", lockToken); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(superuser, lockToken)); superuser.removeLockToken(lockToken); String nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); // commit utx.commit(); // refresh Lock Info lock = n.getLock(); nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); Session other = getHelper().getSuperuserSession(); try { // start new Transaction and try to add lock token utx = new UserTransactionImpl(other); utx.begin(); Node otherNode = other.getNodeByUUID(uuid); assertTrue("Node not locked", otherNode.isLocked()); try { otherNode.setProperty(propertyName1, "foo"); fail("Lock exception should be thrown"); } catch (LockException e) { // expected } // add lock token other.addLockToken(lockToken); // refresh Lock Info lock = otherNode.getLock(); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(other, lock.getLockToken())); otherNode.unlock(); assertFalse("Node is locked", otherNode.isLocked()); otherNode.setProperty(propertyName1, "foo"); other.save(); utx.commit(); } finally { other.logout(); } } /** * Test locking/unlocking a node inside a transaction which should be a * no-op. * @throws Exception */ public void testLockUnlock() throws Exception { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // verify node is not locked in this session assertFalse("Node not locked", n.isLocked()); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); n.lock(false, true); // verify node is locked assertTrue("Node locked", n.isLocked()); // unlock node n.unlock(); // commit utx.commit(); // verify node is not locked assertFalse("Node not locked", n.isLocked()); } /** * Test correct behaviour of {@link javax.jcr.lock.Lock} inside a * transaction. * @throws Exception */ public void testLockBehaviour() throws Exception { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); Lock lock = n.lock(false, true); // verify lock is live assertTrue("Lock live", lock.isLive()); // rollback utx.rollback(); // verify lock is not live anymore assertFalse("Lock not live", lock.isLive()); } /** * Test correct behaviour of {@link javax.jcr.lock.Lock} inside a * transaction. * @throws Exception */ public void testLockBehaviour2() throws Exception { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); Lock lock = n.lock(false, true); // get user transaction object, start UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // verify lock is live assertTrue("Lock live", lock.isLive()); // unlock n.unlock(); // verify lock is no longer live assertFalse("Lock not live", lock.isLive()); // rollback utx.rollback(); // verify lock is live again assertTrue("Lock live", lock.isLive()); } /** * Test correct behaviour of lock related properties within transaction. * * @throws Exception */ public void testLockProperties() throws Exception { Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); // get user transaction object, start and lock node UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); Lock lock = n.lock(false, true); // verify that the lock properties have been created and are neither // NEW nor MODIFIED. assertTrue(n.hasProperty(jcrLockOwner)); Property lockOwner = n.getProperty(jcrLockOwner); assertFalse(lockOwner.isNew()); assertFalse(lockOwner.isModified()); assertTrue(n.hasProperty(jcrlockIsDeep)); Property lockIsDeep = n.getProperty(jcrlockIsDeep); assertFalse(lockIsDeep.isNew()); assertFalse(lockIsDeep.isModified()); // rollback utx.rollback(); // verify that the lock properties have been removed again. assertFalse(n.hasProperty(jcrLockOwner)); try { lockOwner.getPath(); fail("jcr:lockIsDeep property must have been invalidated."); } catch (InvalidItemStateException e) { // success } assertFalse(n.hasProperty(jcrlockIsDeep)); try { lockIsDeep.getPath(); fail("jcr:lockIsDeep property must have been invalidated."); } catch (InvalidItemStateException e) { // success } } /** * Test correct behaviour of lock related properties within transaction. * * @throws Exception */ public void testLockProperties2() throws Exception { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); Lock lock = n.lock(false, true); try { // get user transaction object, start UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // verify that the lock properties are present assertTrue(n.hasProperty(jcrLockOwner)); assertTrue(n.hasProperty(jcrlockIsDeep)); // unlock n.unlock(); // verify that the lock properties have been removed. assertFalse(n.hasProperty(jcrLockOwner)); assertFalse(n.hasProperty(jcrlockIsDeep)); // rollback utx.rollback(); // verify lock is live again -> properties must be present assertTrue(n.hasProperty(jcrLockOwner)); assertTrue(n.hasProperty(jcrlockIsDeep)); } finally { n.unlock(); } } /** * Test visibility of lock properties by another session. * * @throws Exception */ public void testLockProperties3() throws Exception { // add node that is both lockable and referenceable, save Node n = testRootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); testRootNode.save(); Lock lock = n.lock(false, true); // get user transaction object, start UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // unlock n.unlock(); Node n2 = (Node) otherSuperuser.getItem(n.getPath()); assertTrue(n2.isLocked()); assertTrue(n2.hasProperty(jcrLockOwner)); assertTrue(n2.hasProperty(jcrlockIsDeep)); Lock lock2 = n2.getLock(); // complete transaction utx.commit(); // unlock must now be visible to other session n2.refresh(false); assertFalse(lock2.isLive()); assertFalse(n2.isLocked()); assertFalse(n2.hasProperty(jcrLockOwner)); assertFalse(n2.hasProperty(jcrlockIsDeep)); } //-----------------------------------------------------------< versioning > /** * Checkin inside tx should not be visible to other users. */ public void testCheckin() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixVersionable); testRootNode.save(); // reference node in other session Node nOther = otherSuperuser.getNodeByUUID(n.getUUID()); // start transaction utx.begin(); // checkin node n.checkin(); // assert: base versions must differ if (n.getBaseVersion().getName().equals(nOther.getBaseVersion().getName())) { fail("Base versions must differ"); } // assert: version must not be visible to other session try { nOther.getVersionHistory().getVersion(n.getBaseVersion().getName()); fail("Version must not be visible to other session."); } catch (VersionException e) { // expected. } // commit utx.commit(); // assert: base versions must be equal assertEquals("Base versions must be equal", n.getBaseVersion().getName(), nOther.getBaseVersion().getName()); } /** * Checkin from two sessions simultaneously should throw when committing. * @throws Exception */ public void testConflictingCheckin() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixVersionable); testRootNode.save(); // reference node in other session Node nOther = otherSuperuser.getNodeByUUID(n.getUUID()); // start transaction utx.begin(); // checkin node inside tx n.checkin(); // checkin node outside tx nOther.checkin(); // commit try { utx.commit(); fail("Commit failing with modified version history."); } catch (RollbackException e) { // expected } } /** * Test removed version gets invalid for other users on commit. */ public void testRemoveVersion() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixVersionable); testRootNode.save(); // reference node in other session Node nOther = otherSuperuser.getNodeByUUID(n.getUUID()); // create two versions, reference first version in other session n.checkin(); Version vOther = nOther.getBaseVersion(); n.checkout(); n.checkin(); // start transaction utx.begin(); // remove version and commit n.getVersionHistory().removeVersion(vOther.getName()); // commit utx.commit(); // assert: version has become invalid try { vOther.getPredecessors(); fail("Removed version still operational."); } catch (RepositoryException e) { // expected } } /** * Tests a couple of checkin/restore/remove operations on different * workspaces and different transactions. * * @throws Exception */ public void testXAVersionsThoroughly() throws Exception { Session s1 = superuser; Session s2 = getHelper().getSuperuserSession(workspaceName); // add node and save Node n1 = testRootNode.addNode(nodeName1, testNodeType); n1.addMixin(mixVersionable); testRootNode.save(); if (!s2.itemExists(testRootNode.getPath())) { s2.getRootNode().addNode(testRootNode.getName()); s2.save(); } s2.getWorkspace().clone(s1.getWorkspace().getName(), n1.getPath(), n1.getPath(), true); Node n2 = (Node) s2.getItem(n1.getPath()); //log.println("---------------------------------------"); String phase="init"; Version v1_1 = n1.getBaseVersion(); Version v2_1 = n2.getBaseVersion(); check(v1_1, phase, "jcr:rootVersion", 0); check(v2_1, phase, "jcr:rootVersion", 0); //log.println("--------checkout/checkin n1 (uncommitted)----------"); phase="checkin N1 uncomitted."; UserTransaction tx = new UserTransactionImpl(s1); tx.begin(); n1.checkout(); n1.checkin(); Version v1_2 = n1.getBaseVersion(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 0); check(v1_2, phase, "1.0", 0); //log.println("--------checkout/checkin n1 (comitted)----------"); phase="checkin N1 committed."; tx.commit(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 0); //log.println("--------restore n2 (uncommitted) ----------"); phase="restore N2 uncommitted."; tx = new UserTransactionImpl(s2); tx.begin(); n2.restore("1.0", false); Version v2_2 = n2.getBaseVersion(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 0); check(v2_2, phase, "1.0", 0); //log.println("--------restore n2 (comitted) ----------"); phase="restore N2 committed."; tx.commit(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 0); check(v2_2, phase, "1.0", 0); //log.println("--------checkout/checkin n2 (uncommitted) ----------"); phase="checkin N2 uncommitted."; tx = new UserTransactionImpl(s2); tx.begin(); n2.checkout(); n2.checkin(); Version v2_3 = n2.getBaseVersion(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 0); check(v2_2, phase, "1.0", 1); check(v2_3, phase, "1.1", 0); //log.println("--------checkout/checkin n2 (committed) ----------"); phase="checkin N2 committed."; tx.commit(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 1); check(v2_2, phase, "1.0", 1); check(v2_3, phase, "1.1", 0); //log.println("--------checkout/checkin n1 (uncommitted) ----------"); phase="checkin N1 uncommitted."; tx = new UserTransactionImpl(s1); tx.begin(); n1.checkout(); n1.checkin(); Version v1_3 = n1.getBaseVersion(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 2); check(v2_2, phase, "1.0", 1); check(v2_3, phase, "1.1", 0); check(v1_3, phase, "1.0.0", 0); //log.println("--------checkout/checkin n1 (committed) ----------"); phase="checkin N1 committed."; tx.commit(); check(v1_1, phase, "jcr:rootVersion", 1); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", 2); check(v2_2, phase, "1.0", 2); check(v2_3, phase, "1.1", 0); check(v1_3, phase, "1.0.0", 0); //log.println("--------remove n1-1.0 (uncommitted) ----------"); phase="remove N1 1.0 uncommitted."; tx = new UserTransactionImpl(s1); tx.begin(); n1.getVersionHistory().removeVersion("1.0"); check(v1_1, phase, "jcr:rootVersion", 2); check(v2_1, phase, "jcr:rootVersion", 1); check(v1_2, phase, "1.0", -1); check(v2_2, phase, "1.0", 2); check(v2_3, phase, "1.1", 0); check(v1_3, phase, "1.0.0", 0); //log.println("--------remove n1-1.0 (committed) ----------"); phase="remove N1 1.0 committed."; tx.commit(); check(v1_1, phase, "jcr:rootVersion", 2); check(v2_1, phase, "jcr:rootVersion", 2); check(v1_2, phase, "1.0", -1); check(v2_2, phase, "1.0", -1); check(v2_3, phase, "1.1", 0); check(v1_3, phase, "1.0.0", 0); //s1.logout(); s2.logout(); } /** * helper method for {@link #testXAVersionsThoroughly()} */ private void check(Version v, String phase, String name, int numSucc) { String vName; int vSucc = -1; try { vName = v.getName(); //vSucc = v.getProperty("jcr:successors").getValues().length; vSucc = v.getSuccessors().length; } catch (RepositoryException e) { // node is invalid after remove vName = name; } assertEquals(phase + " Version Name", name, vName); assertEquals(phase + " Num Successors", numSucc, vSucc); } /** * Test new version label becomes available to other sessions on commit. */ public void testSetVersionLabel() throws Exception { final String versionLabel = "myVersion"; // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixVersionable); testRootNode.save(); // reference node in other session Node nOther = otherSuperuser.getNodeByUUID(n.getUUID()); // create another version Version v = n.checkin(); // start transaction utx.begin(); // add new version label n.getVersionHistory().addVersionLabel(v.getName(), versionLabel, false); // assert: version label unknown in other session try { nOther.getVersionHistory().getVersionByLabel(versionLabel); fail("Version label visible outside tx."); } catch (VersionException e) { // expected } // commit utx.commit(); // assert: version label known in other session nOther.getVersionHistory().getVersionByLabel(versionLabel); } /** * Tests two different Threads for prepare and commit in a Transaction */ public void testDistributedThreadAccess() throws Exception { // get user transaction object UserTransaction utx = new UserTransactionImpl(superuser, true); //utx.setTransactionTimeout(50); // start transaction utx.begin(); // add node and save Node n = testRootNode.addNode(nodeName1, testNodeType); n.addMixin(mixReferenceable); testRootNode.save(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("New node not visible after save()"); } // commit utx.commit(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in this session"); } } /** * Tests two different Sessions in one Transaction * (see JCR-769) */ public void testTwoSessionsInOneTransaction() throws Exception { Session otherSuperuser = getHelper().getSuperuserSession(); // get user transaction object UserTransactionImpl utx = new UserTransactionImpl(superuser, true); utx.enlistXAResource(otherSuperuser); // start transaction utx.begin(); Node rootNode = superuser.getRootNode(); // add node and save Node n = rootNode.addNode(nodeName1, testNodeType); n.addMixin(mixReferenceable); rootNode.save(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("New node not visible after save()"); } // assertion: node does exist in other session try { otherSuperuser.getNodeByUUID(n.getUUID()); fail("Uncommitted node visible for other session"); } catch (ItemNotFoundException e) { /* expected */ } // add node with other session and save rootNode = otherSuperuser.getRootNode(); Node n1 = rootNode.addNode(nodeName2, testNodeType); n1.addMixin(mixReferenceable); rootNode.save(); // assertion: node exists in this session try { otherSuperuser.getNodeByUUID(n1.getUUID()); } catch (ItemNotFoundException e) { fail("New node not visible after save()"); } // assertion: node does exist in other session try { superuser.getNodeByUUID(n1.getUUID()); fail("Uncommitted node visible for other session"); } catch (ItemNotFoundException e) { /* expected */ } // commit utx.commit(); // assertion: node exists in this session try { superuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in this session"); } // assertion: node also exists in other session try { otherSuperuser.getNodeByUUID(n.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in the other session"); } // assertion: node1 exists in this session try { superuser.getNodeByUUID(n1.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in this session"); } // assertion: node1 also exists in other session try { otherSuperuser.getNodeByUUID(n1.getUUID()); } catch (ItemNotFoundException e) { fail("Committed node not visible in this session"); } // logout superuser.logout(); otherSuperuser.logout(); } /** * Test add lock token and remove node in in a transaction * (see JCR-2332) * @throws Exception */ public void testAddLockTokenRemoveNode() throws Exception { // create new node and lock it UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // add node that is both lockable and referenceable, save Node rootNode = superuser.getRootNode(); Node n = rootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); rootNode.save(); String uuid = n.getUUID(); // lock this new node Lock lock = n.lock(true, false); String lockToken = lock.getLockToken(); // assert: session must get a non-null lock token assertNotNull("session must get a non-null lock token", lockToken); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(superuser, lockToken)); superuser.removeLockToken(lockToken); String nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); // commit utx.commit(); // refresh Lock Info lock = n.getLock(); nlt = lock.getLockToken(); assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier", nlt == null || nlt.equals(lockToken)); Session other = getHelper().getSuperuserSession(); // start new Transaction and try to add lock token unlock the node and then remove it utx = new UserTransactionImpl(other); utx.begin(); Node otherNode = other.getNodeByUUID(uuid); assertTrue("Node not locked", otherNode.isLocked()); // add lock token other.addLockToken(lockToken); // refresh Lock Info lock = otherNode.getLock(); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(other, lock.getLockToken())); otherNode.unlock(); assertFalse("Node is locked", otherNode.isLocked()); otherNode.remove(); other.save(); utx.commit(); } /** * Tests if it is possible to add-lock a node and unlock-remove it with * a shared session in different transactions * (see JCR-2341) * @throws Exception */ public void testAddLockTokenRemoveNode2() throws Exception { // create new node and lock it UserTransaction utx = new UserTransactionImpl(superuser); utx.begin(); // add node that is both lockable and referenceable, save Node rootNode = superuser.getRootNode(); Node n = rootNode.addNode(nodeName1); n.addMixin(mixLockable); n.addMixin(mixReferenceable); rootNode.save(); String uuid = n.getUUID(); // lock this new node Lock lock = n.lock(true, false); String lockToken = lock.getLockToken(); // commit utx.commit(); // refresh Lock Info lock = n.getLock(); // start new Transaction and try to add lock token unlock the node and then remove it utx = new UserTransactionImpl(superuser); utx.begin(); Node otherNode = superuser.getNodeByUUID(uuid); assertTrue("Node not locked", otherNode.isLocked()); // add lock token superuser.addLockToken(lockToken); // refresh Lock Info lock = otherNode.getLock(); // assert: session must hold lock token assertTrue("session must hold lock token", containsLockToken(superuser, lockToken)); otherNode.unlock(); assertFalse("Node is locked", otherNode.isLocked()); otherNode.remove(); superuser.save(); utx.commit(); } /** * Test setting the same property multiple times. Exposes an issue where * the same property instance got reused in subsequent transactions * (see JCR-1554). * * @throws Exception if an error occurs */ public void testSetProperty() throws Exception { final String testNodePath = testPath + "/" + Math.random(); Session session = getHelper().getSuperuserSession(); try { // Add node doTransactional(new Operation() { public void invoke(Session session) throws Exception { session.getRootNode().addNode(testNodePath); session.save(); } }, session); for (int i = 1; i <= 3; i++) { // Set property "name" to value "value" doTransactional(new Operation() { public void invoke(Session session) throws Exception { Node n = (Node) session.getItem("/" + testNodePath); n.setProperty("name", "value"); session.save(); } }, session); } } finally { session.logout(); } } /** * Test deleting a subnode after creation. Exposes an issue where * the same node instance got reused in subsequent transactions * (see JCR-1554). * * @throws Exception if an error occurs */ public void testDeleteNode() throws Exception { final String testNodePath = testPath + "/" + Math.random(); Session session = getHelper().getSuperuserSession(); try { for (int i = 1; i <= 3; i++) { // Add parent node doTransactional(new Operation() { public void invoke(Session session) throws Exception { session.getRootNode().addNode(testNodePath); session.save(); } }, session); // Add child node doTransactional(new Operation() { public void invoke(Session session) throws Exception { session.getRootNode().addNode(testNodePath + "/subnode"); session.save(); } }, session); // Remove parent node doTransactional(new Operation() { public void invoke(Session session) throws Exception { session.getRootNode().getNode(testNodePath).remove(); session.save(); } }, session); } } finally { session.logout(); } } /** * Operation to invoke on a session scope. */ interface Operation { /** * Invoke the operation. * @param session session to use inside operation * @throws Exception if an error occurs */ void invoke(Session session) throws Exception; } /** * Wrap a session-scoped operation with a transaction. * * @param op operation to invoke * @param session session to use for the transaction * @throws Exception if an error occurs */ private void doTransactional(Operation op, Session session) throws Exception { UserTransaction utx = new UserTransactionImpl(session); utx.begin(); op.invoke(session); utx.commit(); } /** * Return a flag indicating whether the indicated session contains * a specific lock token */ private boolean containsLockToken(Session session, String lockToken) { String[] lt = session.getLockTokens(); for (int i = 0; i < lt.length; i++) { if (lt[i].equals(lockToken)) { return true; } } return false; } }