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

svn commit: r365558 [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/ test/java/org/apache/jackrabbit/core/

Author: dpfister
Date: Mon Jan  2 23:22:25 2006
New Revision: 365558

URL: http://svn.apache.org/viewcvs?rev=365558&view=rev
Log:
Locking and XA
- Introduce internal XA resources
- Add rollback thread that runs when transaction timeout has expired
- Make lock's live attribute transaction-aware
- Make NodeImpl's internalSetProperty protected again 


Added:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XAWorkspace.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java   (with props)
Removed:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/SharedLockManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/TxLockManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransactionalItemStateManager.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/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/TransactionContext.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/AbstractLockInfo.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java Mon Jan  2 23:22:25 2006
@@ -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;
+
+/**
+ * Interface implemented by resources that provide XA functionality.
+ */
+public interface InternalXAResource {
+
+    /**
+     * Associate this resource with a transaction. All further operations on
+     * the object should be interpreted as part of this transaction and changes
+     * recorded in some attribute of the transaction context.
+     * @param tx transaction context, if <code>null</code> disassociate
+     */
+    public void associate(TransactionContext tx);
+
+    /**
+     * Prepare transaction. The transaction is identified by a transaction
+     * context. 
+     * @param tx transaction context
+     * @throws TransactionException if an error occurs
+     */
+    public void prepare(TransactionContext tx) throws TransactionException;
+
+    /**
+     * Commit transaction. The transaction is identified by a transaction
+     * context. If the method throws, other resources get their changes
+     * rolled back.
+     * @param tx transaction context
+     * @throws TransactionException if an error occurs
+     */
+    public void commit(TransactionContext tx) throws TransactionException;
+
+    /**
+     * Rollback transaction. The transaction is identified by a transaction
+     * context.
+     * @param tx transaction context.
+     */
+    public void rollback(TransactionContext tx) throws TransactionException;
+}

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

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

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

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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core;
+package org.apache.jackrabbit.core
 
 import org.apache.jackrabbit.BaseException;
 import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
@@ -42,6 +42,7 @@
 import org.apache.jackrabbit.core.version.VersionHistoryImpl;
 import org.apache.jackrabbit.core.version.VersionImpl;
 import org.apache.jackrabbit.core.version.VersionSelector;
+import org.apache.jackrabbit.core.lock.LockManager;
 import org.apache.jackrabbit.name.IllegalNameException;
 import org.apache.jackrabbit.name.MalformedPathException;
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
@@ -1248,37 +1249,14 @@
      * Note that no type conversion is being performed, i.e. it's the caller's
      * responsibility to make sure that the type of the given value is compatible
      * with the specified property's definition.
-     * <p/>
-     * <strong>Important:</strong> This method is public in order to make it
-     * accessible from internal code located in subpackages, i.e. it should
-     * never be called from an application directly!!!
-     *
      * @param name
      * @param value
      * @return
      * @throws ValueFormatException
      * @throws RepositoryException
      */
-    public Property internalSetProperty(QName name, InternalValue value)
+    protected Property internalSetProperty(QName name, InternalValue value)
             throws ValueFormatException, RepositoryException {
-        /**
-         * todo FIXME internalSetProperty being public is a potential security risk
-         * the following code snippet is a workaround that verifies that the
-         * caller is located in the same package (i.e. 'core') or a subpackage
-         * thereof; it is tested with jackrabbit being run standalone; however
-         * i commented it out as i am not sure whether there are potential
-         * problems/side effects with other setups/environments.
-         */
-/*
-        CallContext ctx = new CallContext();
-        String calledFromPackage = ctx.getCaller().getPackage().getName();
-        String thisPackage = NodeImpl.class.getPackage().getName();
-        // check if we're called from current package (i.e. 'core')
-        // or a subpackage thereof
-        if (!calledFromPackage.startsWith(thisPackage)) {
-            throw new SecurityException("illegal method invokation");
-        }
-*/
         int type;
         if (value == null) {
             type = PropertyType.UNDEFINED;
@@ -3745,7 +3723,18 @@
 
         checkLockable();
 
-        return session.getLockManager().lock(this, isDeep, isSessionScoped);
+        LockManager lockMgr = session.getLockManager();
+        synchronized (lockMgr) {
+            Lock lock = lockMgr.lock(this, isDeep, isSessionScoped);
+
+            // add properties to content
+            internalSetProperty(QName.JCR_LOCKOWNER,
+                    InternalValue.create(getSession().getUserID()));
+            internalSetProperty(QName.JCR_LOCKISDEEP,
+                    InternalValue.create(isDeep));
+            save();
+            return lock;
+        }
     }
 
     /**
@@ -3781,7 +3770,16 @@
         }
 
         checkLockable();
-        session.getLockManager().unlock(this);
+
+        LockManager lockMgr = session.getLockManager();
+        synchronized (lockMgr) {
+            lockMgr.unlock(this);
+
+            // remove properties in content
+            internalSetProperty(QName.JCR_LOCKOWNER, (InternalValue) null);
+            internalSetProperty(QName.JCR_LOCKISDEEP, (InternalValue) null);
+            save();
+        }
     }
 
     /**

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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -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.SharedLockManager;
+import org.apache.jackrabbit.core.lock.LockManagerImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
 import org.apache.jackrabbit.core.nodetype.virtual.VirtualNodeTypeStateManager;
@@ -1170,7 +1170,7 @@
         /**
          * Lock manager
          */
-        private SharedLockManager lockMgr;
+        private LockManagerImpl lockMgr;
 
         /**
          * Creates a new <code>WorkspaceInfo</code> based on the given
@@ -1315,7 +1315,7 @@
          */
         synchronized LockManager getLockManager() throws RepositoryException {
             if (lockMgr == null) {
-                lockMgr = new SharedLockManager(getSystemSession(), config.getFileSystem());
+                lockMgr = new LockManagerImpl(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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -22,7 +22,6 @@
 import org.apache.jackrabbit.core.nodetype.NodeDefinitionImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
-import org.apache.jackrabbit.core.observation.EventStateCollection;
 import org.apache.jackrabbit.core.security.AMContext;
 import org.apache.jackrabbit.core.security.AccessManager;
 import org.apache.jackrabbit.core.security.AuthContext;
@@ -244,7 +243,7 @@
         hierMgr = itemStateMgr.getHierarchyMgr();
         itemMgr = createItemManager(itemStateMgr, hierMgr);
         accessMgr = createAccessManager(subject, hierMgr);
-        versionMgr = rep.getVersionManager();
+        versionMgr = createVersionManager(rep);
     }
 
     /**
@@ -276,7 +275,6 @@
 
     /**
      * Create the item manager.
-     *
      * @return item manager
      */
     protected ItemManager createItemManager(SessionItemStateManager itemStateMgr,
@@ -286,6 +284,17 @@
     }
 
     /**
+     * Create the version manager. If we are not using XA, we may safely use
+     * the repository version manager.
+     * @return version manager
+     */
+    protected VersionManager createVersionManager(RepositoryImpl rep)
+            throws RepositoryException {
+        
+        return rep.getVersionManager();
+    }
+
+    /**
      * Create the access manager.
      *
      * @return access manager
@@ -1275,11 +1284,8 @@
     }
 
     /**
-     * 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 the lock manager for this session.
      * @return lock manager for this session
-     * @throws RepositoryException if an error occurs
      */
     public LockManager getLockManager() throws RepositoryException {
         return wsp.getLockManager();

Modified: 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=365558&r1=365557&r2=365558&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/TransactionContext.java Mon Jan  2 23:22:25 2006
@@ -16,23 +16,59 @@
  */
 package org.apache.jackrabbit.core;
 
-import java.util.ArrayList;
+import org.apache.log4j.Logger;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.Status;
 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.
+ * explictely demarcate transcation boundaries. After having been prepared,
+ * starts a thread that rolls back the transaction if some time passes without
+ * any further action. This will guarantee that global objects locked by one
+ * of the resources' {@link InternalXAResource#prepare} method, are eventually
+ * unlocked.
  */
-public class TransactionContext {
+public class TransactionContext implements Runnable {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger log = Logger.getLogger(TransactionContext.class);
+
+    /**
+     * Transactional resources.
+     */
+    private final InternalXAResource[] resources;
 
     /**
-     * Transaction attributes
+     * Timeout, in seconds.
+     */
+    private final int timeout;
+
+    /**
+     * Transaction attributes.
      */
     private final Map attributes = new HashMap();
 
     /**
+     * Status.
+     */
+    private int status;
+
+    /**
+     * Create a new instance of this class.
+     * @param resources transactional resources
+     * @param timeout timeout, in seconds
+     */
+    public TransactionContext(InternalXAResource[] resources, int timeout) {
+        this.resources = resources;
+        this.timeout = timeout;
+    }
+
+    /**
      * Set an attribute on this transaction. If the value specified is
      * <code>null</code>, it is semantically equivalent to
      * {@link #removeAttribute}.
@@ -65,5 +101,136 @@
      */
     public void removeAttribute(String name) {
         attributes.remove(name);
+    }
+
+    /**
+     * Prepare the transaction identified by this context. Prepares changes on
+     * all resources. If some resource reports an error on prepare,
+     * automatically rollback changes on all other resources. Throw exception
+     * at the end if errors were found.
+     * @throws XAException if an error occurs
+     */
+    public synchronized void prepare() throws XAException {
+        status = Status.STATUS_PREPARING;
+
+        TransactionException txe = null;
+        for (int i = 0; i < resources.length; i++) {
+            InternalXAResource resource = resources[i];
+            if (txe != null) {
+                try {
+                    resource.rollback(this);
+                } catch (TransactionException e) {
+                    log.warn("Unable to rollback changes on " + resource, e);
+                }
+            } else {
+                try {
+                    resource.prepare(this);
+                } catch (TransactionException e) {
+                    txe = e;
+                }
+            }
+        }
+        status = Status.STATUS_PREPARED;
+
+        Thread rollbackThread = new Thread(this, "RollbackThread");
+        rollbackThread.start();
+
+        if (txe != null) {
+            XAException e = new XAException(XAException.XA_RBOTHER);
+            e.initCause(txe);
+            throw e;
+        }
+    }
+
+    /**
+     * Commit the transaction identified by this context. Commits changes on
+     * all resources. If some resource reports an error on commit,
+     * automatically rollback changes on all other resources. Throw
+     * exception at the end if some commit failed.
+     * @throws XAException if an error occurs
+     */
+    public synchronized void commit() throws XAException {
+        if (status == Status.STATUS_ROLLEDBACK) {
+            throw new XAException(XAException.XA_RBTIMEOUT);
+        }
+        status = Status.STATUS_COMMITTING;
+
+        TransactionException txe = null;
+        for (int i = 0; i < resources.length; i++) {
+            InternalXAResource resource = resources[i];
+            if (txe != null) {
+                try {
+                    resource.rollback(this);
+                } catch (TransactionException e) {
+                    log.warn("Unable to rollback changes on " + resource, e);
+                }
+            } else {
+                try {
+                    resource.commit(this);
+                } catch (TransactionException e) {
+                    txe = e;
+                }
+            }
+        }
+        status = Status.STATUS_COMMITTED;
+
+        if (txe != null) {
+            XAException e = new XAException(XAException.XA_RBOTHER);
+            e.initCause(txe);
+            throw e;
+        }
+    }
+
+    /**
+     * Rollback the transaction identified by this context. Rolls back changes
+     * on all resources. Throws exception at the end if errors were found.
+     * @throws XAException if an error occurs
+     */
+    public synchronized void rollback() throws XAException {
+        if (status == Status.STATUS_ROLLEDBACK) {
+            throw new XAException(XAException.XA_RBTIMEOUT);
+        }
+        status = Status.STATUS_ROLLING_BACK;
+
+        int errors = 0;
+        for (int i = 0; i < resources.length; i++) {
+            InternalXAResource resource = resources[i];
+            try {
+                resource.rollback(this);
+            } catch (TransactionException e) {
+                log.warn("Unable to rollback changes on " + resource, e);
+                errors++;
+            }
+        }
+        status = Status.STATUS_ROLLEDBACK;
+        if (errors != 0) {
+            throw new XAException(XAException.XA_RBOTHER);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Waits for the amount of time specified as transaction timeout. After
+     * this time has elapsed, rolls back the transaction if still prepared
+     * and marks the transaction rolled back.
+     */
+    public void run() {
+        try {
+            Thread.sleep(timeout * 1000);
+        } catch (InterruptedException e) {
+            /* ignore */
+        }
+
+        synchronized (this) {
+            if (status == Status.STATUS_PREPARED) {
+                try {
+                    rollback();
+                } catch (XAException e) {
+                    /* ignore */
+                }
+                log.warn("Transaction rolled back because timeout expired.");
+            }
+        }
     }
 }

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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -22,7 +22,7 @@
 import org.apache.jackrabbit.core.observation.ObservationManagerImpl;
 import org.apache.jackrabbit.core.query.QueryManagerImpl;
 import org.apache.jackrabbit.core.state.SharedItemStateManager;
-import org.apache.jackrabbit.core.state.TransactionalItemStateManager;
+import org.apache.jackrabbit.core.state.LocalItemStateManager;
 import org.apache.jackrabbit.core.version.GenericVersionSelector;
 import org.apache.jackrabbit.core.version.InternalVersion;
 import org.apache.jackrabbit.core.version.VersionImpl;
@@ -85,7 +85,7 @@
      * The persistent state mgr associated with the workspace represented by <i>this</i>
      * <code>Workspace</code> instance.
      */
-    protected final TransactionalItemStateManager stateMgr;
+    protected final LocalItemStateManager stateMgr;
 
     /**
      * The hierarchy mgr that reflects persistent state only
@@ -128,7 +128,7 @@
                   SessionImpl session) {
         this.wspConfig = wspConfig;
         this.rep = rep;
-        this.stateMgr = new TransactionalItemStateManager(stateMgr, this);
+        this.stateMgr = createItemStateManager(stateMgr);
         this.hierMgr = new CachingHierarchyManager(rep.getRootNodeUUID(),
                 this.stateMgr, session.getNamespaceResolver());
         this.session = session;
@@ -151,7 +151,7 @@
      *
      * @return the item state manager of this workspace
      */
-    public TransactionalItemStateManager getItemStateManager() {
+    public LocalItemStateManager getItemStateManager() {
         return stateMgr;
     }
 
@@ -718,6 +718,16 @@
                 throw new InvalidSerializedDataException(msg, se);
             }
         }
+    }
+
+    /**
+     * Create the persistent item state manager on top of the shared item
+     * state manager. May be overridden by subclasses.
+     * @param shared shared item state manager
+     * @return local item state manager
+     */
+    protected LocalItemStateManager createItemStateManager(SharedItemStateManager shared) {
+        return new LocalItemStateManager(shared, this);
     }
 }
 

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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -19,10 +19,11 @@
 import org.apache.jackrabbit.core.config.WorkspaceConfig;
 import org.apache.jackrabbit.core.security.AuthContext;
 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.jackrabbit.core.lock.XALockManager;
+import org.apache.jackrabbit.core.lock.LockManagerImpl;
+import org.apache.jackrabbit.core.state.XAItemStateManager;
+import org.apache.jackrabbit.core.state.SessionItemStateManager;
+import org.apache.jackrabbit.core.state.SharedItemStateManager;
 import org.apache.log4j.Logger;
 
 import javax.jcr.AccessDeniedException;
@@ -51,14 +52,9 @@
     private static final Map txGlobal = new HashMap();
 
     /**
-     * Known attribute name.
+     * Default transaction timeout, in seconds.
      */
-    private static final String ATTRIBUTE_CHANGE_LOG = "ChangeLog";
-
-    /**
-     * Known attribute name.
-     */
-    private static final String ATTRIBUTE_LOCK_MANAGER = "LockManager";
+    private static final int DEFAULT_TX_TIMEOUT = 5;
 
     /**
      * Currently associated transaction
@@ -71,6 +67,16 @@
     private int txTimeout;
 
     /**
+     * List of transactional resources.
+     */
+    private InternalXAResource[] txResources;
+
+    /**
+     * Session-local lock manager.
+     */
+    private LockManager lockMgr;
+
+    /**
      * Create a new instance of this class.
      *
      * @param rep          repository
@@ -86,6 +92,11 @@
             throws AccessDeniedException, RepositoryException {
 
         super(rep, loginContext, wspConfig);
+
+        txResources = new InternalXAResource[] {
+            (XAItemStateManager) wsp.getItemStateManager(),
+            (XALockManager) getLockManager()
+        };
     }
 
     /**
@@ -103,8 +114,33 @@
             throws AccessDeniedException, RepositoryException {
 
         super(rep, subject, wspConfig);
+
+        txResources = new InternalXAResource[] {
+            (XAItemStateManager) wsp.getItemStateManager(),
+            (XALockManager) getLockManager()
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected WorkspaceImpl createWorkspaceInstance(WorkspaceConfig wspConfig,
+                                                    SharedItemStateManager stateMgr,
+                                                    RepositoryImpl rep,
+                                                    SessionImpl session) {
+        return new XAWorkspace(wspConfig, stateMgr, rep, session);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public LockManager getLockManager() throws RepositoryException {
+        if (lockMgr == null) {
+            LockManagerImpl lockMgr = (LockManagerImpl) wsp.getLockManager();
+            this.lockMgr = new XALockManager(this, lockMgr);
+        }
+        return lockMgr;
+    }
     //-------------------------------------------------------------< XASession >
     /**
      * {@inheritDoc}
@@ -117,14 +153,14 @@
     /**
      * {@inheritDoc}
      */
-    public int getTransactionTimeout() throws XAException {
-        return txTimeout;
+    public int getTransactionTimeout() {
+        return txTimeout == 0 ? DEFAULT_TX_TIMEOUT : txTimeout;
     }
 
     /**
      * {@inheritDoc}
      */
-    public boolean setTransactionTimeout(int seconds) throws XAException {
+    public boolean setTransactionTimeout(int seconds) {
         txTimeout = seconds;
         return true;
     }
@@ -160,7 +196,6 @@
             log.error("Resource already associated with a transaction.");
             throw new XAException(XAException.XAER_PROTO);
         }
-
         TransactionContext tx;
         if (flags == TMNOFLAGS) {
             tx = (TransactionContext) txGlobal.get(xid);
@@ -186,13 +221,12 @@
     }
 
     /**
-     * Create a global transaction.
-     *
+     * Create a new transaction context.
      * @param xid xid of global transaction.
-     * @return transaction
+     * @return transaction context
      */
     private TransactionContext createTransaction(Xid xid) {
-        TransactionContext tx = new TransactionContext();
+        TransactionContext tx = new TransactionContext(txResources, getTransactionTimeout());
         txGlobal.put(xid, tx);
         return tx;
     }
@@ -214,17 +248,12 @@
             log.error("Resource not associated with a transaction.");
             throw new XAException(XAException.XAER_PROTO);
         }
-
         TransactionContext tx = (TransactionContext) txGlobal.get(xid);
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-        if (flags == TMSUCCESS) {
-            disassociate();
-        } else if (flags == TMFAIL) {
-            disassociate();
-        } else if (flags == TMSUSPEND) {
-            disassociate();
+        if (flags == TMSUCCESS || flags == TMFAIL || flags == TMSUSPEND) {
+            associate(null);
         } else {
             throw new XAException(XAException.XAER_INVAL);
         }
@@ -233,99 +262,38 @@
     /**
      * {@inheritDoc}
      */
-    public synchronized int prepare(Xid xid) throws XAException {
+    public int prepare(Xid xid) throws XAException {
         TransactionContext tx = (TransactionContext) txGlobal.get(xid);
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-
-        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);
-        }
+        tx.prepare();
+        return XA_OK;
     }
 
     /**
      * {@inheritDoc}
      */
-    public void rollback(Xid xid) throws XAException {
+    public void commit(Xid xid, boolean onePhase) throws XAException {
         TransactionContext tx = (TransactionContext) txGlobal.get(xid);
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-
-        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);
+        if (onePhase) {
+            tx.prepare();
         }
+        tx.commit();
     }
 
     /**
      * {@inheritDoc}
      */
-    public void commit(Xid xid, boolean onePhase) throws XAException {
+    public void rollback(Xid xid) throws XAException {
         TransactionContext tx = (TransactionContext) txGlobal.get(xid);
         if (tx == null) {
             throw new XAException(XAException.XAER_NOTA);
         }
-
-        TransactionalItemStateManager stateMgr = wsp.getItemStateManager();
-        stateMgr.setChangeLog(getChangeLog(tx), true);
-
-        TxLockManager lockMgr = getTxLockManager(tx);
-
-        try {
-            // 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);
-        }
+        tx.rollback();
     }
 
     /**
@@ -350,15 +318,13 @@
      * the transaction containing all transaction-local objects to be
      * used when performing item retrieval and store.
      */
-    void associate(TransactionContext tx) {
+    public synchronized void associate(TransactionContext tx) {
         this.tx = tx;
 
-        ChangeLog txLog = getChangeLog(tx);
-        if (txLog == null) {
-            txLog = new ChangeLog();
-            tx.setAttribute(ATTRIBUTE_CHANGE_LOG, txLog);
+        for (int i = 0; i < txResources.length; i++) {
+            InternalXAResource txResource = txResources[i];
+            txResource.associate(tx);
         }
-        wsp.getItemStateManager().setChangeLog(txLog, false);
     }
 
     /**
@@ -368,21 +334,11 @@
      * @return <code>true</code> if this resource is associated
      *         with a transaction; otherwise <code>false</code>
      */
-    boolean isAssociated() {
+    private boolean isAssociated() {
         return tx != null;
     }
 
     /**
-     * Disassociate this session from a global transaction. Internally,
-     * clear the transaction object.
-     */
-    void disassociate() {
-        tx = null;
-
-        wsp.getItemStateManager().setChangeLog(null, false);
-    }
-
-    /**
      * Compare two strings for equality. If both are <code>null</code>, this
      * is also considered to be equal.
      */
@@ -392,71 +348,5 @@
         } else {
             return s1.equals(s2);
         }
-    }
-
-    /**
-     * Internal XAException derived class that allows passing a base exception
-     * in its constructor.
-     */
-    static class ExtendedXAException extends XAException {
-
-        /**
-         * Create an XAException with a given error code and a root cause.
-         * @param errcode The error code identifying the exception.
-         * @param cause The cause (which is saved for later retrieval by the
-         *              {@link #getCause()} method).  (A <tt>null</tt> value is
-         *              permitted, and indicates that the cause is nonexistent
-         *              or unknown.)
-         */
-        public ExtendedXAException(int errcode, Throwable cause) {
-            super(errcode);
-
-            if (cause != null) {
-                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/XAWorkspace.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XAWorkspace.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XAWorkspace.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/XAWorkspace.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,51 @@
+/*
+ * 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.config.WorkspaceConfig;
+import org.apache.jackrabbit.core.state.SharedItemStateManager;
+import org.apache.jackrabbit.core.state.XAItemStateManager;
+import org.apache.jackrabbit.core.state.LocalItemStateManager;
+
+/**
+ * Workspace extension that works in an XA environment.
+ */
+public class XAWorkspace extends WorkspaceImpl {
+
+    /**
+     * Protected constructor.
+     *
+     * @param wspConfig The workspace configuration
+     * @param stateMgr  The shared item state manager
+     * @param rep       The repository
+     * @param session   The session
+     */
+    protected XAWorkspace(WorkspaceConfig wspConfig,
+                          SharedItemStateManager stateMgr, RepositoryImpl rep,
+                          SessionImpl session) {
+
+        super(wspConfig, stateMgr, rep, session);
+    }
+
+    /**
+     * {@inheritDoc}
+     */ 
+    protected LocalItemStateManager createItemStateManager(SharedItemStateManager shared) {
+        return new XAItemStateManager(shared, this);
+    }
+}
+

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

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

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

Modified: 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=365558&r1=365557&r2=365558&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/AbstractLockInfo.java Mon Jan  2 23:22:25 2006
@@ -127,6 +127,13 @@
     }
 
     /**
+     * Return a flag indicating whether the lock information may still change.
+     */
+    public boolean mayChange() {
+        return live;
+    }
+
+    /**
      * Return a flag indicating whether the lock is session-scoped
      *
      * @return <code>true</code> if the lock is session-scoped;

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=365558&r1=365557&r2=365558&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 Mon Jan  2 23:22:25 2006
@@ -30,12 +30,12 @@
     /**
      * Lock info containing latest information
      */
-    private final AbstractLockInfo info;
+    protected final AbstractLockInfo info;
 
     /**
      * Node holding lock
      */
-    private final Node node;
+    protected final Node node;
 
     /**
      * Create a new instance of this class.

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,912 @@
+/*
+ * 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.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 LockManagerImpl implements LockManager, SynchronousEventListener {
+
+    /**
+     * Logger
+     */
+    private static final Logger log = Logger.getLogger(LockManagerImpl.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;
+
+    /**
+     * 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 LockManagerImpl(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
+     */
+    AbstractLockInfo 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 info;
+
+        } 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();
+        }
+    }
+
+    /**
+     * 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 {
+
+        AbstractLockInfo info = internalLock(node, isDeep, isSessionScoped);
+        return new LockImpl(info, node);
+    }
+
+    /**
+     * {@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 {
+
+        internalUnlock(node);
+    }
+
+    /**
+     * {@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) {
+                    // if no session currently holds lock, reassign
+                    SessionImpl lockHolder = getLockHolder();
+                    if (lockHolder == null) {
+                        setLockHolder(session);
+                    }
+                    try {
+                        NodeImpl node = (NodeImpl) session.getItemManager().getItem(
+                                new NodeId(getUUID()));
+                        node.unlock();
+                    } catch (RepositoryException e) {
+                        log.warn("Unable to unlock session-scoped lock on node '"
+                                + lockToken + "': " + e.getMessage());
+                        log.debug("Root cause: ", e);
+                    }
+                } 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/LockManagerImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,399 @@
+package org.apache.jackrabbit.core.lock;
+
+import org.apache.jackrabbit.core.TransactionException;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.log4j.Logger;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.transaction.Status;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Encapsulates operations that happen in an XA environment.
+ */
+class XAEnvironment {
+
+    /**
+     * Logger instance for this class
+     */
+    private static final Logger log = Logger.getLogger(XAEnvironment.class);
+
+    /**
+     * Global lock manager.
+     */
+    private final LockManagerImpl lockMgr;
+
+    /**
+     * Session owning this environment.
+     */
+    private final SessionImpl session;
+
+    /**
+     * 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;
+
+    /**
+     * Current status.
+     */
+    private int status;
+
+    /**
+     * Create a new instance of this class.
+     * @param lockMgr global lock manager
+     */
+    public XAEnvironment(SessionImpl session, LockManagerImpl lockMgr) {
+        this.session = session;
+        this.lockMgr = lockMgr;
+    }
+
+    /**
+     * Reset this environment.
+     */
+    public void reset() {
+        lockedNodesMap.clear();
+        unlockedNodesMap.clear();
+        operations.clear();
+        opIndex = 0;
+    }
+
+    /**
+     * Lock some node.
+     * @param node node to lock
+     * @param isDeep <code>true</code> to deep lock this node;
+     *               <code>false</code> otherwise
+     * @param isSessionScoped <code>true</code> if lock should be session scoped;
+     *                        <code>false</code> otherwise
+     * @throws LockException if node is already locked
+     * @throws RepositoryException if an error occurs
+     */
+    public AbstractLockInfo 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 lockMgr.getLockInfo(uuid);
+            }
+        }
+
+        // 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);
+
+        return info;
+    }
+
+    /**
+     * Unlock some node.
+     * @param node node to unlock
+     * @throws LockException if the node is not locked
+     * @throws RepositoryException if an error occurs
+     */
+    public void unlock(NodeImpl node) throws LockException, RepositoryException {
+        String uuid = node.internalGetUUID();
+
+        // check positive set first
+        AbstractLockInfo 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.");
+            }
+            info = new LockInfo(node, info);
+            unlockedNodesMap.put(uuid, info);
+            operations.add(info);
+        }
+
+    }
+
+    /**
+     * Return a flag indicating whether the specified node is locked.
+     * @return <code>true</code> if this node is locked;
+     *         <code>false</code> otherwise
+     * @throws RepositoryException if an error occurs
+     */
+    public boolean isLocked(NodeImpl node) throws RepositoryException {
+        AbstractLockInfo info = getLockInfo(node);
+        return info != null;
+    }
+
+    /**
+     * 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
+     */
+    public AbstractLockInfo 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
+        return lockMgr.getLockInfo(uuid);
+    }
+
+    /**
+     * Add lock token to this environment.
+     * @param lt lock token
+     */
+    public void addLockToken(String lt) {}
+
+    /**
+     * Remove lock token from this environment.
+     * @param lt lock token
+     */
+    public void removeLockToken(String lt) {}
+
+    /**
+     * Prepare update. Locks global lock manager and feeds all lock/
+     * unlock operations.
+     */
+    public void prepare() throws TransactionException {
+        status = Status.STATUS_PREPARING;
+        if (!operations.isEmpty()) {
+            lockMgr.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--;
+                    }
+                    lockMgr.cancelUpdate();
+                }
+            }
+        }
+        status = Status.STATUS_PREPARED;
+    }
+
+    /**
+     * Commit changes. This will finish the update and unlock the
+     * global lock manager.
+     */
+    public void commit() {
+        int oldStatus = status;
+
+        status = Status.STATUS_COMMITTING;
+        if (oldStatus == Status.STATUS_PREPARED) {
+            if (!operations.isEmpty()) {
+                lockMgr.endUpdate();
+                reset();
+            }
+        }
+        status = Status.STATUS_COMMITTED;
+    }
+
+    /**
+     * Rollback changes. This will undo all updates and unlock the
+     * global lock manager.
+     */
+    public void rollback() {
+        int oldStatus = status;
+
+        status = Status.STATUS_ROLLING_BACK;
+        if (oldStatus == Status.STATUS_PREPARED) {
+            if (!operations.isEmpty()) {
+                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--;
+                }
+                lockMgr.cancelUpdate();
+                reset();
+            }
+        }
+        status = Status.STATUS_ROLLEDBACK;
+    }
+
+    /**
+     * Return a flag indicating whether a lock info belongs to a different
+     * XA environment.
+     */
+    public boolean differentXAEnv(AbstractLockInfo info) {
+        if (info instanceof LockInfo) {
+            LockInfo lockInfo = (LockInfo) info;
+            return lockInfo.getXAEnv() != this;
+        }
+        return true;
+    }
+
+    /**
+     * 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;
+        }
+
+        /**
+         * Create a new instance of this class. Used to signal an
+         * unlock operation on some existing lock information.
+         */
+        public LockInfo(NodeImpl node, AbstractLockInfo info) {
+            super(info.lockToken, info.sessionScoped, info.deep, info.lockOwner);
+
+            this.node = node;
+            this.isUnlock = true;
+        }
+
+        /**
+         * 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;
+        }
+
+        /**
+         * Do operation.
+         */
+        public void update() throws LockException, RepositoryException {
+            if (isUnlock) {
+                lockMgr.internalUnlock(node);
+            } else {
+                lockMgr.internalLock(node, deep, sessionScoped);
+            }
+        }
+
+        /**
+         * Undo operation.
+         */
+        public void undo() throws LockException, RepositoryException {
+            if (isUnlock) {
+                lockMgr.internalLock(node, deep, sessionScoped);
+            } else {
+                lockMgr.internalUnlock(node);
+            }
+        }
+
+        /**
+         * Return parent environment.
+         */
+        public XAEnvironment getXAEnv() {
+            return XAEnvironment.this;
+        }
+
+        /**
+         * {@inheritDoc}
+         * <p/>
+         * As long as the XA environment is neither committed nor rolled back,
+         * associated lock information is subject to change.
+         */
+        public boolean mayChange() {
+            if (status != Status.STATUS_COMMITTED &&
+                    status != Status.STATUS_ROLLEDBACK) {
+                return true;
+            }
+            return super.mayChange();
+        }
+    }
+}

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

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

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

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java?rev=365558&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/lock/XALock.java Mon Jan  2 23:22:25 2006
@@ -0,0 +1,64 @@
+/*
+ * 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.log4j.Logger;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+/**
+ * Extension to standard lock implementation that works in XA environment.
+ */
+class XALock extends LockImpl {
+
+    /**
+     * Logger instance for this class.
+     */
+    private static final Logger log = Logger.getLogger(XALock.class);
+
+    /**
+     * XA lock manager.
+     */
+    private final XALockManager lockMgr;
+
+    /**
+     * Create a new instance of this class.
+     * @param info lock information
+     * @param node node holding lock
+     */
+    public XALock(XALockManager lockMgr, AbstractLockInfo info, Node node) {
+        super(info, node);
+
+        this.lockMgr = lockMgr;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Refresh lock information if XA environment has changed.
+     */
+    public boolean isLive() throws RepositoryException {
+        if (info.mayChange()) {
+            if (lockMgr.differentXAEnv(info)) {
+                return lockMgr.holdsLock((NodeImpl) node);
+            }
+        }
+        return super.isLive();
+    }
+}