1 /*
2  * Copyright 2002-2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.transaction.support;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 
30 import org.springframework.core.NamedThreadLocal;
31 import org.springframework.core.OrderComparator;
32 import org.springframework.util.Assert;
33 
34 /**
35  * Central helper that manages resources and transaction synchronizations per thread.
36  * To be used by resource management code but not by typical application code.
37  *
38  * <p>Supports one resource per key without overwriting, that is, a resource needs
39  * to be removed before a new one can be set for the same key.
40  * Supports a list of transaction synchronizations if synchronization is active.
41  *
42  * <p>Resource management code should check for thread-bound resources, e.g. JDBC
43  * Connections or Hibernate Sessions, via <code>getResource</code>. Such code is
44  * normally not supposed to bind resources to threads, as this is the responsibility
45  * of transaction managers. A further option is to lazily bind on first use if
46  * transaction synchronization is active, for performing transactions that span
47  * an arbitrary number of resources.
48  *
49  * <p>Transaction synchronization must be activated and deactivated by a transaction
50  * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}.
51  * This is automatically supported by {@link AbstractPlatformTransactionManager},
52  * and thus by all standard Spring transaction managers, such as
53  * {@link org.springframework.transaction.jta.JtaTransactionManager} and
54  * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
55  *
56  * <p>Resource management code should only register synchronizations when this
57  * manager is active, which can be checked via {@link #isSynchronizationActive};
58  * it should perform immediate resource cleanup else. If transaction synchronization
59  * isn't active, there is either no current transaction, or the transaction manager
60  * doesn't support transaction synchronization.
61  *
62  * <p>Synchronization is for example used to always return the same resources
63  * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for
64  * any given DataSource or SessionFactory, respectively.
65  *
66  * @author Juergen Hoeller
67  * @since 02.06.2003
68  * @see #isSynchronizationActive
69  * @see #registerSynchronization
70  * @see TransactionSynchronization
71  * @see AbstractPlatformTransactionManager#setTransactionSynchronization
72  * @see org.springframework.transaction.jta.JtaTransactionManager
73  * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
74  * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
75  */
76 public abstract class TransactionSynchronizationManager {
77 
78 	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
79 
80 	private static final ThreadLocal<Map<Object, Object>> resources =
81 			new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
82 
83 	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
84 			new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
85 
86 	private static final ThreadLocal<String> currentTransactionName =
87 			new NamedThreadLocal<String>("Current transaction name");
88 
89 	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
90 			new NamedThreadLocal<Boolean>("Current transaction read-only status");
91 
92 	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
93 			new NamedThreadLocal<Integer>("Current transaction isolation level");
94 
95 	private static final ThreadLocal<Boolean> actualTransactionActive =
96 			new NamedThreadLocal<Boolean>("Actual transaction active");
97 
98 
99 	//-------------------------------------------------------------------------
100 	// Management of transaction-associated resource handles
101 	//-------------------------------------------------------------------------
102 
103 	/**
104 	 * Return all resources that are bound to the current thread.
105 	 * <p>Mainly for debugging purposes. Resource managers should always invoke
106 	 * <code>hasResource</code> for a specific resource key that they are interested in.
107 	 * @return a Map with resource keys (usually the resource factory) and resource
108 	 * values (usually the active resource object), or an empty Map if there are
109 	 * currently no resources bound
110 	 * @see #hasResource
111 	 */
getResourceMap()112 	public static Map<Object, Object> getResourceMap() {
113 		Map<Object, Object> map = resources.get();
114 		return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap());
115 	}
116 
117 	/**
118 	 * Check if there is a resource for the given key bound to the current thread.
119 	 * @param key the key to check (usually the resource factory)
120 	 * @return if there is a value bound to the current thread
121 	 * @see ResourceTransactionManager#getResourceFactory()
122 	 */
hasResource(Object key)123 	public static boolean hasResource(Object key) {
124 		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
125 		Object value = doGetResource(actualKey);
126 		return (value != null);
127 	}
128 
129 	/**
130 	 * Retrieve a resource for the given key that is bound to the current thread.
131 	 * @param key the key to check (usually the resource factory)
132 	 * @return a value bound to the current thread (usually the active
133 	 * resource object), or <code>null</code> if none
134 	 * @see ResourceTransactionManager#getResourceFactory()
135 	 */
getResource(Object key)136 	public static Object getResource(Object key) {
137 		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
138 		Object value = doGetResource(actualKey);
139 		if (value != null && logger.isTraceEnabled()) {
140 			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
141 					Thread.currentThread().getName() + "]");
142 		}
143 		return value;
144 	}
145 
146 	/**
147 	 * Actually check the value of the resource that is bound for the given key.
148 	 */
doGetResource(Object actualKey)149 	private static Object doGetResource(Object actualKey) {
150 		Map<Object, Object> map = resources.get();
151 		if (map == null) {
152 			return null;
153 		}
154 		Object value = map.get(actualKey);
155 		// Transparently remove ResourceHolder that was marked as void...
156 		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
157 			map.remove(actualKey);
158 			// Remove entire ThreadLocal if empty...
159 			if (map.isEmpty()) {
160 				resources.remove();
161 			}
162 			value = null;
163 		}
164 		return value;
165 	}
166 
167 	/**
168 	 * Bind the given resource for the given key to the current thread.
169 	 * @param key the key to bind the value to (usually the resource factory)
170 	 * @param value the value to bind (usually the active resource object)
171 	 * @throws IllegalStateException if there is already a value bound to the thread
172 	 * @see ResourceTransactionManager#getResourceFactory()
173 	 */
bindResource(Object key, Object value)174 	public static void bindResource(Object key, Object value) throws IllegalStateException {
175 		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
176 		Assert.notNull(value, "Value must not be null");
177 		Map<Object, Object> map = resources.get();
178 		// set ThreadLocal Map if none found
179 		if (map == null) {
180 			map = new HashMap<Object, Object>();
181 			resources.set(map);
182 		}
183 		Object oldValue = map.put(actualKey, value);
184 		// Transparently suppress a ResourceHolder that was marked as void...
185 		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
186 			oldValue = null;
187 		}
188 		if (oldValue != null) {
189 			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
190 					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
191 		}
192 		if (logger.isTraceEnabled()) {
193 			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
194 					Thread.currentThread().getName() + "]");
195 		}
196 	}
197 
198 	/**
199 	 * Unbind a resource for the given key from the current thread.
200 	 * @param key the key to unbind (usually the resource factory)
201 	 * @return the previously bound value (usually the active resource object)
202 	 * @throws IllegalStateException if there is no value bound to the thread
203 	 * @see ResourceTransactionManager#getResourceFactory()
204 	 */
unbindResource(Object key)205 	public static Object unbindResource(Object key) throws IllegalStateException {
206 		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
207 		Object value = doUnbindResource(actualKey);
208 		if (value == null) {
209 			throw new IllegalStateException(
210 					"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
211 		}
212 		return value;
213 	}
214 
215 	/**
216 	 * Unbind a resource for the given key from the current thread.
217 	 * @param key the key to unbind (usually the resource factory)
218 	 * @return the previously bound value, or <code>null</code> if none bound
219 	 */
unbindResourceIfPossible(Object key)220 	public static Object unbindResourceIfPossible(Object key) {
221 		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
222 		return doUnbindResource(actualKey);
223 	}
224 
225 	/**
226 	 * Actually remove the value of the resource that is bound for the given key.
227 	 */
doUnbindResource(Object actualKey)228 	private static Object doUnbindResource(Object actualKey) {
229 		Map<Object, Object> map = resources.get();
230 		if (map == null) {
231 			return null;
232 		}
233 		Object value = map.remove(actualKey);
234 		// Remove entire ThreadLocal if empty...
235 		if (map.isEmpty()) {
236 			resources.remove();
237 		}
238 		// Transparently suppress a ResourceHolder that was marked as void...
239 		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
240 			value = null;
241 		}
242 		if (value != null && logger.isTraceEnabled()) {
243 			logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
244 					Thread.currentThread().getName() + "]");
245 		}
246 		return value;
247 	}
248 
249 
250 	//-------------------------------------------------------------------------
251 	// Management of transaction synchronizations
252 	//-------------------------------------------------------------------------
253 
254 	/**
255 	 * Return if transaction synchronization is active for the current thread.
256 	 * Can be called before register to avoid unnecessary instance creation.
257 	 * @see #registerSynchronization
258 	 */
isSynchronizationActive()259 	public static boolean isSynchronizationActive() {
260 		return (synchronizations.get() != null);
261 	}
262 
263 	/**
264 	 * Activate transaction synchronization for the current thread.
265 	 * Called by a transaction manager on transaction begin.
266 	 * @throws IllegalStateException if synchronization is already active
267 	 */
initSynchronization()268 	public static void initSynchronization() throws IllegalStateException {
269 		if (isSynchronizationActive()) {
270 			throw new IllegalStateException("Cannot activate transaction synchronization - already active");
271 		}
272 		logger.trace("Initializing transaction synchronization");
273 		synchronizations.set(new LinkedHashSet<TransactionSynchronization>());
274 	}
275 
276 	/**
277 	 * Register a new transaction synchronization for the current thread.
278 	 * Typically called by resource management code.
279 	 * <p>Note that synchronizations can implement the
280 	 * {@link org.springframework.core.Ordered} interface.
281 	 * They will be executed in an order according to their order value (if any).
282 	 * @param synchronization the synchronization object to register
283 	 * @throws IllegalStateException if transaction synchronization is not active
284 	 * @see org.springframework.core.Ordered
285 	 */
registerSynchronization(TransactionSynchronization synchronization)286 	public static void registerSynchronization(TransactionSynchronization synchronization)
287 	    throws IllegalStateException {
288 
289 		Assert.notNull(synchronization, "TransactionSynchronization must not be null");
290 		if (!isSynchronizationActive()) {
291 			throw new IllegalStateException("Transaction synchronization is not active");
292 		}
293 		synchronizations.get().add(synchronization);
294 	}
295 
296 	/**
297 	 * Return an unmodifiable snapshot list of all registered synchronizations
298 	 * for the current thread.
299 	 * @return unmodifiable List of TransactionSynchronization instances
300 	 * @throws IllegalStateException if synchronization is not active
301 	 * @see TransactionSynchronization
302 	 */
getSynchronizations()303 	public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
304 		Set<TransactionSynchronization> synchs = synchronizations.get();
305 		if (synchs == null) {
306 			throw new IllegalStateException("Transaction synchronization is not active");
307 		}
308 		// Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions
309 		// while iterating and invoking synchronization callbacks that in turn
310 		// might register further synchronizations.
311 		if (synchs.isEmpty()) {
312 			return Collections.emptyList();
313 		}
314 		else {
315 			// Sort lazily here, not in registerSynchronization.
316 			List<TransactionSynchronization> sortedSynchs = new ArrayList<TransactionSynchronization>(synchs);
317 			OrderComparator.sort(sortedSynchs);
318 			return Collections.unmodifiableList(sortedSynchs);
319 		}
320 	}
321 
322 	/**
323 	 * Deactivate transaction synchronization for the current thread.
324 	 * Called by the transaction manager on transaction cleanup.
325 	 * @throws IllegalStateException if synchronization is not active
326 	 */
clearSynchronization()327 	public static void clearSynchronization() throws IllegalStateException {
328 		if (!isSynchronizationActive()) {
329 			throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
330 		}
331 		logger.trace("Clearing transaction synchronization");
332 		synchronizations.remove();
333 	}
334 
335 
336 	//-------------------------------------------------------------------------
337 	// Exposure of transaction characteristics
338 	//-------------------------------------------------------------------------
339 
340 	/**
341 	 * Expose the name of the current transaction, if any.
342 	 * Called by the transaction manager on transaction begin and on cleanup.
343 	 * @param name the name of the transaction, or <code>null</code> to reset it
344 	 * @see org.springframework.transaction.TransactionDefinition#getName()
345 	 */
setCurrentTransactionName(String name)346 	public static void setCurrentTransactionName(String name) {
347 		currentTransactionName.set(name);
348 	}
349 
350 	/**
351 	 * Return the name of the current transaction, or <code>null</code> if none set.
352 	 * To be called by resource management code for optimizations per use case,
353 	 * for example to optimize fetch strategies for specific named transactions.
354 	 * @see org.springframework.transaction.TransactionDefinition#getName()
355 	 */
getCurrentTransactionName()356 	public static String getCurrentTransactionName() {
357 		return currentTransactionName.get();
358 	}
359 
360 	/**
361 	 * Expose a read-only flag for the current transaction.
362 	 * Called by the transaction manager on transaction begin and on cleanup.
363 	 * @param readOnly <code>true</code> to mark the current transaction
364 	 * as read-only; <code>false</code> to reset such a read-only marker
365 	 * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
366 	 */
setCurrentTransactionReadOnly(boolean readOnly)367 	public static void setCurrentTransactionReadOnly(boolean readOnly) {
368 		currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null);
369 	}
370 
371 	/**
372 	 * Return whether the current transaction is marked as read-only.
373 	 * To be called by resource management code when preparing a newly
374 	 * created resource (for example, a Hibernate Session).
375 	 * <p>Note that transaction synchronizations receive the read-only flag
376 	 * as argument for the <code>beforeCommit</code> callback, to be able
377 	 * to suppress change detection on commit. The present method is meant
378 	 * to be used for earlier read-only checks, for example to set the
379 	 * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront.
380 	 * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
381 	 * @see TransactionSynchronization#beforeCommit(boolean)
382 	 */
isCurrentTransactionReadOnly()383 	public static boolean isCurrentTransactionReadOnly() {
384 		return (currentTransactionReadOnly.get() != null);
385 	}
386 
387 	/**
388 	 * Expose an isolation level for the current transaction.
389 	 * Called by the transaction manager on transaction begin and on cleanup.
390 	 * @param isolationLevel the isolation level to expose, according to the
391 	 * JDBC Connection constants (equivalent to the corresponding Spring
392 	 * TransactionDefinition constants), or <code>null</code> to reset it
393 	 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
394 	 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
395 	 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
396 	 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
397 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
398 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
399 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
400 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
401 	 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
402 	 */
setCurrentTransactionIsolationLevel(Integer isolationLevel)403 	public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) {
404 		currentTransactionIsolationLevel.set(isolationLevel);
405 	}
406 
407 	/**
408 	 * Return the isolation level for the current transaction, if any.
409 	 * To be called by resource management code when preparing a newly
410 	 * created resource (for example, a JDBC Connection).
411 	 * @return the currently exposed isolation level, according to the
412 	 * JDBC Connection constants (equivalent to the corresponding Spring
413 	 * TransactionDefinition constants), or <code>null</code> if none
414 	 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
415 	 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
416 	 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
417 	 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
418 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
419 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
420 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
421 	 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
422 	 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
423 	 */
getCurrentTransactionIsolationLevel()424 	public static Integer getCurrentTransactionIsolationLevel() {
425 		return currentTransactionIsolationLevel.get();
426 	}
427 
428 	/**
429 	 * Expose whether there currently is an actual transaction active.
430 	 * Called by the transaction manager on transaction begin and on cleanup.
431 	 * @param active <code>true</code> to mark the current thread as being associated
432 	 * with an actual transaction; <code>false</code> to reset that marker
433 	 */
setActualTransactionActive(boolean active)434 	public static void setActualTransactionActive(boolean active) {
435 		actualTransactionActive.set(active ? Boolean.TRUE : null);
436 	}
437 
438 	/**
439 	 * Return whether there currently is an actual transaction active.
440 	 * This indicates whether the current thread is associated with an actual
441 	 * transaction rather than just with active transaction synchronization.
442 	 * <p>To be called by resource management code that wants to discriminate
443 	 * between active transaction synchronization (with or without backing
444 	 * resource transaction; also on PROPAGATION_SUPPORTS) and an actual
445 	 * transaction being active (with backing resource transaction;
446 	 * on PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etc).
447 	 * @see #isSynchronizationActive()
448 	 */
isActualTransactionActive()449 	public static boolean isActualTransactionActive() {
450 		return (actualTransactionActive.get() != null);
451 	}
452 
453 
454 	/**
455 	 * Clear the entire transaction synchronization state for the current thread:
456 	 * registered synchronizations as well as the various transaction characteristics.
457 	 * @see #clearSynchronization()
458 	 * @see #setCurrentTransactionName
459 	 * @see #setCurrentTransactionReadOnly
460 	 * @see #setCurrentTransactionIsolationLevel
461 	 * @see #setActualTransactionActive
462 	 */
clear()463 	public static void clear() {
464 		clearSynchronization();
465 		setCurrentTransactionName(null);
466 		setCurrentTransactionReadOnly(false);
467 		setCurrentTransactionIsolationLevel(null);
468 		setActualTransactionActive(false);
469 	}
470 
471 }
472