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 2005/12/06 17:08:43 UTC

svn commit: r354456 [1/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/ main/java/org/apache/jackrabbit/core/xml/ test/java/...

Author: dpfister
Date: Tue Dec  6 08:07:53 2005
New Revision: 354456

URL: http://svn.apache.org/viewcvs?rev=354456&view=rev
Log:
Make locking part of XAResource's transaction support

Added:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionException.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionListener.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java   (with props)
Removed:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionContext.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionException.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionListener.java
Modified:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
    incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/UserTransactionImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/XATest.java

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Tue Dec  6 08:07:53 2005
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.core;
 
 import org.apache.jackrabbit.BaseException;
-import org.apache.jackrabbit.core.lock.LockManager;
 import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
 import org.apache.jackrabbit.core.nodetype.NodeDef;
 import org.apache.jackrabbit.core.nodetype.NodeDefId;
@@ -3730,8 +3729,7 @@
 
         checkLockable();
 
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        return lockMgr.lock(this, isDeep, isSessionScoped);
+        return session.getLockManager().lock(this, isDeep, isSessionScoped);
     }
 
     /**
@@ -3743,13 +3741,10 @@
         // check state of this instance
         sanityCheck();
 
-        // todo: fixme for transactions
-        if (isTransactionalNew()) {
+        if (isNew()) {
             throw new LockException("Node not locked: " + safeGetJCRPath());
         }
-
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        return lockMgr.getLock(this);
+        return session.getLockManager().getLock(this);
     }
 
     /**
@@ -3770,9 +3765,7 @@
         }
 
         checkLockable();
-
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        lockMgr.unlock(this);
+        session.getLockManager().unlock(this);
     }
 
     /**
@@ -3782,14 +3775,11 @@
         // check state of this instance
         sanityCheck();
 
-        // todo: fixme for transactions
-        if (!isNodeType(QName.MIX_LOCKABLE) || isTransactionalNew()) {
+        if (!isNodeType(QName.MIX_LOCKABLE) || isNew()) {
             // a node that is new or not lockable never holds a lock
             return false;
         }
-
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        return lockMgr.holdsLock(this);
+        return session.getLockManager().holdsLock(this);
     }
 
     /**
@@ -3799,13 +3789,10 @@
         // check state of this instance
         sanityCheck();
 
-        // todo: fixme for transactions
-        if (isTransactionalNew()) {
+        if (isNew()) {
             return false;
         }
-
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        return lockMgr.isLocked(this);
+        return session.getLockManager().isLocked(this);
     }
 
     /**
@@ -3830,14 +3817,11 @@
      * @throws RepositoryException if some other error occurs
      */
     protected void checkLock() throws LockException, RepositoryException {
-        // todo: fixme for transactions
-        if (isTransactionalNew()) {
-            // a new node must not be checked
+        if (isNew()) {
+            // a new node needs no check
             return;
         }
-
-        LockManager lockMgr = ((WorkspaceImpl) session.getWorkspace()).getLockManager();
-        lockMgr.checkLock(this);
+        session.getLockManager().checkLock(this);
     }
 
     //--------------------------------------------------------< inner classes >

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PathMap.java Tue Dec  6 08:07:53 2005
@@ -318,6 +318,19 @@
         }
 
         /**
+         * Remove all children of this element. Removes this element itself
+         * if this element does not contain associated information.
+         */
+        public void removeAll() {
+            children = null;
+            childrenCount = 0;
+
+            if (obj == null && parent != null) {
+                parent.remove(getPathElement(), false);
+            }
+        }
+
+        /**
          * Return the object associated with this element
          * @return object associated with this element
          */

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java Tue Dec  6 08:07:53 2005
@@ -27,7 +27,7 @@
 import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
 import org.apache.jackrabbit.core.lock.LockManager;
-import org.apache.jackrabbit.core.lock.LockManagerImpl;
+import org.apache.jackrabbit.core.lock.SharedLockManager;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
 import org.apache.jackrabbit.core.nodetype.virtual.VirtualNodeTypeStateManager;
@@ -1127,7 +1127,7 @@
         /**
          * Lock manager
          */
-        private LockManagerImpl lockMgr;
+        private SharedLockManager lockMgr;
 
         /**
          * Creates a new <code>WorkspaceInfo</code> based on the given
@@ -1269,7 +1269,7 @@
          */
         synchronized LockManager getLockManager() throws RepositoryException {
             if (lockMgr == null) {
-                lockMgr = new LockManagerImpl(getSystemSession(), config.getFileSystem());
+                lockMgr = new SharedLockManager(getSystemSession(), config.getFileSystem());
             }
             return lockMgr;
         }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Tue Dec  6 08:07:53 2005
@@ -38,6 +38,7 @@
 import org.apache.jackrabbit.core.xml.SessionImporter;
 import org.apache.jackrabbit.core.xml.SysViewSAXEventGenerator;
 import org.apache.jackrabbit.core.util.Dumpable;
+import org.apache.jackrabbit.core.lock.LockManager;
 import org.apache.jackrabbit.name.MalformedPathException;
 import org.apache.jackrabbit.name.NamespaceResolver;
 import org.apache.jackrabbit.name.Path;
@@ -403,16 +404,6 @@
     }
 
     /**
-     * Dispatch events belonging to a save operation.
-     *
-     * @param events events to dispatch as result of a successful save
-     *               operation
-     */
-    protected void dispatch(EventStateCollection events) {
-        events.dispatch();
-    }
-
-    /**
      * Returns the names of all workspaces of this repository with respect of the
      * access rights of this session.
      *
@@ -1240,7 +1231,7 @@
         synchronized (lockTokens) {
             if (lockTokens.add(lt) && notify) {
                 try {
-                    wsp.getLockManager().lockTokenAdded(this, lt);
+                    getLockManager().lockTokenAdded(this, lt);
                 } catch (RepositoryException e) {
                     log.error("Lock manager not available.", e);
                 }
@@ -1275,12 +1266,23 @@
         synchronized (lockTokens) {
             if (lockTokens.remove(lt) && notify) {
                 try {
-                    wsp.getLockManager().lockTokenRemoved(this, lt);
+                    getLockManager().lockTokenRemoved(this, lt);
                 } catch (RepositoryException e) {
                     log.error("Lock manager not available.", e);
                 }
             }
         }
+    }
+
+    /**
+     * Return the lock manager for this session. In a non-transactional
+     * environment, this is simply the lock manager shared by all sessions
+     * on this workspace.
+     * @return lock manager for this session
+     * @throws RepositoryException if an error occurs
+     */
+    public LockManager getLockManager() throws RepositoryException {
+        return wsp.getLockManager();
     }
 
     //-------------------------------------------------------------< Dumpable >

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents the transaction on behalf of the component that wants to
+ * explictely demarcate transcation boundaries.
+ */
+public class TransactionContext {
+
+    /**
+     * Transaction attributes
+     */
+    private final Map attributes = new HashMap();
+
+    /**
+     * Set an attribute on this transaction. If the value specified is
+     * <code>null</code>, it is semantically equivalent to
+     * {@link #removeAttribute}.
+     *
+     * @param name  attribute name
+     * @param value attribute value
+     */
+    public void setAttribute(String name, Object value) {
+        if (value == null) {
+            removeAttribute(name);
+        }
+        attributes.put(name, value);
+    }
+
+    /**
+     * Return an attribute value on this transaction.
+     *
+     * @param name attribute name
+     * @return attribute value, <code>null</code> if no attribute with that
+     *         name exists
+     */
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    /**
+     * Remove an attribute on this transaction.
+     *
+     * @param name attribute name
+     */
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+    }
+}

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

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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionException.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionException.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionException.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionException.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.BaseException;
+
+/**
+ * TransactionException is thrown when some operation inside the transaction
+ * fails.
+ */
+public class TransactionException extends BaseException {
+
+    /**
+     * Creates an instance of this class. Takes a detail message as parameter.
+     *
+     * @param message message
+     */
+    public TransactionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an instance of this class. Takes a root throwable as parameter.
+     *
+     * @param rootCause root throwable
+     */
+    public TransactionException(Throwable rootCause) {
+        super(rootCause);
+    }
+
+    /**
+     * Creates an instance of this class. Takes a message and a root throwable
+     * as parameter.
+     *
+     * @param message   message
+     * @param rootCause root throwable
+     */
+    public TransactionException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+}

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

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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionListener.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionListener.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionListener.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionListener.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.core.TransactionContext;
+
+/**
+ * Listener on a transaction. Will receive notifications about commit
+ * and rollback actions.
+ *
+ * @see org.apache.jackrabbit.core.TransactionContext
+ */
+public interface TransactionListener {
+
+    /**
+     * Transaction was committed
+     *
+     * @param tx transaction that was committed
+     */
+    void transactionCommitted(TransactionContext tx);
+
+    /**
+     * Transaction was rolled back
+     *
+     * @param tx transaction that was rolled back
+     */
+    void transactionRolledBack(TransactionContext tx);
+}

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

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

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java Tue Dec  6 08:07:53 2005
@@ -254,7 +254,7 @@
 
         BatchedItemOperations ops =
                 new BatchedItemOperations(stateMgr, rep.getNodeTypeRegistry(),
-                        getLockManager(), session, hierMgr,
+                        session.getLockManager(), session, hierMgr,
                         session.getNamespaceResolver());
 
         try {
@@ -481,7 +481,7 @@
 
         BatchedItemOperations ops =
                 new BatchedItemOperations(stateMgr, rep.getNodeTypeRegistry(),
-                        getLockManager(), session, hierMgr,
+                        session.getLockManager(), session, hierMgr,
                         session.getNamespaceResolver());
 
         try {

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java Tue Dec  6 08:07:53 2005
@@ -17,11 +17,12 @@
 package org.apache.jackrabbit.core;
 
 import org.apache.jackrabbit.core.config.WorkspaceConfig;
-import org.apache.jackrabbit.core.observation.EventStateCollection;
 import org.apache.jackrabbit.core.security.AuthContext;
-import org.apache.jackrabbit.core.state.TransactionContext;
-import org.apache.jackrabbit.core.state.TransactionException;
-import org.apache.jackrabbit.core.state.TransactionListener;
+import org.apache.jackrabbit.core.lock.LockManager;
+import org.apache.jackrabbit.core.lock.TxLockManager;
+import org.apache.jackrabbit.core.lock.SharedLockManager;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.TransactionalItemStateManager;
 import org.apache.log4j.Logger;
 
 import javax.jcr.AccessDeniedException;
@@ -50,6 +51,16 @@
     private static final Map txGlobal = new HashMap();
 
     /**
+     * Known attribute name.
+     */
+    private static final String ATTRIBUTE_CHANGE_LOG = "ChangeLog";
+
+    /**
+     * Known attribute name.
+     */
+    private static final String ATTRIBUTE_LOCK_MANAGER = "LockManager";
+
+    /**
      * Currently associated transaction
      */
     private TransactionContext tx;
@@ -227,7 +238,33 @@
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-        return XA_OK;
+
+        TransactionalItemStateManager stateMgr = wsp.getItemStateManager();
+        stateMgr.setChangeLog(getChangeLog(tx), true);
+
+        try {
+            // 1. Prepare state manager
+            try {
+                stateMgr.prepare();
+            } catch (TransactionException e) {
+                throw new ExtendedXAException(XAException.XA_RBOTHER, e);
+            }
+
+            // 2. Prepare lock manager
+            try {
+                TxLockManager lockMgr = getTxLockManager(tx);
+                if (lockMgr != null) {
+                    lockMgr.prepare();
+                }
+            } catch (TransactionException e) {
+                stateMgr.rollback();
+                throw new ExtendedXAException(XAException.XA_RBOTHER, e);
+            }
+            return XA_OK;
+
+        } finally {
+            stateMgr.setChangeLog(null, true);
+        }
     }
 
     /**
@@ -238,7 +275,23 @@
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-        wsp.getItemStateManager().rollback(tx);
+
+        TransactionalItemStateManager stateMgr = wsp.getItemStateManager();
+        stateMgr.setChangeLog(getChangeLog(tx), true);
+
+        try {
+            // 1. Rollback changes on lock manager
+            TxLockManager lockMgr = getTxLockManager(tx);
+            if (lockMgr != null) {
+                lockMgr.rollback();
+            }
+
+            // 2. Rollback changes on state manager
+            stateMgr.rollback();
+
+        } finally {
+            stateMgr.setChangeLog(null, true);
+        }
     }
 
     /**
@@ -250,10 +303,28 @@
             throw new XAException(XAException.XAER_NOTA);
         }
 
+        TransactionalItemStateManager stateMgr = wsp.getItemStateManager();
+        stateMgr.setChangeLog(getChangeLog(tx), true);
+
+        TxLockManager lockMgr = getTxLockManager(tx);
+
         try {
-            wsp.getItemStateManager().commit(tx);
-        } catch (TransactionException e) {
-            throw new ExtendedXAException(XAException.XA_RBOTHER, e);
+            // 1. Commit changes on state manager
+            try {
+                stateMgr.commit();
+            } catch (TransactionException e) {
+                if (lockMgr != null) {
+                    lockMgr.rollback();
+                }
+                throw new ExtendedXAException(XAException.XA_RBOTHER, e);
+            }
+
+            // 2. Commit changes on lock manager
+            if (lockMgr != null) {
+                lockMgr.commit();
+            }
+        } finally {
+            stateMgr.setChangeLog(null, true);
         }
     }
 
@@ -282,7 +353,12 @@
     void associate(TransactionContext tx) {
         this.tx = tx;
 
-        wsp.getItemStateManager().setTransactionContext(tx);
+        ChangeLog txLog = getChangeLog(tx);
+        if (txLog == null) {
+            txLog = new ChangeLog();
+            tx.setAttribute(ATTRIBUTE_CHANGE_LOG, txLog);
+        }
+        wsp.getItemStateManager().setChangeLog(txLog, false);
     }
 
     /**
@@ -303,59 +379,7 @@
     void disassociate() {
         tx = null;
 
-        wsp.getItemStateManager().setTransactionContext(null);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * If we are currently associated with a transaction, the dispatch operation
-     * will be postponed until commit.
-     */
-    protected void dispatch(EventStateCollection events) {
-        if (tx != null) {
-            tx.addListener(new EventDispatcher(events));
-            return;
-        }
-        super.dispatch(events);
-    }
-
-    /**
-     * Internal {@link TransactionListener} implementation that will dispatch
-     * events only when a transaction has actually been committed.
-     */
-    static class EventDispatcher implements TransactionListener {
-
-        /**
-         * Events to dispatch if transaction is committed
-         */
-        private final EventStateCollection events;
-
-        /**
-         * Create a new instance of this class.
-         *
-         * @param events events to dispatch on commit
-         */
-        public EventDispatcher(EventStateCollection events) {
-            this.events = events;
-        }
-
-        /**
-         * {@inheritDoc}
-         * <p/>
-         * Dispatch events.
-         */
-        public void transactionCommitted(TransactionContext tx) {
-            events.dispatch();
-        }
-
-        /**
-         * {@inheritDoc}
-         * <p/>
-         * Nothing to do.
-         */
-        public void transactionRolledBack(TransactionContext tx) {
-        }
+        wsp.getItemStateManager().setChangeLog(null, false);
     }
 
     /**
@@ -391,5 +415,48 @@
                 initCause(cause);
             }
         }
+    }
+
+    //-------------------------------------------------------< locking support >
+
+    /**
+     * Return the lock manager for this session. In a transactional environment,
+     * this is a session-local object that records locking/unlocking operations
+     * until final commit.
+     *
+     * @return lock manager for this session
+     * @throws javax.jcr.RepositoryException if an error occurs
+     */
+    public LockManager getLockManager() throws RepositoryException {
+        if (tx != null) {
+            TxLockManager lockMgr = (TxLockManager) tx.getAttribute(ATTRIBUTE_LOCK_MANAGER);
+            if (lockMgr == null) {
+                lockMgr = new TxLockManager(
+                        (SharedLockManager) super.getLockManager());
+                tx.setAttribute(ATTRIBUTE_LOCK_MANAGER, lockMgr);
+            }
+            return lockMgr;
+        }
+        return super.getLockManager();
+    }
+
+    /**
+     * Return the transactional change log for this session.
+     *
+     * @param tx transactional context
+     * @return change log for this session, may be <code>null</code>
+     */
+    private static ChangeLog getChangeLog(TransactionContext tx) {
+        return (ChangeLog) tx.getAttribute(ATTRIBUTE_CHANGE_LOG);
+    }
+
+    /**
+     * Return the transactional lock manager for this session. Returns
+     * <code>null</code> if no lock manager has been used yet.
+     *
+     * @return lock manager for this session
+     */
+    private static TxLockManager getTxLockManager(TransactionContext tx) {
+        return (TxLockManager) tx.getAttribute(ATTRIBUTE_LOCK_MANAGER);
     }
 }

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,138 @@
+/*
+ * 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.SessionImpl;
+
+import javax.jcr.Session;
+
+/**
+ * Common information about a lock.
+ */
+abstract class AbstractLockInfo {
+
+    /**
+     * Lock token
+     */
+    protected final LockToken lockToken;
+
+    /**
+     * Flag indicating whether lock is session scoped
+     */
+    protected final boolean sessionScoped;
+
+    /**
+     * Flag indicating whether lock is deep
+     */
+    protected final boolean deep;
+
+    /**
+     * Lock owner, determined on creation time
+     */
+    protected final String lockOwner;
+
+    /**
+     * Session currently holding lock
+     */
+    protected SessionImpl lockHolder;
+
+    /**
+     * Flag indicating whether this lock is live
+     */
+    protected boolean live;
+
+    /**
+     * Create a new instance of this class.
+     *
+     * @param lockToken     lock token
+     * @param sessionScoped whether lock token is session scoped
+     * @param deep          whether lock is deep
+     * @param lockOwner     owner of lock
+     */
+    public AbstractLockInfo(LockToken lockToken, boolean sessionScoped, boolean deep,
+                    String lockOwner) {
+        this.lockToken = lockToken;
+        this.sessionScoped = sessionScoped;
+        this.deep = deep;
+        this.lockOwner = lockOwner;
+    }
+
+    /**
+     * Set the live flag
+     * @param live live flag
+     */
+    public void setLive(boolean live) {
+        this.live = live;
+    }
+
+    /**
+     * Return the UUID of the lock holding node
+     * @return uuid
+     */
+    public String getUUID() {
+        return lockToken.uuid;
+    }
+
+    /**
+     * Return the session currently holding the lock
+     *
+     * @return session currently holding the lock
+     */
+    public SessionImpl getLockHolder() {
+        return lockHolder;
+    }
+
+    /**
+     * Set the session currently holding the lock
+     *
+     * @param lockHolder session currently holding the lock
+     */
+    public void setLockHolder(SessionImpl lockHolder) {
+        this.lockHolder = lockHolder;
+    }
+
+    /**
+     * Return the lock token as seen by the session passed as parameter. If
+     * this session is currently holding the lock, it will get the lock token
+     * itself, otherwise a <code>null</code> string
+     */
+    public String getLockToken(Session session) {
+        if (session.equals(lockHolder)) {
+            return lockToken.toString();
+        }
+        return null;
+    }
+
+    /**
+     * Return a flag indicating whether the lock is live
+     *
+     * @return <code>true</code> if the lock is live; otherwise <code>false</code>
+     */
+    public boolean isLive() {
+        return live;
+    }
+
+    /**
+     * Return a flag indicating whether the lock is session-scoped
+     *
+     * @return <code>true</code> if the lock is session-scoped;
+     *         otherwise <code>false</code>
+     */
+    public boolean isSessionScoped() {
+        return sessionScoped;
+    }
+}

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

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

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java Tue Dec  6 08:07:53 2005
@@ -30,7 +30,7 @@
     /**
      * Lock info containing latest information
      */
-    private final LockInfo info;
+    private final AbstractLockInfo info;
 
     /**
      * Node holding lock
@@ -43,7 +43,7 @@
      * @param info lock information
      * @param node node holding lock
      */
-    public LockImpl(LockInfo info, Node node) {
+    public LockImpl(AbstractLockInfo info, Node node) {
         this.info = info;
         this.node = node;
     }

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,973 @@
+/*
+ * 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.commons.collections.map.LinkedMap;
+import org.apache.jackrabbit.core.*;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.observation.EventImpl;
+import org.apache.jackrabbit.core.observation.SynchronousEventListener;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.log4j.Logger;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
+
+
+/**
+ * Provides the functionality needed for locking and unlocking nodes.
+ */
+public class SharedLockManager implements LockManager, SynchronousEventListener {
+
+    /**
+     * Logger
+     */
+    private static final Logger log = Logger.getLogger(SharedLockManager.class);
+
+    /**
+     * Name of the lock file
+     */
+    private static final String LOCKS_FILE = "locks";
+
+    /**
+     * Path map containing all locks at the leaves.
+     */
+    private final PathMap lockMap = new PathMap();
+
+    /**
+     * Lock to path map.
+     */
+    private final ReentrantLock lockMapLock = new ReentrantLock();
+
+    /**
+     * System session
+     */
+    private final SessionImpl session;
+
+    /**
+     * Locks file
+     */
+    private final FileSystemResource locksFile;
+
+    /**
+     * Flag indicating whether automatic saving is disabled.
+     */
+    private boolean savingDisabled;
+
+    /**
+     * Monitor used when modifying content, too, in order to make modifications
+     * in the lock map and modifications in the content atomic.
+     */
+    private final Object contentMonitor = new Object();
+
+    /**
+     * Namespace resolver
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * Create a new instance of this class.
+     *
+     * @param session system session
+     * @param fs      file system for persisting locks
+     * @throws RepositoryException if an error occurs
+     */
+    public SharedLockManager(SessionImpl session, FileSystem fs)
+            throws RepositoryException {
+
+        this.session = session;
+        this.nsResolver = session.getNamespaceResolver();
+        this.locksFile = new FileSystemResource(fs, FileSystem.SEPARATOR + LOCKS_FILE);
+
+        session.getWorkspace().getObservationManager().
+                addEventListener(this, Event.NODE_ADDED | Event.NODE_REMOVED,
+                        "/", true, null, null, true);
+
+        try {
+            if (locksFile.exists()) {
+                load();
+            }
+        } catch (FileSystemException e) {
+            throw new RepositoryException("I/O error while reading locks from '"
+                    + locksFile.getPath() + "'", e);
+        }
+    }
+
+    /**
+     * Close this lock manager. Writes back all changes.
+     */
+    public void close() {
+        save();
+    }
+
+    /**
+     * Read locks from locks file and populate path map
+     */
+    private void load() throws FileSystemException {
+        BufferedReader reader = null;
+
+        try {
+            reader = new BufferedReader(
+                    new InputStreamReader(locksFile.getInputStream()));
+            while (true) {
+                String s = reader.readLine();
+                if (s == null || s.equals("")) {
+                    break;
+                }
+                reapplyLock(LockToken.parse(s));
+            }
+        } catch (IOException e) {
+            throw new FileSystemException("error while reading locks file", e);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e2) {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    /**
+     * Reapply a lock given a lock token that was read from the locks file
+     *
+     * @param lockToken lock token to apply
+     */
+    private void reapplyLock(LockToken lockToken) {
+        try {
+            NodeId id = new NodeId(lockToken.uuid);
+
+            NodeImpl node = (NodeImpl) session.getItemManager().getItem(id);
+            Path path = getPath(node.getId());
+
+            LockInfo info = new LockInfo(lockToken, false,
+                    node.getProperty(QName.JCR_LOCKISDEEP).getBoolean(),
+                    node.getProperty(QName.JCR_LOCKOWNER).getString());
+            info.setLive(true);
+            lockMap.put(path, info);
+        } catch (RepositoryException e) {
+            log.warn("Unable to recreate lock '" + lockToken
+                    + "': " + e.getMessage());
+            log.debug("Root cause: ", e);
+        }
+    }
+
+    /**
+     * Write locks to locks file
+     */
+    private void save() {
+        if (savingDisabled) {
+            return;
+        }
+
+        final ArrayList list = new ArrayList();
+
+        lockMap.traverse(new PathMap.ElementVisitor() {
+            public void elementVisited(PathMap.Element element) {
+                LockInfo info = (LockInfo) element.get();
+                if (!info.sessionScoped) {
+                    list.add(info);
+                }
+            }
+        }, false);
+
+
+        BufferedWriter writer = null;
+
+        try {
+            writer = new BufferedWriter(
+                    new OutputStreamWriter(locksFile.getOutputStream()));
+            for (int i = 0; i < list.size(); i++) {
+                AbstractLockInfo info = (AbstractLockInfo) list.get(i);
+                writer.write(info.lockToken.toString());
+                writer.newLine();
+            }
+        } catch (FileSystemException fse) {
+            log.warn("I/O error while saving locks to '"
+                    + locksFile.getPath() + "': " + fse.getMessage());
+            log.debug("Root cause: ", fse);
+        } catch (IOException ioe) {
+            log.warn("I/O error while saving locks to '"
+                    + locksFile.getPath() + "': " + ioe.getMessage());
+            log.debug("Root cause: ", ioe);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Internal <code>lock</code> implementation that takes the same parameters
+     * as the public method but will not modify content.
+     * @param node node to lock
+     * @param isDeep whether the lock applies to this node only
+     * @param isSessionScoped whether the lock is session scoped
+     * @return lock
+     * @throws LockException       if the node is already locked
+     * @throws RepositoryException if another error occurs
+     */
+    Lock internalLock(NodeImpl node, boolean isDeep, boolean isSessionScoped)
+            throws LockException, RepositoryException {
+
+        SessionImpl session = (SessionImpl) node.getSession();
+        LockInfo info = new LockInfo(new LockToken(node.internalGetUUID()),
+                isSessionScoped, isDeep, session.getUserID());
+
+        acquire();
+
+        try {
+            // check whether node is already locked
+            Path path = getPath(node.getId());
+            PathMap.Element element = lockMap.map(path, false);
+
+            LockInfo other = (LockInfo) element.get();
+            if (other != null) {
+                if (element.hasPath(path)) {
+                    throw new LockException("Node already locked: " + node.safeGetJCRPath());
+                } else if (other.deep) {
+                    throw new LockException("Parent node has deep lock.");
+                }
+            }
+            if (info.deep && element.hasPath(path) &&
+                    element.getChildrenCount() > 0) {
+                throw new LockException("Some child node is locked.");
+            }
+
+            // create lock token
+            info.setLockHolder(session);
+            info.setLive(true);
+            session.addListener(info);
+            session.addLockToken(info.lockToken.toString(), false);
+            lockMap.put(path, info);
+
+            if (!info.sessionScoped) {
+                save();
+            }
+            return new LockImpl(info, node);
+
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * Unlock a node (internal implementation)
+     * @param node node to unlock
+     * @throws LockException       if the node can not be unlocked
+     * @throws RepositoryException if another error occurs
+     */
+    void internalUnlock(NodeImpl node)
+            throws LockException, RepositoryException {
+
+        acquire();
+
+        try {
+            SessionImpl session = (SessionImpl) node.getSession();
+
+            // check whether node is locked by this session
+            PathMap.Element element = lockMap.map(
+                    getPath(node.getId()), true);
+            if (element == null) {
+                throw new LockException("Node not locked: " + node.safeGetJCRPath());
+            }
+            AbstractLockInfo info = (AbstractLockInfo) element.get();
+            if (info == null) {
+                throw new LockException("Node not locked: " + node.safeGetJCRPath());
+            }
+            if (!session.equals(info.getLockHolder())) {
+                throw new LockException("Node not locked by session: " + node.safeGetJCRPath());
+            }
+
+            element.set(null);
+            info.setLive(false);
+
+            if (!info.sessionScoped) {
+                save();
+            }
+
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * Unlock a node given by its info. Invoked when a session logs out and
+     * all session scoped locks of that session must be unlocked.<p>
+     * In order to prevent deadlocks from within the synchronous dispatching of
+     * events, content modifications should not be made from within code
+     * sections that hold monitors. (see #JCR-194)
+     *
+     * @param info lock info
+     */
+    void unlock(LockInfo info) {
+        // if no session currently holds lock, take system session
+        SessionImpl session = info.getLockHolder();
+        if (session == null) {
+            session = this.session;
+        }
+        try {
+            synchronized (contentMonitor) {
+                // get node's path and remove child in path map
+                NodeImpl node = (NodeImpl) session.getItemManager().getItem(
+                        new NodeId(info.getUUID()));
+                Path path = getPath(node.getId());
+
+                acquire();
+
+                try {
+                    PathMap.Element element = lockMap.map(path, true);
+                    if (element != null) {
+                        element.set(null);
+                    }
+                    info.setLive(false);
+                    if (info.sessionScoped) {
+                        save();
+                    }
+
+                } finally {
+                    release();
+                }
+
+                // remove properties in content
+                node.internalSetProperty(QName.JCR_LOCKOWNER, (InternalValue) null);
+                node.internalSetProperty(QName.JCR_LOCKISDEEP, (InternalValue) null);
+                node.save();
+            }
+        } catch (RepositoryException e) {
+            log.warn("Unable to unlock session-scoped lock on node '"
+                    + info.lockToken + "': " + e.getMessage());
+            log.debug("Root cause: ", e);
+        }
+    }
+
+    /**
+     * Return the most appropriate lock information for a node. This is either
+     * the lock info for the node itself, if it is locked, or a lock info for one
+     * of its parents, if that is deep locked.
+     * @return lock info or <code>null</code> if node is not locked
+     * @throws RepositoryException if an error occurs
+     */
+    public AbstractLockInfo getLockInfo(String uuid) throws RepositoryException {
+        acquire();
+
+        try {
+            Path path = getPath(new NodeId(uuid));
+
+            PathMap.Element element = lockMap.map(path, false);
+            AbstractLockInfo info = (AbstractLockInfo) element.get();
+            if (info != null) {
+                if (element.hasPath(path) || info.deep) {
+                    return info;
+                }
+            }
+            return null;
+        } catch (ItemNotFoundException e) {
+            return null;
+        } finally {
+            release();
+        }
+    }
+
+    //----------------------------------------------------------< LockManager >
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock lock(NodeImpl node, boolean isDeep, boolean isSessionScoped)
+            throws LockException, RepositoryException {
+
+        synchronized (contentMonitor) {
+            Lock lock = internalLock(node, isDeep, isSessionScoped);
+
+            // add properties to content
+            node.internalSetProperty(QName.JCR_LOCKOWNER,
+                    InternalValue.create(node.getSession().getUserID()));
+            node.internalSetProperty(QName.JCR_LOCKISDEEP,
+                    InternalValue.create(isDeep));
+            node.save();
+
+            return lock;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock getLock(NodeImpl node)
+            throws LockException, RepositoryException {
+
+        acquire();
+
+        try {
+            SessionImpl session = (SessionImpl) node.getSession();
+            Path path = getPath(node.getId());
+
+            PathMap.Element element = lockMap.map(path, false);
+            AbstractLockInfo info = (AbstractLockInfo) element.get();
+            if (info == null) {
+                throw new LockException("Node not locked: " + node.safeGetJCRPath());
+            }
+            if (element.hasPath(path) || info.deep) {
+                Node lockHolder = (Node) session.getItemManager().getItem(
+                        new NodeId(info.getUUID()));
+                return new LockImpl(info, lockHolder);
+            } else {
+                throw new LockException("Node not locked: " + node.safeGetJCRPath());
+            }
+        } catch (ItemNotFoundException e) {
+            throw new LockException("Node not locked: " + node.safeGetJCRPath());
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * In order to prevent deadlocks from within the synchronous dispatching of
+     * events, content modifications should not be made from within code
+     * sections that hold monitors. (see #JCR-194)
+     */
+    public void unlock(NodeImpl node)
+            throws LockException, RepositoryException {
+
+        synchronized (contentMonitor) {
+            internalUnlock(node);
+
+            // remove properties in content
+            node.internalSetProperty(QName.JCR_LOCKOWNER, (InternalValue) null);
+            node.internalSetProperty(QName.JCR_LOCKISDEEP, (InternalValue) null);
+            node.save();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean holdsLock(NodeImpl node) throws RepositoryException {
+        acquire();
+
+        try {
+            PathMap.Element element = lockMap.map(getPath(node.getId()), true);
+            if (element == null) {
+                return false;
+            }
+            return element.get() != null;
+        } catch (ItemNotFoundException e) {
+            return false;
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isLocked(NodeImpl node) throws RepositoryException {
+        acquire();
+
+        try {
+            Path path = getPath(node.getId());
+
+            PathMap.Element element = lockMap.map(path, false);
+            AbstractLockInfo info = (AbstractLockInfo) element.get();
+            if (info == null) {
+                return false;
+            }
+            if (element.hasPath(path)) {
+                return true;
+            } else {
+                return info.deep;
+            }
+        } catch (ItemNotFoundException e) {
+            return false;
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void checkLock(NodeImpl node)
+            throws LockException, RepositoryException {
+
+        SessionImpl session = (SessionImpl) node.getSession();
+        checkLock(getPath(node.getId()), session);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void checkLock(Path path, Session session)
+            throws LockException, RepositoryException {
+
+        PathMap.Element element = lockMap.map(path, false);
+        AbstractLockInfo info = (AbstractLockInfo) element.get();
+        if (info != null) {
+            if (element.hasPath(path) || info.deep) {
+                if (!session.equals(info.getLockHolder())) {
+                    throw new LockException("Node locked.");
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lockTokenAdded(SessionImpl session, String lt) {
+        try {
+            LockToken lockToken = LockToken.parse(lt);
+
+            NodeImpl node = (NodeImpl) this.session.getItemManager().
+                    getItem(new NodeId(lockToken.uuid));
+            PathMap.Element element = lockMap.map(node.getPrimaryPath(), true);
+            if (element != null) {
+                AbstractLockInfo info = (AbstractLockInfo) element.get();
+                if (info != null) {
+                    if (info.getLockHolder() == null) {
+                        info.setLockHolder(session);
+                    } else {
+                        log.warn("Adding lock token has no effect: "
+                                + "lock already held by other session.");
+                    }
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("Bad lock token: " + e.getMessage());
+        } catch (RepositoryException e) {
+            log.warn("Unable to set lock holder: " + e.getMessage());
+            log.debug("Root cause: ", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lockTokenRemoved(SessionImpl session, String lt) {
+        try {
+            LockToken lockToken = LockToken.parse(lt);
+
+            NodeImpl node = (NodeImpl) this.session.getItemManager().
+                    getItem(new NodeId(lockToken.uuid));
+            PathMap.Element element = lockMap.map(node.getPrimaryPath(), true);
+            if (element != null) {
+                AbstractLockInfo info = (AbstractLockInfo) element.get();
+                if (info != null) {
+                    if (session.equals(info.getLockHolder())) {
+                        info.setLockHolder(null);
+                    } else {
+                        log.warn("Removing lock token has no effect: "
+                                + "lock held by other session.");
+                    }
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            log.warn("Bad lock token: " + e.getMessage());
+        } catch (RepositoryException e) {
+            log.warn("Unable to reset lock holder: " + e.getMessage());
+            log.debug("Root cause: ", e);
+        }
+    }
+
+    /**
+     * Return the path of an item given its id. This method will lookup the
+     * item inside the systme session.
+     */
+    private Path getPath(ItemId id) throws RepositoryException {
+        return session.getHierarchyManager().getPath(id);
+    }
+
+    /**
+     * Acquire lock on the lock map.
+     */
+    private void acquire() {
+        for (;;) {
+            try {
+                lockMapLock.acquire();
+                break;
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    /**
+     * Release lock on the lock map.
+     */
+    private void release() {
+        lockMapLock.release();
+    }
+
+    /**
+     * Start an update operation. This will acquire the lock on the lock map
+     * and disable saving the lock map file.
+     */
+    public void beginUpdate() {
+        acquire();
+        savingDisabled = true;
+    }
+
+    /**
+     * End an update operation. This will save the lock map file and release
+     * the lock on the lock map.
+     */
+    public void endUpdate() {
+        savingDisabled = false;
+        save();
+        release();
+    }
+
+    /**
+     * Cancel an update operation. This will release the lock on the lock map.
+     */
+    public void cancelUpdate() {
+        savingDisabled = false;
+        release();
+    }
+
+    //----------------------------------------------< SynchronousEventListener >
+
+    /**
+     * Internal event class that holds old and new paths for moved nodes
+     */
+    private class HierarchyEvent {
+
+        /**
+         * UUID recorded in event
+         */
+        public final String uuid;
+
+        /**
+         * Path recorded in event
+         */
+        public final Path path;
+
+        /**
+         * Old path in move operation
+         */
+        private Path oldPath;
+
+        /**
+         * New path in move operation
+         */
+        private Path newPath;
+
+        /**
+         * Event type, may be {@link Event#NODE_ADDED},
+         * {@link Event#NODE_REMOVED} or a combination of both
+         */
+        private int type;
+
+        /**
+         * Create a new instance of this class.
+         *
+         * @param uuid uuid
+         * @param path path
+         * @param type event type
+         */
+        public HierarchyEvent(String uuid, Path path, int type) {
+            this.uuid = uuid;
+            this.path = path;
+            this.type = type;
+        }
+
+        /**
+         * Merge this event with another event. The result will be stored in
+         * this event
+         *
+         * @param event other event to merge with
+         */
+        public void merge(HierarchyEvent event) {
+            type |= event.type;
+            if (event.type == Event.NODE_ADDED) {
+                newPath = event.path;
+                oldPath = path;
+            } else {
+                oldPath = event.path;
+                newPath = path;
+            }
+        }
+
+        /**
+         * Return the event type. May be {@link Event#NODE_ADDED},
+         * {@link Event#NODE_REMOVED} or a combination of both.\
+         *
+         * @return event type
+         */
+        public int getType() {
+            return type;
+        }
+
+        /**
+         * Return the old path if this is a move operation
+         *
+         * @return old path
+         */
+        public Path getOldPath() {
+            return oldPath;
+        }
+
+        /**
+         * Return the new path if this is a move operation
+         *
+         * @return new path
+         */
+        public Path getNewPath() {
+            return newPath;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onEvent(EventIterator events) {
+        Iterator iter = consolidateEvents(events);
+        while (iter.hasNext()) {
+            HierarchyEvent event = (HierarchyEvent) iter.next();
+            switch (event.type) {
+                case Event.NODE_ADDED:
+                    nodeAdded(event.path);
+                    break;
+                case Event.NODE_REMOVED:
+                    nodeRemoved(event.path);
+                    break;
+                case Event.NODE_ADDED | Event.NODE_REMOVED:
+                    nodeMoved(event.getOldPath(), event.getNewPath());
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Consolidate an event iterator obtained from observation, merging
+     * add and remove operations on nodes with the same UUID into a move
+     * operation.
+     */
+    private Iterator consolidateEvents(EventIterator events) {
+        LinkedMap eventMap = new LinkedMap();
+
+        while (events.hasNext()) {
+            EventImpl event = (EventImpl) events.nextEvent();
+            HierarchyEvent he;
+
+            try {
+                he = new HierarchyEvent(event.getChildUUID(),
+                        Path.create(event.getPath(), nsResolver, true),
+                        event.getType());
+            } catch (MalformedPathException e) {
+                log.info("Unable to get event's path: " + e.getMessage());
+                continue;
+            } catch (RepositoryException e) {
+                log.info("Unable to get event's path: " + e.getMessage());
+                continue;
+            }
+
+            HierarchyEvent heExisting = (HierarchyEvent) eventMap.get(he.uuid);
+            if (heExisting != null) {
+                heExisting.merge(he);
+            } else {
+                eventMap.put(he.uuid, he);
+            }
+        }
+        return eventMap.values().iterator();
+    }
+
+    /**
+     * Refresh a non-empty path element whose children might have changed
+     * its position.
+     */
+    private void refresh(PathMap.Element element) {
+        final ArrayList infos = new ArrayList();
+        boolean needsSave = false;
+
+        // save away non-empty children
+        element.traverse(new PathMap.ElementVisitor() {
+            public void elementVisited(PathMap.Element element) {
+                LockInfo info = (LockInfo) element.get();
+                infos.add(info);
+            }
+        }, false);
+
+        // remove all children
+        element.removeAll();
+
+        // now re-insert at appropriate location or throw away if node
+        // does no longer exist
+        for (int i = 0; i < infos.size(); i++) {
+            LockInfo info = (LockInfo) infos.get(i);
+            try {
+                NodeImpl node = (NodeImpl) session.getItemManager().getItem(
+                        new NodeId(info.getUUID()));
+                lockMap.put(node.getPrimaryPath(), info);
+            } catch (RepositoryException e) {
+                info.setLive(false);
+                if (!info.sessionScoped) {
+                    needsSave = true;
+                }
+            }
+        }
+
+        // save if required
+        if (needsSave) {
+            save();
+        }
+    }
+
+    /**
+     * Invoked when some node has been added. If the parent of that node
+     * exists, shift all name siblings of the new node having an index greater
+     * or equal.
+     *
+     * @param path path of added node
+     */
+    private void nodeAdded(Path path) {
+        acquire();
+
+        try {
+            PathMap.Element parent = lockMap.map(path.getAncestor(1), true);
+            if (parent != null) {
+                refresh(parent);
+            }
+        } catch (PathNotFoundException e) {
+            log.warn("Unable to determine path of added node's parent.", e);
+            return;
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * Invoked when some node has been moved. Relink the child inside our
+     * map to the new parent.
+     *
+     * @param oldPath old path
+     * @param newPath new path
+     */
+    private void nodeMoved(Path oldPath, Path newPath) {
+        acquire();
+
+        try {
+            PathMap.Element parent = lockMap.map(oldPath.getAncestor(1), true);
+            if (parent != null) {
+                refresh(parent);
+            }
+        } catch (PathNotFoundException e) {
+            log.warn("Unable to determine path of moved node's parent.", e);
+            return;
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * Invoked when some node has been removed. Remove the child from our
+     * path map. Disable all locks contained in that subtree.
+     *
+     * @param path path of removed node
+     */
+    private void nodeRemoved(Path path) {
+        acquire();
+
+        try {
+            PathMap.Element parent = lockMap.map(path.getAncestor(1), true);
+            if (parent != null) {
+                refresh(parent);
+            }
+        } catch (PathNotFoundException e) {
+            log.warn("Unable to determine path of removed node's parent.", e);
+            return;
+        } finally {
+            release();
+        }
+    }
+
+    /**
+     * Contains information about a lock and gets placed inside the child
+     * information of a {@link org.apache.jackrabbit.core.PathMap}.
+     */
+    class LockInfo extends AbstractLockInfo implements SessionListener {
+
+        /**
+         * Create a new instance of this class.
+         *
+         * @param lockToken     lock token
+         * @param sessionScoped whether lock token is session scoped
+         * @param deep          whether lock is deep
+         * @param lockOwner     owner of lock
+         */
+        public LockInfo(LockToken lockToken, boolean sessionScoped,
+                        boolean deep, String lockOwner) {
+            super(lockToken, sessionScoped, deep, lockOwner);
+        }
+
+        /**
+         * {@inheritDoc}
+         * <p/>
+         * When the owning session is logging out, we have to perform some
+         * operations depending on the lock type.
+         * (1) If the lock was session-scoped, we unlock the node.
+         * (2) If the lock was open-scoped, we remove the lock token
+         *     from the session and set the lockHolder field to <code>null</code>.
+         */
+        public void loggingOut(SessionImpl session) {
+            if (live) {
+                if (sessionScoped) {
+                    unlock(this);
+                } else {
+                    if (session.equals(lockHolder)) {
+                        session.removeLockToken(lockToken.toString());
+                        lockHolder = null;
+                    }
+                }
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void loggedOut(SessionImpl session) {
+        }
+    }
+}

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

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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java?rev=354456&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java Tue Dec  6 08:07:53 2005
@@ -0,0 +1,419 @@
+/*
+ * 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.value.InternalValue;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.log4j.Logger;
+
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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 TxLockManager implements LockManager {
+
+    /**
+     * Logger instance for this class
+     */
+    private static final Logger log = Logger.getLogger(TxLockManager.class);
+
+    /**
+     * Shared lock manager.
+     */
+    private final SharedLockManager shared;
+
+    /**
+     * Map of locked nodes, indexed by their (internal) id.
+     */
+    private final Map lockedNodesMap = new HashMap();
+
+    /**
+     * Map of unlocked nodes, indexed by their (internal) id.
+     */
+    private final Map unlockedNodesMap = new HashMap();
+
+    /**
+     * List of lock/unlock operations.
+     */
+    private final List operations = new ArrayList();
+
+    /**
+     * Operation index.
+     */
+    private int opIndex;
+
+    /**
+     * Create a new instance of this class. Takes a <code>SharedLockManager</code>
+     * as parameter.
+     * @param shared shared lock manager
+     */
+    public TxLockManager(SharedLockManager shared) {
+        this.shared = shared;
+    }
+
+    /**
+     * Clear this object, releasing all its resources.
+     */
+    public void clear() {
+        lockedNodesMap.clear();
+        unlockedNodesMap.clear();
+        operations.clear();
+        opIndex = 0;
+    }
+
+    //----------------------------------------------------------< LockManager >
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock lock(NodeImpl node, boolean isDeep, boolean isSessionScoped)
+            throws LockException, RepositoryException {
+
+        String uuid = node.internalGetUUID();
+
+        // check negative set first
+        LockInfo info = (LockInfo) unlockedNodesMap.get(uuid);
+        if (info != null) {
+
+            // if settings are compatible, this is effectively a no-op
+            if (info.deep == isDeep && info.sessionScoped == isSessionScoped) {
+                unlockedNodesMap.remove(uuid);
+                operations.remove(info);
+                return getLock(node);
+            }
+        }
+
+        // verify node is not already locked.
+        if (isLocked(node)) {
+            throw new LockException("Node locked.");
+        }
+
+        // create a new lock info for this node
+        info = new LockInfo(node, new LockToken(node.internalGetUUID()),
+                isSessionScoped, isDeep, node.getSession().getUserID());
+        SessionImpl session = (SessionImpl) node.getSession();
+        info.setLockHolder(session);
+        info.setLive(true);
+        session.addLockToken(info.lockToken.toString(), false);
+        lockedNodesMap.put(uuid, info);
+        operations.add(info);
+
+        // add properties to content
+        node.internalSetProperty(QName.JCR_LOCKOWNER,
+                InternalValue.create(node.getSession().getUserID()));
+        node.internalSetProperty(QName.JCR_LOCKISDEEP,
+                InternalValue.create(info.deep));
+        node.save();
+        return new LockImpl(info, node);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Lock getLock(NodeImpl node) throws LockException, RepositoryException {
+        LockInfo info = getLockInfo(node);
+        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 LockImpl(info, holder);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unlock(NodeImpl node) throws LockException, RepositoryException {
+        String uuid = node.internalGetUUID();
+
+        // check positive set first
+        LockInfo info = (LockInfo) lockedNodesMap.get(uuid);
+        if (info != null) {
+            lockedNodesMap.remove(uuid);
+            operations.remove(info);
+            info.setLive(false);
+        } else {
+            info = getLockInfo(node);
+            if (info == null || info.getUUID() != uuid) {
+                throw new LockException("Node not locked.");
+            } else if (info.getLockHolder() != node.getSession()) {
+                throw new LockException("Node not locked by this session.");
+            }
+            unlockedNodesMap.put(uuid, info);
+            info.setUnlock(true);
+            operations.add(info);
+        }
+
+        // remove properties in content
+        node.internalSetProperty(QName.JCR_LOCKOWNER, (InternalValue) null);
+        node.internalSetProperty(QName.JCR_LOCKISDEEP, (InternalValue) null);
+        node.save();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean holdsLock(NodeImpl node) throws RepositoryException {
+        String uuid = node.internalGetUUID();
+
+        if (lockedNodesMap.containsKey(uuid)) {
+            return true;
+        }
+        return shared.holdsLock(node);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isLocked(NodeImpl node) throws RepositoryException {
+        LockInfo info = getLockInfo(node);
+        return info != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void checkLock(NodeImpl node) throws LockException, RepositoryException {
+        LockInfo info = getLockInfo(node);
+        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) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lockTokenRemoved(SessionImpl session, String lt) {
+    }
+
+    //-----------------------------------------------------------< transaction >
+
+    /**
+     * Prepare transaction. This will lock the shared lock manager and feed
+     * all locks.
+     */
+    public void prepare() throws TransactionException {
+        if (operations.isEmpty()) {
+            return;
+        }
+
+        shared.beginUpdate();
+
+        try {
+            while (opIndex < operations.size()) {
+                try {
+                    LockInfo info = (LockInfo) operations.get(opIndex);
+                    info.update();
+                } catch (RepositoryException e) {
+                    throw new TransactionException("Unable to update.", e);
+                }
+                opIndex++;
+            }
+        } finally {
+            if (opIndex < operations.size()) {
+                while (opIndex > 0) {
+                    try {
+                        LockInfo info = (LockInfo) operations.get(opIndex - 1);
+                        info.undo();
+                    } catch (RepositoryException e) {
+                        log.error("Unable to undo lock operation.", e);
+                    }
+                    opIndex--;
+                }
+            }
+        }
+    }
+
+    /**
+     * Commit transaction. This will finish the update and unlock the shared
+     * lock manager.
+     */
+    public void commit() {
+        if (!operations.isEmpty()) {
+            shared.endUpdate();
+        }
+        clear();
+    }
+
+    /**
+     * Rollback transaction. This will undo all updates and unlock the shared
+     * lock manager.
+     */
+    public void rollback() {
+        if (!operations.isEmpty() && opIndex == operations.size()) {
+            while (opIndex > 0) {
+                try {
+                    LockInfo info = (LockInfo) operations.get(opIndex - 1);
+                    info.undo();
+                } catch (RepositoryException e) {
+                    log.error("Unable to undo lock operation.", e);
+                }
+                opIndex--;
+            }
+            shared.cancelUpdate();
+        }
+        clear();
+    }
+
+    //--------------------------------------------------------------< internal >
+
+    /**
+     * Return the most appropriate lock information for a node. This is either
+     * the lock info for the node itself, if it is locked, or a lock info for
+     * one of its parents, if that one is deep locked.
+     * @param node node
+     * @return LockInfo lock info or <code>null</code> if node is not locked
+     * @throws RepositoryException if an error occurs
+     */
+    private LockInfo getLockInfo(NodeImpl node) throws RepositoryException {
+        String uuid = node.internalGetUUID();
+
+        // check negative set
+        if (unlockedNodesMap.containsKey(uuid)) {
+            return null;
+        }
+
+        // check positive set, iteratively ascending in hierarchy
+        if (!lockedNodesMap.isEmpty()) {
+            NodeImpl current = node;
+            for (;;) {
+                LockInfo info = (LockInfo) lockedNodesMap.get(current.internalGetUUID());
+                if (info != null) {
+                    if (info.getUUID() == uuid || info.deep) {
+                        return info;
+                    }
+                    break;
+                }
+                if (current.getDepth() == 0) {
+                    break;
+                }
+                current = (NodeImpl) current.getParent();
+            }
+        }
+
+        // ask parent and return a copy of its information
+        AbstractLockInfo info = shared.getLockInfo(uuid);
+        if (info == null) {
+            return null;
+        }
+        return new LockInfo(node, info.lockToken, info.sessionScoped,
+                info.deep, info.lockOwner);
+    }
+
+    /**
+     * Information about a lock used inside transactions.
+     */
+    class LockInfo extends AbstractLockInfo {
+
+        /**
+         * Node being locked/unlocked.
+         */
+        private final NodeImpl node;
+
+        /**
+         * Flag indicating whether this info belongs to a unlock operation.
+         */
+        private boolean isUnlock;
+
+        /**
+         * Create a new instance of this class.
+         * @param lockToken     lock token
+         * @param sessionScoped whether lock token is session scoped
+         * @param deep          whether lock is deep
+         * @param lockOwner     owner of lock
+         */
+        public LockInfo(NodeImpl node, LockToken lockToken,
+                        boolean sessionScoped, boolean deep, String lockOwner) {
+
+            super(lockToken, sessionScoped, deep, lockOwner);
+
+            this.node = node;
+        }
+
+        /**
+         * Return a flag indicating whether this info belongs to a unlock operation.
+         * @return <code>true</code> if this info belongs to an unlock operation;
+         *         otherwise <code>false</code>
+         */
+        public boolean isUnlock() {
+            return isUnlock;
+        }
+
+        /**
+         * Set a flag indicating whether this info belongs to an unlock operation.
+         * @param isUnlock <code>true</code> if this info belongs to an unlock operation;
+         *                 otherwise <code>false</code>
+         */
+        public void setUnlock(boolean isUnlock) {
+            this.isUnlock = isUnlock;
+        }
+
+        /**
+         * Do operation.
+         */
+        public void update() throws LockException, RepositoryException {
+            if (isUnlock) {
+                shared.internalUnlock(node);
+            } else {
+                shared.internalLock(node, deep, sessionScoped);
+            }
+        }
+
+        /**
+         * Undo operation.
+         */
+        public void undo() throws LockException, RepositoryException {
+            if (isUnlock) {
+                shared.internalLock(node, deep, sessionScoped);
+            } else {
+                shared.internalUnlock(node);
+            }
+        }
+    }
+}

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

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

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

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.java?rev=354456&r1=354455&r2=354456&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.java Tue Dec  6 08:07:53 2005
@@ -18,6 +18,8 @@
 
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.WorkspaceImpl;
+import org.apache.jackrabbit.core.TransactionContext;
+import org.apache.jackrabbit.core.TransactionException;
 import org.apache.log4j.Logger;
 
 import javax.jcr.ReferentialIntegrityException;
@@ -30,12 +32,12 @@
 public class TransactionalItemStateManager extends LocalItemStateManager {
 
     /**
-     * Logger instance
+     * Logger instance.
      */
     private static Logger log = Logger.getLogger(TransactionalItemStateManager.class);
 
     /**
-     * Known attribute name
+     * Known attribute name inside the {@link TransactionContext}.
      */
     private static final String ATTRIBUTE_CHANGE_LOG = "ChangeLog";
 
@@ -50,7 +52,7 @@
     };
 
     /**
-     * Current transactional change log
+     * Current instance-local change log
      */
     private transient ChangeLog txLog;
 
@@ -59,45 +61,41 @@
      *
      * @param sharedStateMgr shared state manager
      */
-    public TransactionalItemStateManager(SharedItemStateManager sharedStateMgr, WorkspaceImpl wspImpl) {
+    public TransactionalItemStateManager(SharedItemStateManager sharedStateMgr,
+                                         WorkspaceImpl wspImpl) {
         super(sharedStateMgr, wspImpl);
     }
 
     /**
-     * Set transaction context.
-     *
-     * @param tx transaction context.
-     */
-    public void setTransactionContext(TransactionContext tx) {
-        txLog = null;
-
-        if (tx != null) {
-            txLog = (ChangeLog) tx.getAttribute(ATTRIBUTE_CHANGE_LOG);
-            if (txLog == null) {
-                txLog = new ChangeLog();
-                tx.setAttribute(ATTRIBUTE_CHANGE_LOG, txLog);
-            }
+     * Set transactional change log to use.
+     * @param txLog change log, may be <code>null</code>.
+     * @param threadLocal if <code>true</code> set thread-local change log;
+     *                    otherwise set instance-local change log
+     */
+    public void setChangeLog(ChangeLog txLog, boolean threadLocal) {
+        if (threadLocal) {
+            ((CommitLog) commitLog.get()).setChanges(txLog);
+        } else {
+            this.txLog = txLog;
         }
     }
 
     /**
-     * Prepare a transaction
-     *
-     * @param tx transaction context
+     * Prepare a transaction.
      * @throws TransactionException if an error occurs
      */
-    public void prepare(TransactionContext tx) throws TransactionException {
-        ChangeLog changeLog = (ChangeLog) tx.getAttribute(ATTRIBUTE_CHANGE_LOG);
-        if (changeLog != null) {
+    public void prepare() throws TransactionException {
+        ChangeLog txLog = ((CommitLog) commitLog.get()).getChanges();
+        if (txLog != null) {
             try {
-                sharedStateMgr.checkReferentialIntegrity(changeLog);
+                sharedStateMgr.checkReferentialIntegrity(txLog);
             } catch (ReferentialIntegrityException rie) {
                 log.error(rie);
-                changeLog.undo(sharedStateMgr);
+                txLog.undo(sharedStateMgr);
                 throw new TransactionException("Unable to prepare transaction.", rie);
             } catch (ItemStateException ise) {
                 log.error(ise);
-                changeLog.undo(sharedStateMgr);
+                txLog.undo(sharedStateMgr);
                 throw new TransactionException("Unable to prepare transaction.", ise);
             }
         }
@@ -105,44 +103,34 @@
 
     /**
      * Commit changes made within a transaction
-     *
-     * @param tx transaction context
      * @throws TransactionException if an error occurs
      */
-    public void commit(TransactionContext tx) throws TransactionException {
-        ChangeLog changeLog = (ChangeLog) tx.getAttribute(ATTRIBUTE_CHANGE_LOG);
-        if (changeLog != null) {
+    public void commit() throws TransactionException {
+        ChangeLog txLog = ((CommitLog) commitLog.get()).getChanges();
+        if (txLog != null) {
             try {
-                // set changeLog in ThreadLocal
-                ((CommitLog) commitLog.get()).setChanges(changeLog);
-                super.update(changeLog);
+                super.update(txLog);
             } catch (ReferentialIntegrityException rie) {
                 log.error(rie);
-                changeLog.undo(sharedStateMgr);
+                txLog.undo(sharedStateMgr);
                 throw new TransactionException("Unable to commit transaction.", rie);
             } catch (ItemStateException ise) {
                 log.error(ise);
-                changeLog.undo(sharedStateMgr);
+                txLog.undo(sharedStateMgr);
                 throw new TransactionException("Unable to commit transaction.", ise);
-            } finally {
-                ((CommitLog) commitLog.get()).setChanges(null);
             }
-            changeLog.reset();
-            tx.notifyCommitted();
+            txLog.reset();
         }
     }
 
     /**
      * Rollback changes made within a transaction
-     *
-     * @param tx transaction context
      */
-    public void rollback(TransactionContext tx) {
-        ChangeLog changeLog = (ChangeLog) tx.getAttribute(ATTRIBUTE_CHANGE_LOG);
-        if (changeLog != null) {
-            changeLog.undo(sharedStateMgr);
+    public void rollback() {
+        ChangeLog txLog = ((CommitLog) commitLog.get()).getChanges();
+        if (txLog != null) {
+            txLog.undo(sharedStateMgr);
         }
-        tx.notifyRolledBack();
     }
 
     //-----------------------------------------------------< ItemStateManager >