You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by dp...@apache.org on 2006/01/03 08:23:34 UTC

svn commit: r365558 [2/2] - in /incubator/jackrabbit/trunk/jackrabbit/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/lock/ main/java/org/apache/jackrabbit/core/state/ test/java/org/apache/jackrabbit/core/

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.lock;
+
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.TransactionException;
+import org.apache.jackrabbit.core.TransactionContext;
+import org.apache.jackrabbit.core.InternalXAResource;
+import org.apache.jackrabbit.name.Path;
+import org.apache.log4j.Logger;
+
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Session-local lock manager that implements the semantical changes inside
+ * transactions. This manager validates lock/unlock operations inside its
+ * view of the locking space.
+ */
+public class XALockManager implements LockManager, InternalXAResource {
+
+    /**
+     * Logger instance for this class
+     */
+    private static final Logger log = Logger.getLogger(XALockManager.class);
+
+    /**
+     * Attribute name for XA Environment.
+     */
+    private static final String XA_ENV_ATTRIBUTE_NAME = "XALockManager.XAEnv";
+
+    /**
+     * Parent session.
+     */
+    private final SessionImpl session;
+
+    /**
+     * Global lock manager.
+     */
+    private final LockManagerImpl lockMgr;
+
+    /**
+     * Current XA environment.
+     */
+    private XAEnvironment xaEnv;
+
+    /**
+     * Create a new instance of this class.
+     * @param session session
+     * @param lockMgr lockMgr global lock manager
+     */
+    public XALockManager(SessionImpl session, LockManagerImpl lockMgr) {
+        this.session = session;
+        this.lockMgr = lockMgr;
+    }
+
+    //----------------------------------------------------------< LockManager >
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock lock(NodeImpl node, boolean isDeep, boolean isSessionScoped)
+            throws LockException, RepositoryException {
+
+        AbstractLockInfo info = null;
+        if (isInXA()) {
+            info = xaEnv.lock(node, isDeep, isSessionScoped);
+        } else {
+            info = lockMgr.internalLock(node, isDeep, isSessionScoped);
+        }
+        return new XALock(this, info, node);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock getLock(NodeImpl node) throws LockException, RepositoryException {
+        AbstractLockInfo info = null;
+        if (isInXA()) {
+            info = xaEnv.getLockInfo(node);
+        } else {
+            info = lockMgr.getLockInfo(node.internalGetUUID());
+        }
+        if (info == null) {
+            throw new LockException("Node not locked: " + node.safeGetJCRPath());
+        }
+        SessionImpl session = (SessionImpl) node.getSession();
+        NodeImpl holder = (NodeImpl) session.getItemManager().getItem(
+                new NodeId(info.getUUID()));
+        return new XALock(this, info, holder);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unlock(NodeImpl node) throws LockException, RepositoryException {
+        if (isInXA()) {
+            xaEnv.unlock(node);
+        } else {
+            lockMgr.unlock(node);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean holdsLock(NodeImpl node) throws RepositoryException {
+        AbstractLockInfo info = null;
+        if (isInXA()) {
+            info = xaEnv.getLockInfo(node);
+        } else {
+            info = lockMgr.getLockInfo(node.internalGetUUID());
+        }
+        if (info != null && info.getUUID().equals(node.internalGetUUID())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isLocked(NodeImpl node) throws RepositoryException {
+        AbstractLockInfo info = null;
+        if (isInXA()) {
+            info = xaEnv.getLockInfo(node);
+        } else {
+            info = lockMgr.getLockInfo(node.internalGetUUID());
+        }
+        return info != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void checkLock(NodeImpl node) throws LockException, RepositoryException {
+        AbstractLockInfo info = null;
+        if (isInXA()) {
+            info = xaEnv.getLockInfo(node);
+        } else {
+            info = lockMgr.getLockInfo(node.internalGetUUID());
+        }
+        if (info != null && info.getLockHolder() != node.getSession()) {
+            throw new LockException("Node locked.");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void checkLock(Path path, Session session)
+            throws LockException, RepositoryException {
+
+        SessionImpl sessionImpl = (SessionImpl) session;
+        checkLock((NodeImpl) sessionImpl.getItemManager().getItem(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lockTokenAdded(SessionImpl session, String lt) {
+        if (isInXA()) {
+            xaEnv.addLockToken(lt);
+        } else {
+            lockMgr.lockTokenAdded(session, lt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lockTokenRemoved(SessionImpl session, String lt) {
+        if (isInXA()) {
+            xaEnv.removeLockToken(lt);
+        } else {
+            lockMgr.lockTokenRemoved(session, lt);
+        }
+    }
+
+    //-----------------------------------------------------------< transaction >
+
+    /**
+     * {@inheritDoc}
+     */
+    public void associate(TransactionContext tx) {
+        XAEnvironment xaEnv = null;
+        if (tx != null) {
+            xaEnv = (XAEnvironment) tx.getAttribute(XA_ENV_ATTRIBUTE_NAME);
+            if (xaEnv == null) {
+                xaEnv = new XAEnvironment(session, lockMgr);
+                tx.setAttribute(XA_ENV_ATTRIBUTE_NAME, xaEnv);
+            }
+        }
+        this.xaEnv = xaEnv;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void prepare(TransactionContext tx) throws TransactionException {
+        XAEnvironment xaEnv = (XAEnvironment) tx.getAttribute(XA_ENV_ATTRIBUTE_NAME);
+        if (xaEnv != null) {
+            xaEnv.prepare();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * This will finish the update and unlock the shared lock manager.
+     */
+    public void commit(TransactionContext tx) {
+        XAEnvironment xaEnv = (XAEnvironment) tx.getAttribute(XA_ENV_ATTRIBUTE_NAME);
+        if (xaEnv != null) {
+            xaEnv.commit();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * This will undo all updates and unlock the shared lock manager.
+     */
+    public void rollback(TransactionContext tx) {
+        XAEnvironment xaEnv = (XAEnvironment) tx.getAttribute(XA_ENV_ATTRIBUTE_NAME);
+        if (xaEnv != null) {
+            xaEnv.rollback();
+        }
+    }
+
+    /**
+     * Return a flag indicating whether a lock info belongs to a different
+     * XA environment.
+     */
+    public boolean differentXAEnv(AbstractLockInfo info) {
+        if (isInXA()) {
+            return xaEnv.differentXAEnv(info);
+        } else {
+            return info instanceof XAEnvironment.LockInfo;
+        }
+    }
+
+    /**
+     * Return a flag indicating whether this version manager is currently
+     * associated with an XA transaction.
+     */
+    private boolean isInXA() {
+        return xaEnv != null;
+    }
+}

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.state;
+
+import org.apache.jackrabbit.core.ItemId;
+import org.apache.jackrabbit.core.WorkspaceImpl;
+import org.apache.jackrabbit.core.TransactionException;
+import org.apache.jackrabbit.core.TransactionContext;
+import org.apache.jackrabbit.core.InternalXAResource;
+import org.apache.log4j.Logger;
+
+import javax.jcr.ReferentialIntegrityException;
+
+/**
+ * Extension to <code>LocalItemStateManager</code> that remembers changes on
+ * multiple save() requests and commits them only when an associated transaction
+ * is itself committed.
+ */
+public class XAItemStateManager extends LocalItemStateManager implements InternalXAResource {
+
+    /**
+     * Logger instance.
+     */
+    private static Logger log = Logger.getLogger(XAItemStateManager.class);
+
+    /**
+     * Default change log attribute name.
+     */
+    private static final String DEFAULT_ATTRIBUTE_NAME = "ChangeLog";
+
+    /**
+     * ThreadLocal that holds the ChangeLog while this state manager is in one
+     * of the {@link #prepare}, {@link #commit}, {@link #rollback}
+     * methods.
+     */
+    private ThreadLocal commitLog = new ThreadLocal() {
+        protected synchronized Object initialValue() {
+            return new CommitLog();
+        }
+    };
+
+    /**
+     * Current instance-local change log.
+     */
+    private transient ChangeLog txLog;
+
+    /**
+     * Change log attribute name.
+     */
+    private final String attributeName;
+
+    /**
+     * Creates a new instance of this class.
+     * @param sharedStateMgr shared state manager
+     * @param wspImpl workspace
+     */
+    public XAItemStateManager(SharedItemStateManager sharedStateMgr,
+                              WorkspaceImpl wspImpl) {
+        this(sharedStateMgr, wspImpl, DEFAULT_ATTRIBUTE_NAME);
+    }
+
+    /**
+     * Creates a new instance of this class with a custom attribute name.
+     * @param sharedStateMgr shared state manager
+     * @param wspImpl workspace
+     * @param attributeName attribute name
+     */
+    public XAItemStateManager(SharedItemStateManager sharedStateMgr,
+                              WorkspaceImpl wspImpl, String attributeName) {
+        super(sharedStateMgr, wspImpl);
+
+        this.attributeName = attributeName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void associate(TransactionContext tx) {
+        ChangeLog txLog = null;
+        if (tx != null) {
+            txLog = (ChangeLog) tx.getAttribute(attributeName);
+            if (txLog == null) {
+                txLog = new ChangeLog();
+                tx.setAttribute(attributeName, txLog);
+            }
+        }
+        this.txLog = txLog;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void prepare(TransactionContext tx) throws TransactionException {
+        ChangeLog txLog = (ChangeLog) tx.getAttribute(attributeName);
+        if (txLog != null) {
+            try {
+                ((CommitLog) commitLog.get()).setChanges(txLog);
+                sharedStateMgr.checkReferentialIntegrity(txLog);
+            } catch (ReferentialIntegrityException rie) {
+                log.error(rie);
+                txLog.undo(sharedStateMgr);
+                throw new TransactionException("Unable to prepare transaction.", rie);
+            } catch (ItemStateException ise) {
+                log.error(ise);
+                txLog.undo(sharedStateMgr);
+                throw new TransactionException("Unable to prepare transaction.", ise);
+            } finally {
+                ((CommitLog) commitLog.get()).setChanges(null);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void commit(TransactionContext tx) throws TransactionException {
+        ChangeLog txLog = (ChangeLog) tx.getAttribute(attributeName);
+        if (txLog != null) {
+            try {
+                ((CommitLog) commitLog.get()).setChanges(txLog);
+                super.update(txLog);
+            } catch (ReferentialIntegrityException rie) {
+                log.error(rie);
+                txLog.undo(sharedStateMgr);
+                throw new TransactionException("Unable to commit transaction.", rie);
+            } catch (ItemStateException ise) {
+                log.error(ise);
+                txLog.undo(sharedStateMgr);
+                throw new TransactionException("Unable to commit transaction.", ise);
+            } finally {
+                ((CommitLog) commitLog.get()).setChanges(null);
+            }
+            txLog.reset();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void rollback(TransactionContext tx) {
+        ChangeLog txLog = (ChangeLog) tx.getAttribute(attributeName);
+        if (txLog != null) {
+            try {
+                ((CommitLog) commitLog.get()).setChanges(txLog);
+                txLog.undo(sharedStateMgr);
+            } finally {
+                ((CommitLog) commitLog.get()).setChanges(null);
+            }
+        }
+    }
+
+    /**
+     * Returns the current change log. First tries thread-local change log,
+     * then instance-local change log. Returns <code>null</code> if no
+     * change log was found.
+     */
+    public ChangeLog getChangeLog() {
+        ChangeLog changeLog = ((CommitLog) commitLog.get()).getChanges();
+        if (changeLog == null) {
+            changeLog = txLog;
+        }
+        return changeLog;
+    }
+
+    //-----------------------------------------------------< ItemStateManager >
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * If this state manager is committing changes, this method first checks
+     * the commitLog ThreadLocal. Else if associated to a transaction check
+     * the transactional change log. Fallback is always the call to the base
+     * class.
+     */
+    public ItemState getItemState(ItemId id)
+            throws NoSuchItemStateException, ItemStateException {
+
+        ChangeLog changeLog = getChangeLog();
+        if (changeLog != null) {
+            ItemState state = changeLog.get(id);
+            if (state != null) {
+                return state;
+            }
+        }
+        return super.getItemState(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * If this state manager is committing changes, this method first checks
+     * the commitLog ThreadLocal. Else if associated to a transaction check
+     * the transactional change log. Fallback is always the call to the base
+     * class.
+     */
+    public boolean hasItemState(ItemId id) {
+        ChangeLog changeLog = getChangeLog();
+        if (changeLog != null) {
+            try {
+                ItemState state = changeLog.get(id);
+                if (state != null) {
+                    return true;
+                }
+            } catch (NoSuchItemStateException e) {
+                return false;
+            }
+        }
+        return super.hasItemState(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * If this state manager is committing changes, this method first
+     * checks the commitLog ThreadLocal. Else if associated to a transaction
+     * check the transactional change log. Fallback is always the call to
+     * the base class.
+     */
+    public NodeReferences getNodeReferences(NodeReferencesId id)
+            throws NoSuchItemStateException, ItemStateException {
+
+        ChangeLog changeLog = getChangeLog();
+        if (changeLog != null) {
+            NodeReferences refs = changeLog.get(id);
+            if (refs != null) {
+                return refs;
+            }
+        }
+        return super.getNodeReferences(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * If this state manager is committing changes, this method first
+     * checks the commitLog ThreadLocal. Else if associated to a transaction
+     * check the transactional change log. Fallback is always the call to
+     * the base class.
+     */
+    public boolean hasNodeReferences(NodeReferencesId id) {
+        ChangeLog changeLog = getChangeLog();
+        if (changeLog != null) {
+            if (changeLog.get(id) != null) {
+                return true;
+            }
+        }
+        return super.hasNodeReferences(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * If associated with a transaction, simply merge the changes given to
+     * the ones already known (removing items that were first added and
+     * then again deleted).
+     */
+    protected void update(ChangeLog changeLog)
+            throws ReferentialIntegrityException, StaleItemStateException,
+            ItemStateException {
+        if (txLog != null) {
+            txLog.merge(changeLog);
+        } else {
+            super.update(changeLog);
+        }
+    }
+
+    //--------------------------< inner classes >-------------------------------
+
+    /**
+     * Helper class that serves as a container for a ChangeLog in a ThreadLocal.
+     * The <code>CommitLog</code> is associated with a <code>ChangeLog</code>
+     * while the <code>TransactionalItemStateManager</code> is in the commit
+     * method.
+     */
+    private static class CommitLog {
+
+        /**
+         * The changes that are about to be committed
+         */
+        private ChangeLog changes;
+
+        /**
+         * Sets changes that are about to be committed.
+         *
+         * @param changes that are about to be committed, or <code>null</code>
+         *                if changes have been committed and the commit log should be reset.
+         */
+        private void setChanges(ChangeLog changes) {
+            this.changes = changes;
+        }
+
+        /**
+         * The changes that are about to be committed, or <code>null</code> if
+         * the <code>TransactionalItemStateManager</code> is currently not
+         * committing any changes.
+         *
+         * @return the changes about to be committed.
+         */
+        private ChangeLog getChanges() {
+            return changes;
+        }
+    }
+}

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/UserTransactionImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/UserTransactionImpl.java?rev=365558&r1=365557&r2=365558&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/UserTransactionImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/UserTransactionImpl.java Mon Jan  2 23:22:25 2006
@@ -113,10 +113,11 @@
 
             if (e.errorCode >= XAException.XA_RBBASE &&
                     e.errorCode <= XAException.XA_RBEND) {
-                throw new RollbackException();
+                throw new RollbackException("Transaction rolled back: " +
+                        "XA_ERR=" + e.errorCode);
             } else {
                 throw new SystemException("Unable to commit transaction: " +
-                    "XA_ERR=" + e.errorCode);
+                        "XA_ERR=" + e.errorCode);
             }
         }
     }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/XATest.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/XATest.java?rev=365558&r1=365557&r2=365558&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/XATest.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/XATest.java Mon Jan  2 23:22:25 2006
@@ -22,6 +22,7 @@
 import javax.jcr.Node;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Session;
+import javax.jcr.lock.Lock;
 import javax.transaction.UserTransaction;
 import javax.transaction.RollbackException;
 
@@ -742,4 +743,64 @@
         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());
+    }
 }