Moving to root below branch label

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2005-12-08 07:13:07 +00:00
commit e1e6508fec
1095 changed files with 230566 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import org.springframework.transaction.TransactionException;
/**
* Simple concrete implementation of the base class.
*
* @author Derek Hulley
*/
public class AlfrescoTransactionException extends TransactionException
{
private static final long serialVersionUID = 3643033849898962687L;
public AlfrescoTransactionException(String msg)
{
super(msg);
}
public AlfrescoTransactionException(String msg, Throwable ex)
{
super(msg, ex);
}
}

View File

@@ -0,0 +1,677 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.node.integrity.IntegrityChecker;
import org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcherFactory;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* Helper class to manage transaction synchronization. This provides helpers to
* ensure that the necessary <code>TransactionSynchronization</code> instances
* are registered on behalf of the application code.
*
* @author Derek Hulley
*/
public abstract class AlfrescoTransactionSupport
{
/*
* The registrations of services is very explicit on the interface. This
* is to convey the idea that the execution of these services when the
* transaction completes is very explicit. As we only have a finite
* list of types of services that need registration, this is still
* OK.
*/
/**
* The order of synchronization set to be 100 less than the Hibernate synchronization order
*/
public static final int SESSION_SYNCHRONIZATION_ORDER =
SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER - 100;
/** resource key to store the transaction synchronizer instance */
private static final String RESOURCE_KEY_TXN_SYNCH = "txnSynch";
private static Log logger = LogFactory.getLog(AlfrescoTransactionSupport.class);
/**
* Get a unique identifier associated with each transaction of each thread. Null is returned if
* no transaction is currently active.
*
* @return Returns the transaction ID, or null if no transaction is present
*/
public static String getTransactionId()
{
/*
* Go direct to the synchronizations as we don't want to register a resource if one doesn't exist.
* This method is heavily used, so the simple Map lookup on the ThreadLocal is the fastest.
*/
TransactionSynchronizationImpl txnSynch =
(TransactionSynchronizationImpl) TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
if (txnSynch == null)
{
if (TransactionSynchronizationManager.isSynchronizationActive())
{
// need to lazily register synchronizations
return registerSynchronizations().getTransactionId();
}
else
{
return null; // not in a transaction
}
}
else
{
return txnSynch.getTransactionId();
}
}
/**
* Are there any pending changes which must be synchronized with the store?
*
* @return true => changes are pending
*/
public static boolean isDirty()
{
TransactionSynchronizationImpl synch = getSynchronization();
Set<NodeDaoService> services = synch.getNodeDaoServices();
for (NodeDaoService service : services)
{
if (service.isDirty())
{
return true;
}
}
return false;
}
/**
* Gets a resource associated with the current transaction, which must be active.
* <p>
* All necessary synchronization instances will be registered automatically, if required.
*
*
* @param key the thread resource map key
* @return Returns a thread resource of null if not present
*/
public static Object getResource(Object key)
{
// get the synchronization
TransactionSynchronizationImpl txnSynch = getSynchronization();
// get the resource
Object resource = txnSynch.resources.get(key);
// done
if (logger.isDebugEnabled())
{
logger.debug("Fetched resource: \n" +
" key: " + key + "\n" +
" resource: " + resource);
}
return resource;
}
/**
* Binds a resource to the current transaction, which must be active.
* <p>
* All necessary synchronization instances will be registered automatically, if required.
*
* @param key
* @param resource
*/
public static void bindResource(Object key, Object resource)
{
// get the synchronization
TransactionSynchronizationImpl txnSynch = getSynchronization();
// bind the resource
txnSynch.resources.put(key, resource);
// done
if (logger.isDebugEnabled())
{
logger.debug("Bound resource: \n" +
" key: " + key + "\n" +
" resource: " + resource);
}
}
/**
* Unbinds a resource from the current transaction, which must be active.
* <p>
* All necessary synchronization instances will be registered automatically, if required.
*
* @param key
*/
public static void unbindResource(Object key)
{
// get the synchronization
TransactionSynchronizationImpl txnSynch = getSynchronization();
// remove the resource
txnSynch.resources.remove(key);
// done
if (logger.isDebugEnabled())
{
logger.debug("Unbound resource: \n" +
" key: " + key);
}
}
/**
* Method that registers a <tt>NodeDaoService</tt> against the transaction.
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the <tt>NodeDaoService</tt>.
* <p>
* This method can be called repeatedly as long as the service being bound
* implements <tt>equals</tt> and <tt>hashCode</tt>.
*
* @param nodeDaoService
*/
public static void bindNodeDaoService(NodeDaoService nodeDaoService)
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// bind the service in
boolean bound = synch.getNodeDaoServices().add(nodeDaoService);
// done
if (logger.isDebugEnabled())
{
logBoundService(nodeDaoService, bound);
}
}
/**
* Method that registers an <tt>IntegrityChecker</tt> against the transaction.
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the <tt>IntegrityChecker</tt>.
* <p>
* This method can be called repeatedly as long as the service being bound
* implements <tt>equals</tt> and <tt>hashCode</tt>.
*
* @param integrityChecker
*/
public static void bindIntegrityChecker(IntegrityChecker integrityChecker)
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// bind the service in
boolean bound = synch.getIntegrityCheckers().add(integrityChecker);
// done
if (logger.isDebugEnabled())
{
logBoundService(integrityChecker, bound);
}
}
/**
* Method that registers a <tt>LuceneIndexerAndSearcherFactory</tt> against
* the transaction.
* <p>
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the <tt>LuceneIndexerAndSearcherFactory</tt>.
* <p>
* Although bound within a <tt>Set</tt>, it would still be better for the caller
* to only bind once per transaction, if possible.
*
* @param indexerAndSearcher the Lucene indexer to perform transaction completion
* tasks on
*/
public static void bindLucene(LuceneIndexerAndSearcherFactory indexerAndSearcher)
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// bind the service in
boolean bound = synch.getLucenes().add(indexerAndSearcher);
// done
if (logger.isDebugEnabled())
{
logBoundService(indexerAndSearcher, bound);
}
}
/**
* Method that registers a <tt>LuceneIndexerAndSearcherFactory</tt> against
* the transaction.
* <p>
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the <tt>LuceneIndexerAndSearcherFactory</tt>.
* <p>
* Although bound within a <tt>Set</tt>, it would still be better for the caller
* to only bind once per transaction, if possible.
*
* @param indexerAndSearcher the Lucene indexer to perform transaction completion
* tasks on
*/
public static void bindListener(TransactionListener listener)
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// bind the service in
boolean bound = synch.getListeners().add(listener);
// done
if (logger.isDebugEnabled())
{
logBoundService(listener, bound);
}
}
/**
* Use as part of a debug statement
*
* @param service the service to report
* @param bound true if the service was just bound; false if it was previously bound
*/
private static void logBoundService(Object service, boolean bound)
{
if (bound)
{
logger.debug("Bound service: \n" +
" transaction: " + getTransactionId() + "\n" +
" service: " + service);
}
else
{
logger.debug("Service already bound: \n" +
" transaction: " + getTransactionId() + "\n" +
" service: " + service);
}
}
/**
* Flush in-transaction resources. A transaction must be active.
* <p>
* The flush may include:
* <ul>
* <li>{@link NodeDaoService#flush()}</li>
* <li>{@link RuleService#executePendingRules()}</li>
* <li>{@link IntegrityChecker#checkIntegrity()}</li>
* </ul>
*
*/
public static void flush()
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// flush
synch.flush();
}
/**
* Gets the current transaction synchronization instance, which contains the locally bound
* resources that are available to {@link #getResource(Object) retrieve} or
* {@link #bindResource(Object, Object) add to}.
* <p>
* This method also ensures that the transaction binding has been performed.
*
* @return Returns the common synchronization instance used
*/
private static TransactionSynchronizationImpl getSynchronization()
{
// ensure synchronizations
registerSynchronizations();
// get the txn synch instances
return (TransactionSynchronizationImpl) TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
}
/**
* Binds the Alfresco-specific to the transaction resources
*
* @return Returns the current or new synchronization implementation
*/
private static TransactionSynchronizationImpl registerSynchronizations()
{
/*
* No thread synchronization or locking required as the resources are all threadlocal
*/
if (!TransactionSynchronizationManager.isSynchronizationActive())
{
throw new AlfrescoRuntimeException("Transaction must be active and synchronization is required");
}
TransactionSynchronizationImpl txnSynch =
(TransactionSynchronizationImpl) TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
if (txnSynch != null)
{
// synchronization already registered
return txnSynch;
}
// we need a unique ID for the transaction
StringBuilder sb = new StringBuilder(56);
sb.append(System.currentTimeMillis()).append(":").append(GUID.generate());
String txnId = sb.toString();
// register the synchronization
txnSynch = new TransactionSynchronizationImpl(txnId);
TransactionSynchronizationManager.registerSynchronization(txnSynch);
// register the resource that will ensure we don't duplication the synchronization
TransactionSynchronizationManager.bindResource(RESOURCE_KEY_TXN_SYNCH, txnSynch);
// done
if (logger.isDebugEnabled())
{
logger.debug("Bound txn synch: " + txnSynch);
}
return txnSynch;
}
/**
* Cleans out transaction resources if present
*/
private static void clearSynchronization()
{
if (TransactionSynchronizationManager.hasResource(RESOURCE_KEY_TXN_SYNCH))
{
Object txnSynch = TransactionSynchronizationManager.unbindResource(RESOURCE_KEY_TXN_SYNCH);
// done
if (logger.isDebugEnabled())
{
logger.debug("Unbound txn synch:" + txnSynch);
}
}
}
/**
* Helper method to rebind the synchronization to the transaction
*
* @param txnSynch
*/
private static void rebindSynchronization(TransactionSynchronizationImpl txnSynch)
{
TransactionSynchronizationManager.bindResource(RESOURCE_KEY_TXN_SYNCH, txnSynch);
if (logger.isDebugEnabled())
{
logger.debug("Bound txn synch: " + txnSynch);
}
}
/**
* Handler of txn synchronization callbacks specific to internal
* application requirements
*/
private static class TransactionSynchronizationImpl extends TransactionSynchronizationAdapter
{
private final String txnId;
private final Set<NodeDaoService> nodeDaoServices;
private final Set<IntegrityChecker> integrityCheckers;
private final Set<LuceneIndexerAndSearcherFactory> lucenes;
private final Set<TransactionListener> listeners;
private final Map<Object, Object> resources;
/**
* Sets up the resource map
*
* @param txnId
*/
public TransactionSynchronizationImpl(String txnId)
{
this.txnId = txnId;
nodeDaoServices = new HashSet<NodeDaoService>(3);
integrityCheckers = new HashSet<IntegrityChecker>(3);
lucenes = new HashSet<LuceneIndexerAndSearcherFactory>(3);
listeners = new HashSet<TransactionListener>(5);
resources = new HashMap<Object, Object>(17);
}
public String getTransactionId()
{
return txnId;
}
/**
* @return Returns a set of <tt>NodeDaoService</tt> instances that will be called
* during end-of-transaction processing
*/
public Set<NodeDaoService> getNodeDaoServices()
{
return nodeDaoServices;
}
/**
* @return Returns a set of <tt>IntegrityChecker</tt> instances that will be called
* during end-of-transaction processing
*/
public Set<IntegrityChecker> getIntegrityCheckers()
{
return integrityCheckers;
}
/**
* @return Returns a set of <tt>LuceneIndexerAndSearcherFactory</tt> that will be called
* during end-of-transaction processing
*/
public Set<LuceneIndexerAndSearcherFactory> getLucenes()
{
return lucenes;
}
/**
* @return Returns a set of <tt>TransactionListener<tt> instances that will be called
* during end-of-transaction processing
*/
public Set<TransactionListener> getListeners()
{
return listeners;
}
public String toString()
{
StringBuilder sb = new StringBuilder(50);
sb.append("TransactionSychronizationImpl")
.append("[ txnId=").append(txnId)
.append(", node service=").append(nodeDaoServices.size())
.append(", integrity=").append(integrityCheckers.size())
.append(", indexers=").append(lucenes.size())
.append(", resources=").append(resources)
.append("]");
return sb.toString();
}
/**
* Performs the in-transaction flushing. Typically done during a transaction or
* before commit.
*/
public void flush()
{
// check integrity
for (IntegrityChecker integrityChecker : integrityCheckers)
{
integrityChecker.checkIntegrity();
}
// flush listeners
for (TransactionListener listener : listeners)
{
listener.flush();
}
}
/**
* @see AlfrescoTransactionSupport#SESSION_SYNCHRONIZATION_ORDER
*/
@Override
public int getOrder()
{
return AlfrescoTransactionSupport.SESSION_SYNCHRONIZATION_ORDER;
}
@Override
public void suspend()
{
if (logger.isDebugEnabled())
{
logger.debug("Suspending transaction: " + this);
}
AlfrescoTransactionSupport.clearSynchronization();
}
@Override
public void resume()
{
if (logger.isDebugEnabled())
{
logger.debug("Resuming transaction: " + this);
}
AlfrescoTransactionSupport.rebindSynchronization(this);
}
/**
* Pre-commit cleanup.
* <p>
* Ensures that the session resources are {@link #flush() flushed}.
* The Lucene indexes are then prepared.
*/
@Override
public void beforeCommit(boolean readOnly)
{
if (logger.isDebugEnabled())
{
logger.debug("Before commit " + (readOnly ? "read-only" : "" ) + ": " + this);
}
// get the txn ID
TransactionSynchronizationImpl synch = (TransactionSynchronizationImpl)
TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
if (synch == null)
{
throw new AlfrescoRuntimeException("No synchronization bound to thread");
}
// These are still considered part of the transaction so are executed here
for (TransactionListener listener : listeners)
{
listener.beforeCommit(readOnly);
}
// flush
flush();
// prepare the indexes
for (LuceneIndexerAndSearcherFactory lucene : lucenes)
{
lucene.prepare();
}
}
@Override
public void beforeCompletion()
{
if (logger.isDebugEnabled())
{
logger.debug("Before completion: " + this);
}
// notify listeners
for (TransactionListener listener : listeners)
{
listener.beforeCompletion();
}
}
@Override
public void afterCompletion(int status)
{
String statusStr = "unknown";
switch (status)
{
case TransactionSynchronization.STATUS_COMMITTED:
statusStr = "committed";
break;
case TransactionSynchronization.STATUS_ROLLED_BACK:
statusStr = "rolled-back";
break;
default:
}
if (logger.isDebugEnabled())
{
logger.debug("After completion (" + statusStr + "): " + this);
}
// commit/rollback Lucene
for (LuceneIndexerAndSearcherFactory lucene : lucenes)
{
try
{
if (status == TransactionSynchronization.STATUS_COMMITTED)
{
lucene.commit();
}
else
{
lucene.rollback();
}
}
catch (RuntimeException e)
{
logger.error("After completion (" + statusStr + ") Lucene exception", e);
}
}
// notify listeners
if (status == TransactionSynchronization.STATUS_COMMITTED)
{
for (TransactionListener listener : listeners)
{
try
{
listener.afterCommit();
}
catch (RuntimeException e)
{
logger.error("After completion (" + statusStr + ") listener exception: \n" +
" listener: " + listener,
e);
}
}
}
else
{
for (TransactionListener listener : listeners)
{
try
{
listener.afterRollback();
}
catch (RuntimeException e)
{
logger.error("After completion (" + statusStr + ") listener exception: \n" +
" listener: " + listener,
e);
}
}
}
// clear the thread's registrations and synchronizations
AlfrescoTransactionSupport.clearSynchronization();
}
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import java.util.ArrayList;
import java.util.List;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* Tests integration between our <tt>UserTransaction</tt> implementation and
* our <tt>TransactionManager</tt>.
*
* @see org.alfresco.repo.transaction.AlfrescoTransactionManager
* @see org.alfresco.util.transaction.SpringAwareUserTransaction
*
* @author Derek Hulley
*/
public class AlfrescoTransactionSupportTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private ServiceRegistry serviceRegistry;
public void setUp() throws Exception
{
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
}
public void testTransactionId() throws Exception
{
// get a user transaction
TransactionService transactionService = serviceRegistry.getTransactionService();
UserTransaction txn = transactionService.getUserTransaction();
assertNull("Thread shouldn't have a txn ID", AlfrescoTransactionSupport.getTransactionId());
// begine the txn
txn.begin();
String txnId = AlfrescoTransactionSupport.getTransactionId();
assertNotNull("Expected thread to have a txn id", txnId);
// check that it is threadlocal
Thread thread = new Thread(new Runnable()
{
public void run()
{
String txnId = AlfrescoTransactionSupport.getTransactionId();
assertNull("New thread seeing txn id");
}
});
// check that the txn id doesn't change
String txnIdCheck = AlfrescoTransactionSupport.getTransactionId();
assertEquals("Transaction ID changed on same thread", txnId, txnIdCheck);
// begin a new, inner transaction
{
UserTransaction txnInner = transactionService.getNonPropagatingUserTransaction();
String txnIdInner = AlfrescoTransactionSupport.getTransactionId();
assertEquals("Inner transaction not started, so txn ID should not change", txnId, txnIdInner);
// begin the nested txn
txnInner.begin();
// check the ID for the outer transaction
txnIdInner = AlfrescoTransactionSupport.getTransactionId();
assertNotSame("Inner txn ID must be different from outer txn ID", txnIdInner, txnId);
// rollback the nested txn
txnInner.rollback();
txnIdCheck = AlfrescoTransactionSupport.getTransactionId();
assertEquals("Txn ID not popped inner txn completion", txnId, txnIdCheck);
}
// rollback
txn.rollback();
assertNull("Thread shouldn't have a txn ID after rollback", AlfrescoTransactionSupport.getTransactionId());
// start a new transaction
txn = transactionService.getUserTransaction();
txn.begin();
txnIdCheck = AlfrescoTransactionSupport.getTransactionId();
assertNotSame("New transaction has same ID", txnId, txnIdCheck);
// rollback
txn.rollback();
assertNull("Thread shouldn't have a txn ID after rollback", AlfrescoTransactionSupport.getTransactionId());
}
public void testListener() throws Exception
{
final List<String> strings = new ArrayList<String>(1);
// anonymous inner class to test it
TransactionListener listener = new TransactionListener()
{
public void flush()
{
strings.add("flush");
}
public void beforeCommit(boolean readOnly)
{
strings.add("beforeCommit");
}
public void beforeCompletion()
{
strings.add("beforeCompletion");
}
public void afterCommit()
{
strings.add("afterCommit");
}
public void afterRollback()
{
strings.add("afterRollback");
}
};
// begin a transaction
TransactionService transactionService = serviceRegistry.getTransactionService();
UserTransaction txn = transactionService.getUserTransaction();
txn.begin();
// register it
AlfrescoTransactionSupport.bindListener(listener);
// test flush
AlfrescoTransactionSupport.flush();
assertTrue("flush not called on listener", strings.contains("flush"));
// test commit
txn.commit();
assertTrue("beforeCommit not called on listener", strings.contains("beforeCommit"));
assertTrue("beforeCompletion not called on listener", strings.contains("beforeCompletion"));
assertTrue("afterCommit not called on listener", strings.contains("afterCommit"));
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import org.alfresco.service.transaction.TransactionService;
/**
* Simple implementation of the transaction service that serve up
* entirely useless user transactions. It is useful within the context
* of some tests.
*
* @author Derek Hulley
*/
public class DummyTransactionService implements TransactionService
{
private UserTransaction txn = new UserTransaction()
{
public void begin() {};
public void commit() {};
public int getStatus() {return Status.STATUS_NO_TRANSACTION;};
public void rollback() {};
public void setRollbackOnly() {};
public void setTransactionTimeout(int arg0) {};
};
public boolean isReadOnly()
{
return false;
}
public UserTransaction getNonPropagatingUserTransaction()
{
return txn;
}
public UserTransaction getUserTransaction()
{
return txn;
}
public UserTransaction getUserTransaction(boolean readonly)
{
return txn;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.node.db.NodeDaoService;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
/**
* Utility class that ensures that a <tt>NodeDaoService</tt> has been registered
* with the current transaction.
* <p>
* It is designed to act as a <b>postInterceptor</b> on the <tt>NodeDaoService</tt>'s
* {@link org.springframework.transaction.interceptor.TransactionProxyFactoryBean}.
*
* @author Derek Hulley
*/
public class NodeDaoServiceTransactionInterceptor implements MethodInterceptor, InitializingBean
{
private NodeDaoService nodeDaoService;
/**
* @param nodeDaoService the <tt>NodeDaoService</tt> to register
*/
public void setNodeDaoService(NodeDaoService nodeDaoService)
{
this.nodeDaoService = nodeDaoService;
}
/**
* Checks that required values have been injected
*/
public void afterPropertiesSet() throws Exception
{
if (nodeDaoService == null)
{
throw new AlfrescoRuntimeException("NodeDaoService is required: " + this);
}
}
public Object invoke(MethodInvocation invocation) throws Throwable
{
AlfrescoTransactionSupport.bindNodeDaoService(nodeDaoService);
// propogate the call
return invocation.proceed();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import javax.transaction.UserTransaction;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.transaction.SpringAwareUserTransaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
/**
* Default implementation of Transaction Service
*
* @author David Caruana
*/
public class TransactionComponent implements TransactionService
{
private PlatformTransactionManager transactionManager;
private boolean readOnly = false;
/**
* Set the transaction manager to use
*
* @param transactionManager platform transaction manager
*/
public void setTransactionManager(PlatformTransactionManager transactionManager)
{
this.transactionManager = transactionManager;
}
/**
* Set the read-only mode for all generated transactions.
*
* @param allowWrite false if all transactions must be read-only
*/
public void setAllowWrite(boolean allowWrite)
{
this.readOnly = !allowWrite;
}
public boolean isReadOnly()
{
return readOnly;
}
/**
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
*/
public UserTransaction getUserTransaction()
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
this.readOnly,
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
/**
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
*/
public UserTransaction getUserTransaction(boolean readOnly)
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
(readOnly | this.readOnly),
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
/**
* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
*/
public UserTransaction getNonPropagatingUserTransaction()
{
SpringAwareUserTransaction txn = new SpringAwareUserTransaction(
transactionManager,
this.readOnly,
TransactionDefinition.ISOLATION_DEFAULT,
TransactionDefinition.PROPAGATION_REQUIRES_NEW,
TransactionDefinition.TIMEOUT_DEFAULT);
return txn;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @see org.alfresco.repo.transaction.TransactionComponent
*
* @author Derek Hulley
*/
public class TransactionComponentTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private PlatformTransactionManager transactionManager;
private TransactionComponent transactionComponent;
private NodeService nodeService;
public void setUp() throws Exception
{
transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager");
transactionComponent = new TransactionComponent();
transactionComponent.setTransactionManager(transactionManager);
transactionComponent.setAllowWrite(true);
nodeService = (NodeService) ctx.getBean("dbNodeService");
}
public void testPropagatingTxn() throws Exception
{
// start a transaction
UserTransaction txnOuter = transactionComponent.getUserTransaction();
txnOuter.begin();
String txnIdOuter = AlfrescoTransactionSupport.getTransactionId();
// start a propagating txn
UserTransaction txnInner = transactionComponent.getUserTransaction();
txnInner.begin();
String txnIdInner = AlfrescoTransactionSupport.getTransactionId();
// the txn IDs should be the same
assertEquals("Txn ID not propagated", txnIdOuter, txnIdInner);
// rollback the inner
txnInner.rollback();
// check both transactions' status
assertEquals("Inner txn not marked rolled back", Status.STATUS_ROLLEDBACK, txnInner.getStatus());
assertEquals("Outer txn not marked for rolled back", Status.STATUS_MARKED_ROLLBACK, txnOuter.getStatus());
try
{
txnOuter.commit();
fail("Outer txn not marked for rollback");
}
catch (RollbackException e)
{
// expected
txnOuter.rollback();
}
}
public void testNonPropagatingTxn() throws Exception
{
// start a transaction
UserTransaction txnOuter = transactionComponent.getUserTransaction();
txnOuter.begin();
String txnIdOuter = AlfrescoTransactionSupport.getTransactionId();
// start a propagating txn
UserTransaction txnInner = transactionComponent.getNonPropagatingUserTransaction();
txnInner.begin();
String txnIdInner = AlfrescoTransactionSupport.getTransactionId();
// the txn IDs should be different
assertNotSame("Txn ID not propagated", txnIdOuter, txnIdInner);
// rollback the inner
txnInner.rollback();
// outer should commit without problems
txnOuter.commit();
}
public void testReadOnlyTxn() throws Exception
{
// start a read-only transaction
transactionComponent.setAllowWrite(false);
UserTransaction txn = transactionComponent.getUserTransaction();
txn.begin();
// do some writing
try
{
nodeService.createStore(
StoreRef.PROTOCOL_WORKSPACE,
getName() + "_" + System.currentTimeMillis());
txn.commit();
fail("Read-only transaction wasn't detected");
}
catch (InvalidDataAccessApiUsageException e)
{
int i = 0;
// expected
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
/**
* Listener for Alfresco-specific transaction callbacks.
*
* @see org.alfresco.repo.transaction.AlfrescoTransactionSupport
*
* @author Derek Hulley
*/
public interface TransactionListener
{
/**
* Allows the listener to flush any consuming resources. This mechanism is
* used primarily during long-lived transactions to ensure that system resources
* are not used up.
*/
void flush();
/**
* Called before a transaction is committed.
* <p>
* All transaction resources are still available.
*
* @param readOnly true if the transaction is read-only
*/
void beforeCommit(boolean readOnly);
/**
* Invoked before transaction commit/rollback. Will be called after
* {@link #beforeCommit(boolean) } even if {@link #beforeCommit(boolean)}
* failed.
* <p>
* Any exceptions generated here will cause the transaction to rollback.
* <p>
* All transaction resources are still available.
*/
void beforeCompletion();
/**
* Invoked after transaction commit.
* <p>
* Any exceptions generated here will cause the transaction to rollback.
* <p>
* All transaction resources are still available.
*/
void afterCommit();
/**
* Invoked after transaction rollback.
* <p>
* All transaction resources are still available.
*/
void afterRollback();
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.transaction;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Class containing transactions helper methods and interfaces.
*
* @author Roy Wetherall
*/
public class TransactionUtil
{
private static Log logger = LogFactory.getLog(TransactionUtil.class);
/**
* Transaction work interface.
* <p>
* This interface encapsulates a unit of work that should be done within a
* transaction.
*/
public interface TransactionWork<Result>
{
/**
* Method containing the work to be done in the user transaction.
*
* @return Return the result of the operation
*/
Result doWork() throws Exception;
}
/**
* Flush transaction.
*/
public static void flush()
{
AlfrescoTransactionSupport.flush();
}
/**
* Execute the transaction work in a user transaction
*
* @param transactionService the transaction service
* @param transactionWork the transaction work
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*/
public static <R> R executeInUserTransaction(
TransactionService transactionService,
TransactionWork<R> transactionWork)
{
return executeInTransaction(transactionService, transactionWork, false);
}
/**
* Execute the transaction work in a non propigating user transaction
*
* @param transactionService the transaction service
* @param transactionWork the transaction work
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*/
public static <R> R executeInNonPropagatingUserTransaction(
TransactionService transactionService,
TransactionWork<R> transactionWork)
{
return executeInTransaction(transactionService, transactionWork, true);
}
/**
* Execute the transaction work in a user transaction of a specified type
*
* @param transactionService the transaction service
* @param transactionWork the transaction work
* @param ignoreException indicates whether errors raised in the work are
* ignored or re-thrown
* @param nonPropagatingUserTransaction indicates whether the transaction
* should be non propigating or not
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*/
private static <R> R executeInTransaction(
TransactionService transactionService,
TransactionWork<R> transactionWork,
boolean nonPropagatingUserTransaction)
{
ParameterCheck.mandatory("transactionWork", transactionWork);
R result = null;
// Get the right type of user transaction
UserTransaction txn = null;
if (nonPropagatingUserTransaction == true)
{
txn = transactionService.getNonPropagatingUserTransaction();
}
else
{
txn = transactionService.getUserTransaction();
}
try
{
// Begin the transaction, do the work and then commit the
// transaction
txn.begin();
result = transactionWork.doWork();
// rollback or commit
if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK)
{
// something caused the transaction to be marked for rollback
txn.rollback();
}
else
{
// transaction should still commit
txn.commit();
}
}
catch (Throwable exception)
{
try
{
// Roll back the exception
txn.rollback();
}
catch (Throwable rollbackException)
{
// just dump the exception - we are already in a failure state
logger.error("Error rolling back transaction", rollbackException);
}
// Re-throw the exception
if (exception instanceof RuntimeException)
{
throw (RuntimeException) exception;
}
else
{
throw new RuntimeException("Error during execution of transaction.", exception);
}
}
return result;
}
}