You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2010/04/23 15:43:37 UTC
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...
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;
+ }
+ }
+
+}