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