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());
+ }
}