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} + *
+ * 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} + * + * 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 toLocalItemStateManager 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 null if no
+ * change log was found.
+ */
+ public ChangeLog getChangeLog() {
+ ChangeLog changeLog = ((CommitLog) commitLog.get()).getChanges();
+ if (changeLog == null) {
+ changeLog = txLog;
+ }
+ return changeLog;
+ }
+
+ //-----------------------------------------------------< ItemStateManager >
+ /**
+ * {@inheritDoc}
+ *
+ * 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}
+ *
+ * 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}
+ *
+ * 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}
+ *
+ * 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}
+ *
+ * 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 CommitLog is associated with a ChangeLog
+ * while the TransactionalItemStateManager 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 null
+ * 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 null if
+ * the TransactionalItemStateManager 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());
+ }
}