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