TransactionSynchronization
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);
/**
* @return Returns the system time when the transaction started, or -1 if there is no current transaction.
*/
public static long getTransactionStartTime()
{
/*
* This method can be called outside of a transaction, so we can go direct to the synchronizations.
*/
TransactionSynchronizationImpl txnSynch =
(TransactionSynchronizationImpl) TransactionSynchronizationManager.getResource(RESOURCE_KEY_TXN_SYNCH);
if (txnSynch == null)
{
if (TransactionSynchronizationManager.isSynchronizationActive())
{
// need to lazily register synchronizations
return registerSynchronizations().getTransactionStartTime();
}
else
{
return -1; // not in a transaction
}
}
else
{
return txnSynch.getTransactionStartTime();
}
}
/**
* 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();
}
}
/**
*
* @author Derek Hulley
* @since 2.1.4
*/
public static enum TxnReadState
{
/** No transaction is active */
TXN_NONE,
/** The current transaction is read-only */
TXN_READ_ONLY,
/** The current transaction supports writes */
TXN_READ_WRITE
}
/**
* @return Returns the read-write state of the current transaction
* @since 2.1.4
*/
public static TxnReadState getTransactionReadState()
{
if (!TransactionSynchronizationManager.isSynchronizationActive())
{
return TxnReadState.TXN_NONE;
}
// Find the read-write state of the txn
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly())
{
return TxnReadState.TXN_READ_ONLY;
}
else
{
return TxnReadState.TXN_READ_WRITE;
}
}
/**
* Checks the state of the current transaction and throws an exception if a transaction
* is not present or if the transaction is not read-write, if required.
*
* @param requireReadWrite true if the transaction must be read-write
*
* @since 3.2
*/
public static void checkTransactionReadState(boolean requireReadWrite)
{
if (!TransactionSynchronizationManager.isSynchronizationActive())
{
throw new IllegalStateException(
"The current operation requires an active " +
(requireReadWrite ? "read-write" : "") +
"transaction.");
}
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly() && requireReadWrite)
{
throw new IllegalStateException("The current operation requires an active read-write transaction.");
}
}
/**
* Are there any pending changes which must be synchronized with the store?
*
* @return true => changes are pending
*
* @deprecated To be replaced by {@link DirtySessionMethodInterceptor}
*/
public static boolean isDirty()
{
TransactionSynchronizationImpl synch = getSynchronization();
Set
* 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
*
* @see TransactionalResourceHelper for helper methods to create and bind common collection types
*/
@SuppressWarnings("unchecked")
public static
* 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.
*
* 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 NodeDaoService against the transaction.
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the NodeDaoService.
*
* This method can be called repeatedly as long as the service being bound
* implements equals and hashCode.
*
* @param daoService
*/
public static void bindDaoService(TransactionalDao daoService)
{
// get transaction-local synchronization
TransactionSynchronizationImpl synch = getSynchronization();
// bind the service in
boolean bound = synch.getDaoServices().add(daoService);
// done
if (logger.isDebugEnabled())
{
logBoundService(daoService, bound);
}
}
/**
* Method that registers an IntegrityChecker against the transaction.
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the IntegrityChecker.
*
* This method can be called repeatedly as long as the service being bound
* implements equals and hashCode.
*
* @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 LuceneIndexerAndSearcherFactory against
* the transaction.
*
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the LuceneIndexerAndSearcherFactory.
*
* Although bound within a Set, 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(LuceneIndexerAndSearcher 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 LuceneIndexerAndSearcherFactory against
* the transaction.
*
* Setting this will ensure that the pre- and post-commit operations perform
* the necessary cleanups against the LuceneIndexerAndSearcherFactory.
*
* Although bound within a Set, 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.addListener(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);
}
}
/**
* No-op
*
* @deprecated No longer does anything
*/
public static void flush()
{
// No-op
}
/**
* 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}.
*
* This method also ensures that the transaction binding has been performed.
*
* @return Returns the common synchronization instance used
*/
private static TransactionSynchronizationImpl getSynchronization()
{
// ensure synchronizations
return registerSynchronizations();
}
/**
* 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())
{
Thread currentThread = Thread.currentThread();
throw new AlfrescoRuntimeException("Transaction must be active and synchronization is required: " + currentThread);
}
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
String txnId = GUID.generate();
// 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 long txnStartTime;
private final String txnId;
private final Set