/*
* Copyright 2002-2010 the original author or authors.
*
* 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.springframework.transaction.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.OrderComparator;
import org.springframework.util.Assert;
/**
* Central helper that manages resources and transaction synchronizations per thread.
* To be used by resource management code but not by typical application code.
*
* <p>Supports one resource per key without overwriting, that is, a resource needs
* to be removed before a new one can be set for the same key.
* Supports a list of transaction synchronizations if synchronization is active.
*
* <p>Resource management code should check for thread-bound resources, e.g. JDBC
* Connections or Hibernate Sessions, via <code>getResource</code>. Such code is
* normally not supposed to bind resources to threads, as this is the responsibility
* of transaction managers. A further option is to lazily bind on first use if
* transaction synchronization is active, for performing transactions that span
* an arbitrary number of resources.
*
* <p>Transaction synchronization must be activated and deactivated by a transaction
* manager via {@link #initSynchronization()} and {@link #clearSynchronization()}.
* This is automatically supported by {@link AbstractPlatformTransactionManager},
* and thus by all standard Spring transaction managers, such as
* {@link org.springframework.transaction.jta.JtaTransactionManager} and
* {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
*
* <p>Resource management code should only register synchronizations when this
* manager is active, which can be checked via {@link #isSynchronizationActive};
* it should perform immediate resource cleanup else. If transaction synchronization
* isn't active, there is either no current transaction, or the transaction manager
* doesn't support transaction synchronization.
*
* <p>Synchronization is for example used to always return the same resources
* within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for
* any given DataSource or SessionFactory, respectively.
*
* @author Juergen Hoeller
* @since 02.06.2003
* @see #isSynchronizationActive
* @see #registerSynchronization
* @see TransactionSynchronization
* @see AbstractPlatformTransactionManager#setTransactionSynchronization
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
* @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
*/
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<List<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<List<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");
//-------------------------------------------------------------------------
// Management of transaction-associated resource handles
//-------------------------------------------------------------------------
/**
* Return all resources that are bound to the current thread.
* <p>Mainly for debugging purposes. Resource managers should always invoke
* <code>hasResource</code> for a specific resource key that they are interested in.
* @return a Map with resource keys (usually the resource factory) and resource
* values (usually the active resource object), or an empty Map if there are
* currently no resources bound
* @see #hasResource
*/
public static Map<Object, Object> getResourceMap() {
Map<Object, Object> map = resources.get();
return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap());
}
/**
* Check if there is a resource for the given key bound to the current thread.
* @param key the key to check (usually the resource factory)
* @return if there is a value bound to the current thread
* @see ResourceTransactionManager#getResourceFactory()
*/
public static boolean hasResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
return (value != null);
}
/**
* Retrieve a resource for the given key that is bound to the current thread.
* @param key the key to check (usually the resource factory)
* @return a value bound to the current thread (usually the active
* resource object), or <code>null</code> if none
* @see ResourceTransactionManager#getResourceFactory()
*/
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
/**
* Actually check the value of the resource that is bound for the given key.
*/
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
value = null;
}
return value;
}
/**
* Bind the given resource for the given key to the current thread.
* @param key the key to bind the value to (usually the resource factory)
* @param value the value to bind (usually the active resource object)
* @throws IllegalStateException if there is already a value bound to the thread
* @see ResourceTransactionManager#getResourceFactory()
*/
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<Object, Object>();
resources.set(map);
}
if (map.put(actualKey, value) != null) {
throw new IllegalStateException("Already value [" + map.get(actualKey) + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
/**
* Unbind a resource for the given key from the current thread.
* @param key the key to unbind (usually the resource factory)
* @return the previously bound value (usually the active resource object)
* @throws IllegalStateException if there is no value bound to the thread
* @see ResourceTransactionManager#getResourceFactory()
*/
public static Object unbindResource(Object key) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
if (value == null) {
throw new IllegalStateException(
"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
/**
* Unbind a resource for the given key from the current thread.
* @param key the key to unbind (usually the resource factory)
* @return the previously bound value, or <code>null</code> if none bound
*/
public static Object unbindResourceIfPossible(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doUnbindResource(actualKey);
}
/**
* Actually remove the value of the resource that is bound for the given key.
*/
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
if (value != null && logger.isTraceEnabled()) {
logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
//-------------------------------------------------------------------------
// Management of transaction synchronizations
//-------------------------------------------------------------------------
/**
* Return if transaction synchronization is active for the current thread.
* Can be called before register to avoid unnecessary instance creation.
* @see #registerSynchronization
*/
public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
/**
* Activate transaction synchronization for the current thread.
* Called by a transaction manager on transaction begin.
* @throws IllegalStateException if synchronization is already active
*/
public static void initSynchronization() throws IllegalStateException {
if (isSynchronizationActive()) {
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
}
logger.trace("Initializing transaction synchronization");
synchronizations.set(new LinkedList<TransactionSynchronization>());
}
/**
* Register a new transaction synchronization for the current thread.
* Typically called by resource management code.
* <p>Note that synchronizations can implement the
* {@link org.springframework.core.Ordered} interface.
* They will be executed in an order according to their order value (if any).
* @param synchronization the synchronization object to register
* @throws IllegalStateException if transaction synchronization is not active
* @see org.springframework.core.Ordered
*/
public static void registerSynchronization(TransactionSynchronization synchronization)
throws IllegalStateException {
Assert.notNull(synchronization, "TransactionSynchronization must not be null");
if (!isSynchronizationActive()) {
throw new IllegalStateException("Transaction synchronization is not active");
}
synchronizations.get().add(synchronization);
}
/**
* Return an unmodifiable snapshot list of all registered synchronizations
* for the current thread.
* @return unmodifiable List of TransactionSynchronization instances
* @throws IllegalStateException if synchronization is not active
* @see TransactionSynchronization
*/
public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
List<TransactionSynchronization> synchs = synchronizations.get();
if (synchs == null) {
throw new IllegalStateException("Transaction synchronization is not active");
}
// Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions
// while iterating and invoking synchronization callbacks that in turn
// might register further synchronizations.
if (synchs.isEmpty()) {
return Collections.emptyList();
}
else {
// Sort lazily here, not in registerSynchronization.
OrderComparator.sort(synchs);
return Collections.unmodifiableList(new ArrayList<TransactionSynchronization>(synchs));
}
}
/**
* Deactivate transaction synchronization for the current thread.
* Called by the transaction manager on transaction cleanup.
* @throws IllegalStateException if synchronization is not active
*/
public static void clearSynchronization() throws IllegalStateException {
if (!isSynchronizationActive()) {
throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
}
logger.trace("Clearing transaction synchronization");
synchronizations.remove();
}
//-------------------------------------------------------------------------
// Exposure of transaction characteristics
//-------------------------------------------------------------------------
/**
* Expose the name of the current transaction, if any.
* Called by the transaction manager on transaction begin and on cleanup.
* @param name the name of the transaction, or <code>null</code> to reset it
* @see org.springframework.transaction.TransactionDefinition#getName()
*/
public static void setCurrentTransactionName(String name) {
currentTransactionName.set(name);
}
/**
* Return the name of the current transaction, or <code>null</code> if none set.
* To be called by resource management code for optimizations per use case,
* for example to optimize fetch strategies for specific named transactions.
* @see org.springframework.transaction.TransactionDefinition#getName()
*/
public static String getCurrentTransactionName() {
return currentTransactionName.get();
}
/**
* Expose a read-only flag for the current transaction.
* Called by the transaction manager on transaction begin and on cleanup.
* @param readOnly <code>true</code> to mark the current transaction
* as read-only; <code>false</code> to reset such a read-only marker
* @see org.springframework.transaction.TransactionDefinition#isReadOnly()
*/
public static void setCurrentTransactionReadOnly(boolean readOnly) {
currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null);
}
/**
* Return whether the current transaction is marked as read-only.
* To be called by resource management code when preparing a newly
* created resource (for example, a Hibernate Session).
* <p>Note that transaction synchronizations receive the read-only flag
* as argument for the <code>beforeCommit</code> callback, to be able
* to suppress change detection on commit. The present method is meant
* to be used for earlier read-only checks, for example to set the
* flush mode of a Hibernate Session to "FlushMode.NEVER" upfront.
* @see org.springframework.transaction.TransactionDefinition#isReadOnly()
* @see TransactionSynchronization#beforeCommit(boolean)
*/
public static boolean isCurrentTransactionReadOnly() {
return (currentTransactionReadOnly.get() != null);
}
/**
* Expose an isolation level for the current transaction.
* Called by the transaction manager on transaction begin and on cleanup.
* @param isolationLevel the isolation level to expose, according to the
* JDBC Connection constants (equivalent to the corresponding Spring
* TransactionDefinition constants), or <code>null</code> to reset it
* @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
* @see java.sql.Connection#TRANSACTION_READ_COMMITTED
* @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
* @see java.sql.Connection#TRANSACTION_SERIALIZABLE
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
* @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
*/
public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) {
currentTransactionIsolationLevel.set(isolationLevel);
}
/**
* Return the isolation level for the current transaction, if any.
* To be called by resource management code when preparing a newly
* created resource (for example, a JDBC Connection).
* @return the currently exposed isolation level, according to the
* JDBC Connection constants (equivalent to the corresponding Spring
* TransactionDefinition constants), or <code>null</code> if none
* @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
* @see java.sql.Connection#TRANSACTION_READ_COMMITTED
* @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
* @see java.sql.Connection#TRANSACTION_SERIALIZABLE
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
* @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
* @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
*/
public static Integer getCurrentTransactionIsolationLevel() {
return currentTransactionIsolationLevel.get();
}
/**
* Expose whether there currently is an actual transaction active.
* Called by the transaction manager on transaction begin and on cleanup.
* @param active <code>true</code> to mark the current thread as being associated
* with an actual transaction; <code>false</code> to reset that marker
*/
public static void setActualTransactionActive(boolean active) {
actualTransactionActive.set(active ? Boolean.TRUE : null);
}
/**
* Return whether there currently is an actual transaction active.
* This indicates whether the current thread is associated with an actual
* transaction rather than just with active transaction synchronization.
* <p>To be called by resource management code that wants to discriminate
* between active transaction synchronization (with or without backing
* resource transaction; also on PROPAGATION_SUPPORTS) and an actual
* transaction being active (with backing resource transaction;
* on PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etc).
* @see #isSynchronizationActive()
*/
public static boolean isActualTransactionActive() {
return (actualTransactionActive.get() != null);
}
/**
* Clear the entire transaction synchronization state for the current thread:
* registered synchronizations as well as the various transaction characteristics.
* @see #clearSynchronization()
* @see #setCurrentTransactionName
* @see #setCurrentTransactionReadOnly
* @see #setCurrentTransactionIsolationLevel
* @see #setActualTransactionActive
*/
public static void clear() {
clearSynchronization();
setCurrentTransactionName(null);
setCurrentTransactionReadOnly(false);
setCurrentTransactionIsolationLevel(null);
setActualTransactionActive(false);
}
}
|