jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r937293 [3/3] - in /jackrabbit/sandbox/jackrabbit-j3: ./ src/main/java/org/apache/jackrabbit/j3/ src/main/java/org/apache/jackrabbit/j3/api/ src/main/java/org/apache/jackrabbit/j3/api/management/ src/main/java/org/apache/jackrabbit/j3/api/o...
Date Fri, 23 Apr 2010 13:43:37 GMT
Added: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/TestXA.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/TestXA.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/TestXA.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/TestXA.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,1979 @@
+/*
+ * 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.j3.xa;
+
+import java.util.StringTokenizer;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+import javax.transaction.RollbackException;
+import javax.transaction.UserTransaction;
+import org.apache.jackrabbit.j3.TestBase;
+import org.apache.jackrabbit.j3.api.JackrabbitSession;
+
+/**
+ * <code>XATest</code> contains the test cases for the methods
+ * inside {@link org.apache.jackrabbit.api.XASession}.
+ */
+public class TestXA extends TestBase {
+
+    private Session otherSuperuser;
+    private String testPath = "testXA";
+    private String workspaceName = "default";
+    private String testNodeType = "nt:unstructured";
+    private Node testRootNode;
+    private JackrabbitSession superuser;
+    private String nodeName1 = "node1";
+    private String propertyName1 = "p1";
+    private String nodeName2 = "node2";
+    private String nodeName3 = "node3";
+    private String mixReferenceable = "mix:referenceable";
+    private String mixLockable = "mix:lockable";
+    private String jcrlockIsDeep = "jcr:lockIsDeep";
+    private String jcrLockOwner = "jcr:lockOwner";
+    private String mixVersionable = "mix:versionable";
+
+    private JackrabbitSession getSuperuserSession() throws RepositoryException {
+        return (JackrabbitSession) openSession();
+    }
+
+    private JackrabbitSession getSuperuserSession(String workspaceName) throws RepositoryException {
+        return (JackrabbitSession) openSession();
+    }
+
+    protected boolean isSupported(String descriptorKey) throws RepositoryException {
+        return "true".equals(repository.getDescriptor(descriptorKey));
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        otherSuperuser = getSuperuserSession();
+        testRootNode = session.getRootNode();
+        superuser = (JackrabbitSession) session;
+
+        // clean testroot on second workspace
+        Session s2 = 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();
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        if (otherSuperuser != null) {
+            otherSuperuser.logout();
+            otherSuperuser = null;
+        }
+        super.tearDown();
+    }
+
+    public void testSupported() throws RepositoryException {
+        assertTrue(isSupported(Repository.OPTION_TRANSACTIONS_SUPPORTED));
+    }
+
+    /**
+     * Add a node inside a transaction and commit changes. Make sure
+     * node exists for other sessions only after commit.
+     */
+    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 = 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.
+     */
+    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 = 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();
+    }
+
+    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 = getSuperuserSession();
+        Node n = (Node) otherSuperuser.getItem(testRootNode.getPath());
+        assertEquals(n.getProperty(propertyName1).getString(), "1");
+        otherSuperuser.logout();
+    }
+
+    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();
+    }
+
+    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();
+    }
+
+    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();
+    }
+
+    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 = getSuperuserSession();
+        Node n = (Node) otherSuperuser.getItem(testRootNode.getPath());
+        assertEquals(n.getProperty(propertyName1).getString(), "1");
+        otherSuperuser.logout();
+    }
+
+    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 = getSuperuserSession();
+        Node n = (Node) otherSuperuser.getItem(testRootNode.getPath());
+        assertFalse("Property must be deleted.", n.hasProperty(propertyName1));
+        otherSuperuser.logout();
+    }
+
+    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 = getSuperuserSession();
+        Node n = (Node) otherSuperuser.getItem(testRootNode.getPath());
+        assertFalse("Property must be deleted.", n.hasProperty(propertyName1));
+        otherSuperuser.logout();
+    }
+
+    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.
+     */
+    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.
+     */
+    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.
+     */
+    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.
+     */
+    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 = 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 = 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 = 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);
+        assertNull("session must get a null lock token", lock.getLockToken());
+        assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken));
+
+        // commit
+        utx.commit();
+
+        assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken));
+        assertNull("session must get a null lock token", lock.getLockToken());
+
+        // 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 = 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 = 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 = getSuperuserSession();
+        Node nOther = other.getNodeByUUID(n.getUUID());
+        assertTrue(nOther.isLocked());
+
+        // Check if it is also locked in other transaction
+        JackrabbitSession other2 = 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);
+        assertNull("session must get a null lock token", lock.getLockToken());
+
+        // commit
+        utx.commit();
+
+        // refresh Lock Info
+        lock = n.getLock();
+
+        assertNull("session must get a null lock token", lock.getLockToken());
+
+        JackrabbitSession other = 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 {
+        JackrabbitSession s1 = superuser;
+        JackrabbitSession s2 = 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 {
+        JackrabbitSession otherSuperuser = 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);
+        assertNull("session must get a null lock token", lock.getLockToken());
+
+        // commit
+        utx.commit();
+
+        // refresh Lock Info
+        lock = n.getLock();
+
+        assertNull("session must get a null lock token", lock.getLockToken());
+
+        JackrabbitSession other = 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();
+    }
+
+    /**
+     * 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();
+
+        JackrabbitSession session = 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();
+
+        JackrabbitSession session = 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, JackrabbitSession 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;
+    }
+
+}

Added: jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/UserTransactionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/UserTransactionImpl.java?rev=937293&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/UserTransactionImpl.java (added)
+++ jackrabbit/sandbox/jackrabbit-j3/src/test/java/org/apache/jackrabbit/j3/xa/UserTransactionImpl.java Fri Apr 23 13:43:36 2010
@@ -0,0 +1,258 @@
+/*
+ * 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.j3.xa;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import org.apache.jackrabbit.j3.SessionImpl;
+import org.apache.jackrabbit.j3.api.JackrabbitSession;
+
+/**
+ * Internal {@link javax.transaction.UserTransaction} implementation.
+ */
+public class UserTransactionImpl implements UserTransaction {
+
+    /**
+     * Global transaction id counter
+     */
+    private static byte counter;
+
+    /**
+     * The XAResources map
+     */
+    private Map<XAResource, XidImpl> xaResources = new HashMap<XAResource, XidImpl>();
+
+    private int status = Status.STATUS_NO_TRANSACTION;
+
+    private boolean distributedThreadAccess;
+
+    /**
+     * Create a new instance of this class. Takes a session as parameter.
+     * @param session session. If session is not of type
+     * {@link XASession}, an <code>IllegalArgumentException</code>
+     * is thrown
+     */
+    public UserTransactionImpl(JackrabbitSession session) {
+        this(session, false);
+    }
+
+    /**
+     * Create a new instance of this class. Takes a session as parameter.
+     * @param session session. If session is not of type
+     * {@link XASession}, an <code>IllegalArgumentException</code>
+     * is thrown
+     */
+    public UserTransactionImpl(JackrabbitSession session, boolean distributedThreadAccess) {
+        if (session instanceof SessionImpl) {
+            counter++;
+            xaResources.put(session.getXAResource(), new XidImpl(counter));
+            this.distributedThreadAccess = distributedThreadAccess;
+        } else {
+            throw new IllegalArgumentException("Session not of type XASession");
+        }
+    }
+
+    /**
+     * Enlists the given Session to this UserTransaction
+     */
+    public void enlistXAResource(JackrabbitSession session) {
+        xaResources.put(session.getXAResource(), new XidImpl(counter));
+    }
+
+    public void begin() throws NotSupportedException, SystemException {
+        if (status != Status.STATUS_NO_TRANSACTION) {
+            throw new IllegalStateException("Transaction already active");
+        }
+
+        try {
+            for (XAResource resource : xaResources.keySet()) {
+                XidImpl xid = xaResources.get(resource);
+                resource.start(xid, XAResource.TMNOFLAGS);
+            }
+            status = Status.STATUS_ACTIVE;
+
+        } catch (XAException e) {
+            throw new SystemException("Unable to begin transaction: " +
+                    "XA_ERR=" + e.errorCode);
+        }
+    }
+
+    public void commit() throws HeuristicMixedException,
+            HeuristicRollbackException, IllegalStateException,
+            RollbackException, SecurityException, SystemException {
+
+        if (status != Status.STATUS_ACTIVE) {
+            throw new IllegalStateException("Transaction not active");
+        }
+
+        try {
+            for (XAResource resource : xaResources.keySet()) {
+                XidImpl xid = xaResources.get(resource);
+                resource.end(xid, XAResource.TMSUCCESS);
+            }
+
+            status = Status.STATUS_PREPARING;
+            for (XAResource resource : xaResources.keySet()) {
+                XidImpl xid = xaResources.get(resource);
+                resource.prepare(xid);
+            }
+            status = Status.STATUS_PREPARED;
+
+            status = Status.STATUS_COMMITTING;
+            if (distributedThreadAccess) {
+                Thread distributedThread = new Thread() {
+                    public void run() {
+                        try {
+                            for (XAResource resource : xaResources.keySet()) {
+                                XidImpl xid = xaResources.get(resource);
+                                resource.commit(xid, false);
+                            }
+                        } catch (Exception e) {
+                            throw new RuntimeException(e.getMessage());
+                        }
+                    }
+                };
+                distributedThread.start();
+                distributedThread.join(1000);
+                if (distributedThread.isAlive()) {
+                    throw new SystemException(
+                            "Commit from different thread but same XID must not block");
+                }
+            } else {
+                for (XAResource resource : xaResources.keySet()) {
+                    XidImpl xid = xaResources.get(resource);
+                    resource.commit(xid, false);
+                }
+            }
+
+            status = Status.STATUS_COMMITTED;
+
+        } catch (XAException e) {
+
+            if (e.errorCode >= XAException.XA_RBBASE &&
+                    e.errorCode <= XAException.XA_RBEND) {
+                RollbackException re = new RollbackException(
+                        "Transaction rolled back: XA_ERR=" + e.errorCode);
+                re.initCause(e.getCause());
+                throw re;
+            } else {
+                SystemException se = new SystemException(
+                        "Unable to commit transaction: XA_ERR=" + e.errorCode);
+                se.initCause(e.getCause());
+                throw se;
+            }
+        } catch (InterruptedException e) {
+            throw new SystemException("Thread.join() interrupted");
+        }
+    }
+
+    public int getStatus() throws SystemException {
+        return status;
+    }
+
+    public void rollback() throws IllegalStateException, SecurityException,
+            SystemException {
+
+        if (status != Status.STATUS_ACTIVE &&
+                status != Status.STATUS_MARKED_ROLLBACK) {
+
+            throw new IllegalStateException("Transaction not active");
+        }
+
+        try {
+            for (XAResource resource : xaResources.keySet()) {
+                XidImpl xid = xaResources.get(resource);
+                resource.end(xid, XAResource.TMFAIL);
+            }
+
+            status = Status.STATUS_ROLLING_BACK;
+            for (XAResource resource : xaResources.keySet()) {
+                XidImpl xid = xaResources.get(resource);
+                resource.rollback(xid);
+            }
+            status = Status.STATUS_ROLLEDBACK;
+
+        } catch (XAException e) {
+            SystemException se = new SystemException(
+                    "Unable to rollback transaction: XA_ERR=" + e.errorCode);
+            se.initCause(e.getCause());
+            throw se;
+        }
+    }
+
+    public void setRollbackOnly() throws IllegalStateException, SystemException {
+        if (status != Status.STATUS_ACTIVE) {
+            throw new IllegalStateException("Transaction not active");
+        }
+        status = Status.STATUS_MARKED_ROLLBACK;
+    }
+
+    public void setTransactionTimeout(int seconds) throws SystemException {
+        try {
+            for (XAResource resource : xaResources.keySet()) {
+                resource.setTransactionTimeout(seconds);
+            }
+        } catch (XAException e) {
+            SystemException se = new SystemException(
+                    "Unable to set the TransactionTiomeout: XA_ERR=" + e.errorCode);
+            se.initCause(e.getCause());
+            throw se;
+        }
+    }
+
+
+    /**
+     * Internal {@link Xid} implementation.
+     */
+    class XidImpl implements Xid {
+
+        /** Global transaction id */
+        private final byte[] globalTxId;
+
+        /**
+         * Create a new instance of this class. Takes a global
+         * transaction number as parameter
+         * @param globalTxNumber global transaction number
+         */
+        public XidImpl(byte globalTxNumber) {
+            this.globalTxId = new byte[] { globalTxNumber };
+        }
+
+        public int getFormatId() {
+            return 0;
+        }
+
+        public byte[] getBranchQualifier() {
+            return new byte[0];
+        }
+
+        public byte[] getGlobalTransactionId() {
+            return globalTxId;
+        }
+    }
+
+}



Mime
View raw message