001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.transaction.support;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.core.NamedThreadLocal;
031import org.springframework.core.annotation.AnnotationAwareOrderComparator;
032import org.springframework.util.Assert;
033
034/**
035 * Central delegate that manages resources and transaction synchronizations per thread.
036 * To be used by resource management code but not by typical application code.
037 *
038 * <p>Supports one resource per key without overwriting, that is, a resource needs
039 * to be removed before a new one can be set for the same key.
040 * Supports a list of transaction synchronizations if synchronization is active.
041 *
042 * <p>Resource management code should check for thread-bound resources, e.g. JDBC
043 * Connections or Hibernate Sessions, via {@code getResource}. Such code is
044 * normally not supposed to bind resources to threads, as this is the responsibility
045 * of transaction managers. A further option is to lazily bind on first use if
046 * transaction synchronization is active, for performing transactions that span
047 * an arbitrary number of resources.
048 *
049 * <p>Transaction synchronization must be activated and deactivated by a transaction
050 * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}.
051 * This is automatically supported by {@link AbstractPlatformTransactionManager},
052 * and thus by all standard Spring transaction managers, such as
053 * {@link org.springframework.transaction.jta.JtaTransactionManager} and
054 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
055 *
056 * <p>Resource management code should only register synchronizations when this
057 * manager is active, which can be checked via {@link #isSynchronizationActive};
058 * it should perform immediate resource cleanup else. If transaction synchronization
059 * isn't active, there is either no current transaction, or the transaction manager
060 * doesn't support transaction synchronization.
061 *
062 * <p>Synchronization is for example used to always return the same resources
063 * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for
064 * any given DataSource or SessionFactory, respectively.
065 *
066 * @author Juergen Hoeller
067 * @since 02.06.2003
068 * @see #isSynchronizationActive
069 * @see #registerSynchronization
070 * @see TransactionSynchronization
071 * @see AbstractPlatformTransactionManager#setTransactionSynchronization
072 * @see org.springframework.transaction.jta.JtaTransactionManager
073 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
074 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
075 */
076public abstract class TransactionSynchronizationManager {
077
078        private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
079
080        private static final ThreadLocal<Map<Object, Object>> resources =
081                        new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
082
083        private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
084                        new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
085
086        private static final ThreadLocal<String> currentTransactionName =
087                        new NamedThreadLocal<String>("Current transaction name");
088
089        private static final ThreadLocal<Boolean> currentTransactionReadOnly =
090                        new NamedThreadLocal<Boolean>("Current transaction read-only status");
091
092        private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
093                        new NamedThreadLocal<Integer>("Current transaction isolation level");
094
095        private static final ThreadLocal<Boolean> actualTransactionActive =
096                        new NamedThreadLocal<Boolean>("Actual transaction active");
097
098
099        //-------------------------------------------------------------------------
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} 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         */
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         */
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} if none
134         * @see ResourceTransactionManager#getResourceFactory()
135         */
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         */
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         */
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         */
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} if none bound
219         */
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         */
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         */
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         */
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         */
286        public static void registerSynchronization(TransactionSynchronization synchronization)
287                        throws IllegalStateException {
288
289                Assert.notNull(synchronization, "TransactionSynchronization must not be null");
290                Set<TransactionSynchronization> synchs = synchronizations.get();
291                if (synchs == null) {
292                        throw new IllegalStateException("Transaction synchronization is not active");
293                }
294                synchs.add(synchronization);
295        }
296
297        /**
298         * Return an unmodifiable snapshot list of all registered synchronizations
299         * for the current thread.
300         * @return unmodifiable List of TransactionSynchronization instances
301         * @throws IllegalStateException if synchronization is not active
302         * @see TransactionSynchronization
303         */
304        public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
305                Set<TransactionSynchronization> synchs = synchronizations.get();
306                if (synchs == null) {
307                        throw new IllegalStateException("Transaction synchronization is not active");
308                }
309                // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions
310                // while iterating and invoking synchronization callbacks that in turn
311                // might register further synchronizations.
312                if (synchs.isEmpty()) {
313                        return Collections.emptyList();
314                }
315                else {
316                        // Sort lazily here, not in registerSynchronization.
317                        List<TransactionSynchronization> sortedSynchs = new ArrayList<TransactionSynchronization>(synchs);
318                        AnnotationAwareOrderComparator.sort(sortedSynchs);
319                        return Collections.unmodifiableList(sortedSynchs);
320                }
321        }
322
323        /**
324         * Deactivate transaction synchronization for the current thread.
325         * Called by the transaction manager on transaction cleanup.
326         * @throws IllegalStateException if synchronization is not active
327         */
328        public static void clearSynchronization() throws IllegalStateException {
329                if (!isSynchronizationActive()) {
330                        throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
331                }
332                logger.trace("Clearing transaction synchronization");
333                synchronizations.remove();
334        }
335
336
337        //-------------------------------------------------------------------------
338        // Exposure of transaction characteristics
339        //-------------------------------------------------------------------------
340
341        /**
342         * Expose the name of the current transaction, if any.
343         * Called by the transaction manager on transaction begin and on cleanup.
344         * @param name the name of the transaction, or {@code null} to reset it
345         * @see org.springframework.transaction.TransactionDefinition#getName()
346         */
347        public static void setCurrentTransactionName(String name) {
348                currentTransactionName.set(name);
349        }
350
351        /**
352         * Return the name of the current transaction, or {@code null} if none set.
353         * To be called by resource management code for optimizations per use case,
354         * for example to optimize fetch strategies for specific named transactions.
355         * @see org.springframework.transaction.TransactionDefinition#getName()
356         */
357        public static String getCurrentTransactionName() {
358                return currentTransactionName.get();
359        }
360
361        /**
362         * Expose a read-only flag for the current transaction.
363         * Called by the transaction manager on transaction begin and on cleanup.
364         * @param readOnly {@code true} to mark the current transaction
365         * as read-only; {@code false} to reset such a read-only marker
366         * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
367         */
368        public static void setCurrentTransactionReadOnly(boolean readOnly) {
369                currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null);
370        }
371
372        /**
373         * Return whether the current transaction is marked as read-only.
374         * To be called by resource management code when preparing a newly
375         * created resource (for example, a Hibernate Session).
376         * <p>Note that transaction synchronizations receive the read-only flag
377         * as argument for the {@code beforeCommit} callback, to be able
378         * to suppress change detection on commit. The present method is meant
379         * to be used for earlier read-only checks, for example to set the
380         * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront.
381         * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
382         * @see TransactionSynchronization#beforeCommit(boolean)
383         */
384        public static boolean isCurrentTransactionReadOnly() {
385                return (currentTransactionReadOnly.get() != null);
386        }
387
388        /**
389         * Expose an isolation level for the current transaction.
390         * Called by the transaction manager on transaction begin and on cleanup.
391         * @param isolationLevel the isolation level to expose, according to the
392         * JDBC Connection constants (equivalent to the corresponding Spring
393         * TransactionDefinition constants), or {@code null} to reset it
394         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
395         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
396         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
397         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
398         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
399         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
400         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
401         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
402         * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
403         */
404        public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) {
405                currentTransactionIsolationLevel.set(isolationLevel);
406        }
407
408        /**
409         * Return the isolation level for the current transaction, if any.
410         * To be called by resource management code when preparing a newly
411         * created resource (for example, a JDBC Connection).
412         * @return the currently exposed isolation level, according to the
413         * JDBC Connection constants (equivalent to the corresponding Spring
414         * TransactionDefinition constants), or {@code null} if none
415         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
416         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
417         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
418         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
419         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
420         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
421         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
422         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
423         * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
424         */
425        public static Integer getCurrentTransactionIsolationLevel() {
426                return currentTransactionIsolationLevel.get();
427        }
428
429        /**
430         * Expose whether there currently is an actual transaction active.
431         * Called by the transaction manager on transaction begin and on cleanup.
432         * @param active {@code true} to mark the current thread as being associated
433         * with an actual transaction; {@code false} to reset that marker
434         */
435        public static void setActualTransactionActive(boolean active) {
436                actualTransactionActive.set(active ? Boolean.TRUE : null);
437        }
438
439        /**
440         * Return whether there currently is an actual transaction active.
441         * This indicates whether the current thread is associated with an actual
442         * transaction rather than just with active transaction synchronization.
443         * <p>To be called by resource management code that wants to discriminate
444         * between active transaction synchronization (with or without backing
445         * resource transaction; also on PROPAGATION_SUPPORTS) and an actual
446         * transaction being active (with backing resource transaction;
447         * on PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, etc).
448         * @see #isSynchronizationActive()
449         */
450        public static boolean isActualTransactionActive() {
451                return (actualTransactionActive.get() != null);
452        }
453
454
455        /**
456         * Clear the entire transaction synchronization state for the current thread:
457         * registered synchronizations as well as the various transaction characteristics.
458         * @see #clearSynchronization()
459         * @see #setCurrentTransactionName
460         * @see #setCurrentTransactionReadOnly
461         * @see #setCurrentTransactionIsolationLevel
462         * @see #setActualTransactionActive
463         */
464        public static void clear() {
465                synchronizations.remove();
466                currentTransactionName.remove();
467                currentTransactionReadOnly.remove();
468                currentTransactionIsolationLevel.remove();
469                actualTransactionActive.remove();
470        }
471
472}