001/*
002 * Copyright 2002-2020 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.lang.Nullable;
033import org.springframework.util.Assert;
034
035/**
036 * Central delegate that manages resources and transaction synchronizations per thread.
037 * To be used by resource management code but not by typical application code.
038 *
039 * <p>Supports one resource per key without overwriting, that is, a resource needs
040 * to be removed before a new one can be set for the same key.
041 * Supports a list of transaction synchronizations if synchronization is active.
042 *
043 * <p>Resource management code should check for thread-bound resources, e.g. JDBC
044 * Connections or Hibernate Sessions, via {@code getResource}. Such code is
045 * normally not supposed to bind resources to threads, as this is the responsibility
046 * of transaction managers. A further option is to lazily bind on first use if
047 * transaction synchronization is active, for performing transactions that span
048 * an arbitrary number of resources.
049 *
050 * <p>Transaction synchronization must be activated and deactivated by a transaction
051 * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}.
052 * This is automatically supported by {@link AbstractPlatformTransactionManager},
053 * and thus by all standard Spring transaction managers, such as
054 * {@link org.springframework.transaction.jta.JtaTransactionManager} and
055 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
056 *
057 * <p>Resource management code should only register synchronizations when this
058 * manager is active, which can be checked via {@link #isSynchronizationActive};
059 * it should perform immediate resource cleanup else. If transaction synchronization
060 * isn't active, there is either no current transaction, or the transaction manager
061 * doesn't support transaction synchronization.
062 *
063 * <p>Synchronization is for example used to always return the same resources
064 * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for
065 * any given DataSource or SessionFactory, respectively.
066 *
067 * @author Juergen Hoeller
068 * @since 02.06.2003
069 * @see #isSynchronizationActive
070 * @see #registerSynchronization
071 * @see TransactionSynchronization
072 * @see AbstractPlatformTransactionManager#setTransactionSynchronization
073 * @see org.springframework.transaction.jta.JtaTransactionManager
074 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
075 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
076 */
077public abstract class TransactionSynchronizationManager {
078
079        private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
080
081        private static final ThreadLocal<Map<Object, Object>> resources =
082                        new NamedThreadLocal<>("Transactional resources");
083
084        private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
085                        new NamedThreadLocal<>("Transaction synchronizations");
086
087        private static final ThreadLocal<String> currentTransactionName =
088                        new NamedThreadLocal<>("Current transaction name");
089
090        private static final ThreadLocal<Boolean> currentTransactionReadOnly =
091                        new NamedThreadLocal<>("Current transaction read-only status");
092
093        private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
094                        new NamedThreadLocal<>("Current transaction isolation level");
095
096        private static final ThreadLocal<Boolean> actualTransactionActive =
097                        new NamedThreadLocal<>("Actual transaction active");
098
099
100        //-------------------------------------------------------------------------
101        // Management of transaction-associated resource handles
102        //-------------------------------------------------------------------------
103
104        /**
105         * Return all resources that are bound to the current thread.
106         * <p>Mainly for debugging purposes. Resource managers should always invoke
107         * {@code hasResource} for a specific resource key that they are interested in.
108         * @return a Map with resource keys (usually the resource factory) and resource
109         * values (usually the active resource object), or an empty Map if there are
110         * currently no resources bound
111         * @see #hasResource
112         */
113        public static Map<Object, Object> getResourceMap() {
114                Map<Object, Object> map = resources.get();
115                return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap());
116        }
117
118        /**
119         * Check if there is a resource for the given key bound to the current thread.
120         * @param key the key to check (usually the resource factory)
121         * @return if there is a value bound to the current thread
122         * @see ResourceTransactionManager#getResourceFactory()
123         */
124        public static boolean hasResource(Object key) {
125                Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
126                Object value = doGetResource(actualKey);
127                return (value != null);
128        }
129
130        /**
131         * Retrieve a resource for the given key that is bound to the current thread.
132         * @param key the key to check (usually the resource factory)
133         * @return a value bound to the current thread (usually the active
134         * resource object), or {@code null} if none
135         * @see ResourceTransactionManager#getResourceFactory()
136         */
137        @Nullable
138        public static Object getResource(Object key) {
139                Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
140                Object value = doGetResource(actualKey);
141                if (value != null && logger.isTraceEnabled()) {
142                        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
143                                        Thread.currentThread().getName() + "]");
144                }
145                return value;
146        }
147
148        /**
149         * Actually check the value of the resource that is bound for the given key.
150         */
151        @Nullable
152        private static Object doGetResource(Object actualKey) {
153                Map<Object, Object> map = resources.get();
154                if (map == null) {
155                        return null;
156                }
157                Object value = map.get(actualKey);
158                // Transparently remove ResourceHolder that was marked as void...
159                if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
160                        map.remove(actualKey);
161                        // Remove entire ThreadLocal if empty...
162                        if (map.isEmpty()) {
163                                resources.remove();
164                        }
165                        value = null;
166                }
167                return value;
168        }
169
170        /**
171         * Bind the given resource for the given key to the current thread.
172         * @param key the key to bind the value to (usually the resource factory)
173         * @param value the value to bind (usually the active resource object)
174         * @throws IllegalStateException if there is already a value bound to the thread
175         * @see ResourceTransactionManager#getResourceFactory()
176         */
177        public static void bindResource(Object key, Object value) throws IllegalStateException {
178                Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
179                Assert.notNull(value, "Value must not be null");
180                Map<Object, Object> map = resources.get();
181                // set ThreadLocal Map if none found
182                if (map == null) {
183                        map = new HashMap<>();
184                        resources.set(map);
185                }
186                Object oldValue = map.put(actualKey, value);
187                // Transparently suppress a ResourceHolder that was marked as void...
188                if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
189                        oldValue = null;
190                }
191                if (oldValue != null) {
192                        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
193                                        actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
194                }
195                if (logger.isTraceEnabled()) {
196                        logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
197                                        Thread.currentThread().getName() + "]");
198                }
199        }
200
201        /**
202         * Unbind a resource for the given key from the current thread.
203         * @param key the key to unbind (usually the resource factory)
204         * @return the previously bound value (usually the active resource object)
205         * @throws IllegalStateException if there is no value bound to the thread
206         * @see ResourceTransactionManager#getResourceFactory()
207         */
208        public static Object unbindResource(Object key) throws IllegalStateException {
209                Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
210                Object value = doUnbindResource(actualKey);
211                if (value == null) {
212                        throw new IllegalStateException(
213                                        "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
214                }
215                return value;
216        }
217
218        /**
219         * Unbind a resource for the given key from the current thread.
220         * @param key the key to unbind (usually the resource factory)
221         * @return the previously bound value, or {@code null} if none bound
222         */
223        @Nullable
224        public static Object unbindResourceIfPossible(Object key) {
225                Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
226                return doUnbindResource(actualKey);
227        }
228
229        /**
230         * Actually remove the value of the resource that is bound for the given key.
231         */
232        @Nullable
233        private static Object doUnbindResource(Object actualKey) {
234                Map<Object, Object> map = resources.get();
235                if (map == null) {
236                        return null;
237                }
238                Object value = map.remove(actualKey);
239                // Remove entire ThreadLocal if empty...
240                if (map.isEmpty()) {
241                        resources.remove();
242                }
243                // Transparently suppress a ResourceHolder that was marked as void...
244                if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
245                        value = null;
246                }
247                if (value != null && logger.isTraceEnabled()) {
248                        logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
249                                        Thread.currentThread().getName() + "]");
250                }
251                return value;
252        }
253
254
255        //-------------------------------------------------------------------------
256        // Management of transaction synchronizations
257        //-------------------------------------------------------------------------
258
259        /**
260         * Return if transaction synchronization is active for the current thread.
261         * Can be called before register to avoid unnecessary instance creation.
262         * @see #registerSynchronization
263         */
264        public static boolean isSynchronizationActive() {
265                return (synchronizations.get() != null);
266        }
267
268        /**
269         * Activate transaction synchronization for the current thread.
270         * Called by a transaction manager on transaction begin.
271         * @throws IllegalStateException if synchronization is already active
272         */
273        public static void initSynchronization() throws IllegalStateException {
274                if (isSynchronizationActive()) {
275                        throw new IllegalStateException("Cannot activate transaction synchronization - already active");
276                }
277                logger.trace("Initializing transaction synchronization");
278                synchronizations.set(new LinkedHashSet<>());
279        }
280
281        /**
282         * Register a new transaction synchronization for the current thread.
283         * Typically called by resource management code.
284         * <p>Note that synchronizations can implement the
285         * {@link org.springframework.core.Ordered} interface.
286         * They will be executed in an order according to their order value (if any).
287         * @param synchronization the synchronization object to register
288         * @throws IllegalStateException if transaction synchronization is not active
289         * @see org.springframework.core.Ordered
290         */
291        public static void registerSynchronization(TransactionSynchronization synchronization)
292                        throws IllegalStateException {
293
294                Assert.notNull(synchronization, "TransactionSynchronization must not be null");
295                Set<TransactionSynchronization> synchs = synchronizations.get();
296                if (synchs == null) {
297                        throw new IllegalStateException("Transaction synchronization is not active");
298                }
299                synchs.add(synchronization);
300        }
301
302        /**
303         * Return an unmodifiable snapshot list of all registered synchronizations
304         * for the current thread.
305         * @return unmodifiable List of TransactionSynchronization instances
306         * @throws IllegalStateException if synchronization is not active
307         * @see TransactionSynchronization
308         */
309        public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
310                Set<TransactionSynchronization> synchs = synchronizations.get();
311                if (synchs == null) {
312                        throw new IllegalStateException("Transaction synchronization is not active");
313                }
314                // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions
315                // while iterating and invoking synchronization callbacks that in turn
316                // might register further synchronizations.
317                if (synchs.isEmpty()) {
318                        return Collections.emptyList();
319                }
320                else {
321                        // Sort lazily here, not in registerSynchronization.
322                        List<TransactionSynchronization> sortedSynchs = new ArrayList<>(synchs);
323                        AnnotationAwareOrderComparator.sort(sortedSynchs);
324                        return Collections.unmodifiableList(sortedSynchs);
325                }
326        }
327
328        /**
329         * Deactivate transaction synchronization for the current thread.
330         * Called by the transaction manager on transaction cleanup.
331         * @throws IllegalStateException if synchronization is not active
332         */
333        public static void clearSynchronization() throws IllegalStateException {
334                if (!isSynchronizationActive()) {
335                        throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
336                }
337                logger.trace("Clearing transaction synchronization");
338                synchronizations.remove();
339        }
340
341
342        //-------------------------------------------------------------------------
343        // Exposure of transaction characteristics
344        //-------------------------------------------------------------------------
345
346        /**
347         * Expose the name of the current transaction, if any.
348         * Called by the transaction manager on transaction begin and on cleanup.
349         * @param name the name of the transaction, or {@code null} to reset it
350         * @see org.springframework.transaction.TransactionDefinition#getName()
351         */
352        public static void setCurrentTransactionName(@Nullable String name) {
353                currentTransactionName.set(name);
354        }
355
356        /**
357         * Return the name of the current transaction, or {@code null} if none set.
358         * To be called by resource management code for optimizations per use case,
359         * for example to optimize fetch strategies for specific named transactions.
360         * @see org.springframework.transaction.TransactionDefinition#getName()
361         */
362        @Nullable
363        public static String getCurrentTransactionName() {
364                return currentTransactionName.get();
365        }
366
367        /**
368         * Expose a read-only flag for the current transaction.
369         * Called by the transaction manager on transaction begin and on cleanup.
370         * @param readOnly {@code true} to mark the current transaction
371         * as read-only; {@code false} to reset such a read-only marker
372         * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
373         */
374        public static void setCurrentTransactionReadOnly(boolean readOnly) {
375                currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null);
376        }
377
378        /**
379         * Return whether the current transaction is marked as read-only.
380         * To be called by resource management code when preparing a newly
381         * created resource (for example, a Hibernate Session).
382         * <p>Note that transaction synchronizations receive the read-only flag
383         * as argument for the {@code beforeCommit} callback, to be able
384         * to suppress change detection on commit. The present method is meant
385         * to be used for earlier read-only checks, for example to set the
386         * flush mode of a Hibernate Session to "FlushMode.MANUAL" upfront.
387         * @see org.springframework.transaction.TransactionDefinition#isReadOnly()
388         * @see TransactionSynchronization#beforeCommit(boolean)
389         */
390        public static boolean isCurrentTransactionReadOnly() {
391                return (currentTransactionReadOnly.get() != null);
392        }
393
394        /**
395         * Expose an isolation level for the current transaction.
396         * Called by the transaction manager on transaction begin and on cleanup.
397         * @param isolationLevel the isolation level to expose, according to the
398         * JDBC Connection constants (equivalent to the corresponding Spring
399         * TransactionDefinition constants), or {@code null} to reset it
400         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
401         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
402         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
403         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
404         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
405         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
406         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
407         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
408         * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
409         */
410        public static void setCurrentTransactionIsolationLevel(@Nullable Integer isolationLevel) {
411                currentTransactionIsolationLevel.set(isolationLevel);
412        }
413
414        /**
415         * Return the isolation level for the current transaction, if any.
416         * To be called by resource management code when preparing a newly
417         * created resource (for example, a JDBC Connection).
418         * @return the currently exposed isolation level, according to the
419         * JDBC Connection constants (equivalent to the corresponding Spring
420         * TransactionDefinition constants), or {@code null} if none
421         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
422         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
423         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
424         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
425         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
426         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
427         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
428         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
429         * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
430         */
431        @Nullable
432        public static Integer getCurrentTransactionIsolationLevel() {
433                return currentTransactionIsolationLevel.get();
434        }
435
436        /**
437         * Expose whether there currently is an actual transaction active.
438         * Called by the transaction manager on transaction begin and on cleanup.
439         * @param active {@code true} to mark the current thread as being associated
440         * with an actual transaction; {@code false} to reset that marker
441         */
442        public static void setActualTransactionActive(boolean active) {
443                actualTransactionActive.set(active ? Boolean.TRUE : null);
444        }
445
446        /**
447         * Return whether there currently is an actual transaction active.
448         * This indicates whether the current thread is associated with an actual
449         * transaction rather than just with active transaction synchronization.
450         * <p>To be called by resource management code that wants to discriminate
451         * between active transaction synchronization (with or without backing
452         * resource transaction; also on PROPAGATION_SUPPORTS) and an actual
453         * transaction being active (with backing resource transaction;
454         * on PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, etc).
455         * @see #isSynchronizationActive()
456         */
457        public static boolean isActualTransactionActive() {
458                return (actualTransactionActive.get() != null);
459        }
460
461
462        /**
463         * Clear the entire transaction synchronization state for the current thread:
464         * registered synchronizations as well as the various transaction characteristics.
465         * @see #clearSynchronization()
466         * @see #setCurrentTransactionName
467         * @see #setCurrentTransactionReadOnly
468         * @see #setCurrentTransactionIsolationLevel
469         * @see #setActualTransactionActive
470         */
471        public static void clear() {
472                synchronizations.remove();
473                currentTransactionName.remove();
474                currentTransactionReadOnly.remove();
475                currentTransactionIsolationLevel.remove();
476                actualTransactionActive.remove();
477        }
478
479}