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);
/**
* 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* 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. *
* 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 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 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(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 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.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. *
* The flush may include: *
* 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