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.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.core.Constants;
028import org.springframework.transaction.IllegalTransactionStateException;
029import org.springframework.transaction.InvalidTimeoutException;
030import org.springframework.transaction.NestedTransactionNotSupportedException;
031import org.springframework.transaction.PlatformTransactionManager;
032import org.springframework.transaction.TransactionDefinition;
033import org.springframework.transaction.TransactionException;
034import org.springframework.transaction.TransactionStatus;
035import org.springframework.transaction.TransactionSuspensionNotSupportedException;
036import org.springframework.transaction.UnexpectedRollbackException;
037
038/**
039 * Abstract base class that implements Spring's standard transaction workflow,
040 * serving as basis for concrete platform transaction managers like
041 * {@link org.springframework.transaction.jta.JtaTransactionManager}.
042 *
043 * <p>This base class provides the following workflow handling:
044 * <ul>
045 * <li>determines if there is an existing transaction;
046 * <li>applies the appropriate propagation behavior;
047 * <li>suspends and resumes transactions if necessary;
048 * <li>checks the rollback-only flag on commit;
049 * <li>applies the appropriate modification on rollback
050 * (actual rollback or setting rollback-only);
051 * <li>triggers registered synchronization callbacks
052 * (if transaction synchronization is active).
053 * </ul>
054 *
055 * <p>Subclasses have to implement specific template methods for specific
056 * states of a transaction, e.g.: begin, suspend, resume, commit, rollback.
057 * The most important of them are abstract and must be provided by a concrete
058 * implementation; for the rest, defaults are provided, so overriding is optional.
059 *
060 * <p>Transaction synchronization is a generic mechanism for registering callbacks
061 * that get invoked at transaction completion time. This is mainly used internally
062 * by the data access support classes for JDBC, Hibernate, JPA, etc when running
063 * within a JTA transaction: They register resources that are opened within the
064 * transaction for closing at transaction completion time, allowing e.g. for reuse
065 * of the same Hibernate Session within the transaction. The same mechanism can
066 * also be leveraged for custom synchronization needs in an application.
067 *
068 * <p>The state of this class is serializable, to allow for serializing the
069 * transaction strategy along with proxies that carry a transaction interceptor.
070 * It is up to subclasses if they wish to make their state to be serializable too.
071 * They should implement the {@code java.io.Serializable} marker interface in
072 * that case, and potentially a private {@code readObject()} method (according
073 * to Java serialization rules) if they need to restore any transient state.
074 *
075 * @author Juergen Hoeller
076 * @since 28.03.2003
077 * @see #setTransactionSynchronization
078 * @see TransactionSynchronizationManager
079 * @see org.springframework.transaction.jta.JtaTransactionManager
080 */
081@SuppressWarnings("serial")
082public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
083
084        /**
085         * Always activate transaction synchronization, even for "empty" transactions
086         * that result from PROPAGATION_SUPPORTS with no existing backend transaction.
087         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS
088         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED
089         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER
090         */
091        public static final int SYNCHRONIZATION_ALWAYS = 0;
092
093        /**
094         * Activate transaction synchronization only for actual transactions,
095         * that is, not for empty ones that result from PROPAGATION_SUPPORTS with
096         * no existing backend transaction.
097         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
098         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY
099         * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
100         */
101        public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
102
103        /**
104         * Never active transaction synchronization, not even for actual transactions.
105         */
106        public static final int SYNCHRONIZATION_NEVER = 2;
107
108
109        /** Constants instance for AbstractPlatformTransactionManager */
110        private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class);
111
112
113        protected transient Log logger = LogFactory.getLog(getClass());
114
115        private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
116
117        private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT;
118
119        private boolean nestedTransactionAllowed = false;
120
121        private boolean validateExistingTransaction = false;
122
123        private boolean globalRollbackOnParticipationFailure = true;
124
125        private boolean failEarlyOnGlobalRollbackOnly = false;
126
127        private boolean rollbackOnCommitFailure = false;
128
129
130        /**
131         * Set the transaction synchronization by the name of the corresponding constant
132         * in this class, e.g. "SYNCHRONIZATION_ALWAYS".
133         * @param constantName name of the constant
134         * @see #SYNCHRONIZATION_ALWAYS
135         */
136        public final void setTransactionSynchronizationName(String constantName) {
137                setTransactionSynchronization(constants.asNumber(constantName).intValue());
138        }
139
140        /**
141         * Set when this transaction manager should activate the thread-bound
142         * transaction synchronization support. Default is "always".
143         * <p>Note that transaction synchronization isn't supported for
144         * multiple concurrent transactions by different transaction managers.
145         * Only one transaction manager is allowed to activate it at any time.
146         * @see #SYNCHRONIZATION_ALWAYS
147         * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
148         * @see #SYNCHRONIZATION_NEVER
149         * @see TransactionSynchronizationManager
150         * @see TransactionSynchronization
151         */
152        public final void setTransactionSynchronization(int transactionSynchronization) {
153                this.transactionSynchronization = transactionSynchronization;
154        }
155
156        /**
157         * Return if this transaction manager should activate the thread-bound
158         * transaction synchronization support.
159         */
160        public final int getTransactionSynchronization() {
161                return this.transactionSynchronization;
162        }
163
164        /**
165         * Specify the default timeout that this transaction manager should apply
166         * if there is no timeout specified at the transaction level, in seconds.
167         * <p>Default is the underlying transaction infrastructure's default timeout,
168         * e.g. typically 30 seconds in case of a JTA provider, indicated by the
169         * {@code TransactionDefinition.TIMEOUT_DEFAULT} value.
170         * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT
171         */
172        public final void setDefaultTimeout(int defaultTimeout) {
173                if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) {
174                        throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout);
175                }
176                this.defaultTimeout = defaultTimeout;
177        }
178
179        /**
180         * Return the default timeout that this transaction manager should apply
181         * if there is no timeout specified at the transaction level, in seconds.
182         * <p>Returns {@code TransactionDefinition.TIMEOUT_DEFAULT} to indicate
183         * the underlying transaction infrastructure's default timeout.
184         */
185        public final int getDefaultTimeout() {
186                return this.defaultTimeout;
187        }
188
189        /**
190         * Set whether nested transactions are allowed. Default is "false".
191         * <p>Typically initialized with an appropriate default by the
192         * concrete transaction manager subclass.
193         */
194        public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) {
195                this.nestedTransactionAllowed = nestedTransactionAllowed;
196        }
197
198        /**
199         * Return whether nested transactions are allowed.
200         */
201        public final boolean isNestedTransactionAllowed() {
202                return this.nestedTransactionAllowed;
203        }
204
205        /**
206         * Set whether existing transactions should be validated before participating
207         * in them.
208         * <p>When participating in an existing transaction (e.g. with
209         * PROPAGATION_REQUIRED or PROPAGATION_SUPPORTS encountering an existing
210         * transaction), this outer transaction's characteristics will apply even
211         * to the inner transaction scope. Validation will detect incompatible
212         * isolation level and read-only settings on the inner transaction definition
213         * and reject participation accordingly through throwing a corresponding exception.
214         * <p>Default is "false", leniently ignoring inner transaction settings,
215         * simply overriding them with the outer transaction's characteristics.
216         * Switch this flag to "true" in order to enforce strict validation.
217         * @since 2.5.1
218         */
219        public final void setValidateExistingTransaction(boolean validateExistingTransaction) {
220                this.validateExistingTransaction = validateExistingTransaction;
221        }
222
223        /**
224         * Return whether existing transactions should be validated before participating
225         * in them.
226         * @since 2.5.1
227         */
228        public final boolean isValidateExistingTransaction() {
229                return this.validateExistingTransaction;
230        }
231
232        /**
233         * Set whether to globally mark an existing transaction as rollback-only
234         * after a participating transaction failed.
235         * <p>Default is "true": If a participating transaction (e.g. with
236         * PROPAGATION_REQUIRED or PROPAGATION_SUPPORTS encountering an existing
237         * transaction) fails, the transaction will be globally marked as rollback-only.
238         * The only possible outcome of such a transaction is a rollback: The
239         * transaction originator <i>cannot</i> make the transaction commit anymore.
240         * <p>Switch this to "false" to let the transaction originator make the rollback
241         * decision. If a participating transaction fails with an exception, the caller
242         * can still decide to continue with a different path within the transaction.
243         * However, note that this will only work as long as all participating resources
244         * are capable of continuing towards a transaction commit even after a data access
245         * failure: This is generally not the case for a Hibernate Session, for example;
246         * neither is it for a sequence of JDBC insert/update/delete operations.
247         * <p><b>Note:</b>This flag only applies to an explicit rollback attempt for a
248         * subtransaction, typically caused by an exception thrown by a data access operation
249         * (where TransactionInterceptor will trigger a {@code PlatformTransactionManager.rollback()}
250         * call according to a rollback rule). If the flag is off, the caller can handle the exception
251         * and decide on a rollback, independent of the rollback rules of the subtransaction.
252         * This flag does, however, <i>not</i> apply to explicit {@code setRollbackOnly}
253         * calls on a {@code TransactionStatus}, which will always cause an eventual
254         * global rollback (as it might not throw an exception after the rollback-only call).
255         * <p>The recommended solution for handling failure of a subtransaction
256         * is a "nested transaction", where the global transaction can be rolled
257         * back to a savepoint taken at the beginning of the subtransaction.
258         * PROPAGATION_NESTED provides exactly those semantics; however, it will
259         * only work when nested transaction support is available. This is the case
260         * with DataSourceTransactionManager, but not with JtaTransactionManager.
261         * @see #setNestedTransactionAllowed
262         * @see org.springframework.transaction.jta.JtaTransactionManager
263         */
264        public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) {
265                this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure;
266        }
267
268        /**
269         * Return whether to globally mark an existing transaction as rollback-only
270         * after a participating transaction failed.
271         */
272        public final boolean isGlobalRollbackOnParticipationFailure() {
273                return this.globalRollbackOnParticipationFailure;
274        }
275
276        /**
277         * Set whether to fail early in case of the transaction being globally marked
278         * as rollback-only.
279         * <p>Default is "false", only causing an UnexpectedRollbackException at the
280         * outermost transaction boundary. Switch this flag on to cause an
281         * UnexpectedRollbackException as early as the global rollback-only marker
282         * has been first detected, even from within an inner transaction boundary.
283         * <p>Note that, as of Spring 2.0, the fail-early behavior for global
284         * rollback-only markers has been unified: All transaction managers will by
285         * default only cause UnexpectedRollbackException at the outermost transaction
286         * boundary. This allows, for example, to continue unit tests even after an
287         * operation failed and the transaction will never be completed. All transaction
288         * managers will only fail earlier if this flag has explicitly been set to "true".
289         * @since 2.0
290         * @see org.springframework.transaction.UnexpectedRollbackException
291         */
292        public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) {
293                this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly;
294        }
295
296        /**
297         * Return whether to fail early in case of the transaction being globally marked
298         * as rollback-only.
299         * @since 2.0
300         */
301        public final boolean isFailEarlyOnGlobalRollbackOnly() {
302                return this.failEarlyOnGlobalRollbackOnly;
303        }
304
305        /**
306         * Set whether {@code doRollback} should be performed on failure of the
307         * {@code doCommit} call. Typically not necessary and thus to be avoided,
308         * as it can potentially override the commit exception with a subsequent
309         * rollback exception.
310         * <p>Default is "false".
311         * @see #doCommit
312         * @see #doRollback
313         */
314        public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) {
315                this.rollbackOnCommitFailure = rollbackOnCommitFailure;
316        }
317
318        /**
319         * Return whether {@code doRollback} should be performed on failure of the
320         * {@code doCommit} call.
321         */
322        public final boolean isRollbackOnCommitFailure() {
323                return this.rollbackOnCommitFailure;
324        }
325
326
327        //---------------------------------------------------------------------
328        // Implementation of PlatformTransactionManager
329        //---------------------------------------------------------------------
330
331        /**
332         * This implementation handles propagation behavior. Delegates to
333         * {@code doGetTransaction}, {@code isExistingTransaction}
334         * and {@code doBegin}.
335         * @see #doGetTransaction
336         * @see #isExistingTransaction
337         * @see #doBegin
338         */
339        @Override
340        public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
341                Object transaction = doGetTransaction();
342
343                // Cache debug flag to avoid repeated checks.
344                boolean debugEnabled = logger.isDebugEnabled();
345
346                if (definition == null) {
347                        // Use defaults if no transaction definition given.
348                        definition = new DefaultTransactionDefinition();
349                }
350
351                if (isExistingTransaction(transaction)) {
352                        // Existing transaction found -> check propagation behavior to find out how to behave.
353                        return handleExistingTransaction(definition, transaction, debugEnabled);
354                }
355
356                // Check definition settings for new transaction.
357                if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
358                        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
359                }
360
361                // No existing transaction found -> check propagation behavior to find out how to proceed.
362                if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
363                        throw new IllegalTransactionStateException(
364                                        "No existing transaction found for transaction marked with propagation 'mandatory'");
365                }
366                else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
367                                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
368                                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
369                        SuspendedResourcesHolder suspendedResources = suspend(null);
370                        if (debugEnabled) {
371                                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
372                        }
373                        try {
374                                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
375                                DefaultTransactionStatus status = newTransactionStatus(
376                                                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
377                                doBegin(transaction, definition);
378                                prepareSynchronization(status, definition);
379                                return status;
380                        }
381                        catch (RuntimeException ex) {
382                                resume(null, suspendedResources);
383                                throw ex;
384                        }
385                        catch (Error err) {
386                                resume(null, suspendedResources);
387                                throw err;
388                        }
389                }
390                else {
391                        // Create "empty" transaction: no actual transaction, but potentially synchronization.
392                        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
393                                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
394                                                "isolation level will effectively be ignored: " + definition);
395                        }
396                        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
397                        return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
398                }
399        }
400
401        /**
402         * Create a TransactionStatus for an existing transaction.
403         */
404        private TransactionStatus handleExistingTransaction(
405                        TransactionDefinition definition, Object transaction, boolean debugEnabled)
406                        throws TransactionException {
407
408                if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
409                        throw new IllegalTransactionStateException(
410                                        "Existing transaction found for transaction marked with propagation 'never'");
411                }
412
413                if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
414                        if (debugEnabled) {
415                                logger.debug("Suspending current transaction");
416                        }
417                        Object suspendedResources = suspend(transaction);
418                        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
419                        return prepareTransactionStatus(
420                                        definition, null, false, newSynchronization, debugEnabled, suspendedResources);
421                }
422
423                if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
424                        if (debugEnabled) {
425                                logger.debug("Suspending current transaction, creating new transaction with name [" +
426                                                definition.getName() + "]");
427                        }
428                        SuspendedResourcesHolder suspendedResources = suspend(transaction);
429                        try {
430                                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
431                                DefaultTransactionStatus status = newTransactionStatus(
432                                                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
433                                doBegin(transaction, definition);
434                                prepareSynchronization(status, definition);
435                                return status;
436                        }
437                        catch (RuntimeException beginEx) {
438                                resumeAfterBeginException(transaction, suspendedResources, beginEx);
439                                throw beginEx;
440                        }
441                        catch (Error beginErr) {
442                                resumeAfterBeginException(transaction, suspendedResources, beginErr);
443                                throw beginErr;
444                        }
445                }
446
447                if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
448                        if (!isNestedTransactionAllowed()) {
449                                throw new NestedTransactionNotSupportedException(
450                                                "Transaction manager does not allow nested transactions by default - " +
451                                                "specify 'nestedTransactionAllowed' property with value 'true'");
452                        }
453                        if (debugEnabled) {
454                                logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
455                        }
456                        if (useSavepointForNestedTransaction()) {
457                                // Create savepoint within existing Spring-managed transaction,
458                                // through the SavepointManager API implemented by TransactionStatus.
459                                // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
460                                DefaultTransactionStatus status =
461                                                prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
462                                status.createAndHoldSavepoint();
463                                return status;
464                        }
465                        else {
466                                // Nested transaction through nested begin and commit/rollback calls.
467                                // Usually only for JTA: Spring synchronization might get activated here
468                                // in case of a pre-existing JTA transaction.
469                                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
470                                DefaultTransactionStatus status = newTransactionStatus(
471                                                definition, transaction, true, newSynchronization, debugEnabled, null);
472                                doBegin(transaction, definition);
473                                prepareSynchronization(status, definition);
474                                return status;
475                        }
476                }
477
478                // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
479                if (debugEnabled) {
480                        logger.debug("Participating in existing transaction");
481                }
482                if (isValidateExistingTransaction()) {
483                        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
484                                Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
485                                if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
486                                        Constants isoConstants = DefaultTransactionDefinition.constants;
487                                        throw new IllegalTransactionStateException("Participating transaction with definition [" +
488                                                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
489                                                        (currentIsolationLevel != null ?
490                                                                        isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
491                                                                        "(unknown)"));
492                                }
493                        }
494                        if (!definition.isReadOnly()) {
495                                if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
496                                        throw new IllegalTransactionStateException("Participating transaction with definition [" +
497                                                        definition + "] is not marked as read-only but existing transaction is");
498                                }
499                        }
500                }
501                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
502                return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
503        }
504
505        /**
506         * Create a new TransactionStatus for the given arguments,
507         * also initializing transaction synchronization as appropriate.
508         * @see #newTransactionStatus
509         * @see #prepareTransactionStatus
510         */
511        protected final DefaultTransactionStatus prepareTransactionStatus(
512                        TransactionDefinition definition, Object transaction, boolean newTransaction,
513                        boolean newSynchronization, boolean debug, Object suspendedResources) {
514
515                DefaultTransactionStatus status = newTransactionStatus(
516                                definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
517                prepareSynchronization(status, definition);
518                return status;
519        }
520
521        /**
522         * Create a TransactionStatus instance for the given arguments.
523         */
524        protected DefaultTransactionStatus newTransactionStatus(
525                        TransactionDefinition definition, Object transaction, boolean newTransaction,
526                        boolean newSynchronization, boolean debug, Object suspendedResources) {
527
528                boolean actualNewSynchronization = newSynchronization &&
529                                !TransactionSynchronizationManager.isSynchronizationActive();
530                return new DefaultTransactionStatus(
531                                transaction, newTransaction, actualNewSynchronization,
532                                definition.isReadOnly(), debug, suspendedResources);
533        }
534
535        /**
536         * Initialize transaction synchronization as appropriate.
537         */
538        protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
539                if (status.isNewSynchronization()) {
540                        TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
541                        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
542                                        definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
543                                                        definition.getIsolationLevel() : null);
544                        TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
545                        TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
546                        TransactionSynchronizationManager.initSynchronization();
547                }
548        }
549
550        /**
551         * Determine the actual timeout to use for the given definition.
552         * Will fall back to this manager's default timeout if the
553         * transaction definition doesn't specify a non-default value.
554         * @param definition the transaction definition
555         * @return the actual timeout to use
556         * @see org.springframework.transaction.TransactionDefinition#getTimeout()
557         * @see #setDefaultTimeout
558         */
559        protected int determineTimeout(TransactionDefinition definition) {
560                if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
561                        return definition.getTimeout();
562                }
563                return getDefaultTimeout();
564        }
565
566
567        /**
568         * Suspend the given transaction. Suspends transaction synchronization first,
569         * then delegates to the {@code doSuspend} template method.
570         * @param transaction the current transaction object
571         * (or {@code null} to just suspend active synchronizations, if any)
572         * @return an object that holds suspended resources
573         * (or {@code null} if neither transaction nor synchronization active)
574         * @see #doSuspend
575         * @see #resume
576         */
577        protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
578                if (TransactionSynchronizationManager.isSynchronizationActive()) {
579                        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
580                        try {
581                                Object suspendedResources = null;
582                                if (transaction != null) {
583                                        suspendedResources = doSuspend(transaction);
584                                }
585                                String name = TransactionSynchronizationManager.getCurrentTransactionName();
586                                TransactionSynchronizationManager.setCurrentTransactionName(null);
587                                boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
588                                TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
589                                Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
590                                TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
591                                boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
592                                TransactionSynchronizationManager.setActualTransactionActive(false);
593                                return new SuspendedResourcesHolder(
594                                                suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
595                        }
596                        catch (RuntimeException ex) {
597                                // doSuspend failed - original transaction is still active...
598                                doResumeSynchronization(suspendedSynchronizations);
599                                throw ex;
600                        }
601                        catch (Error err) {
602                                // doSuspend failed - original transaction is still active...
603                                doResumeSynchronization(suspendedSynchronizations);
604                                throw err;
605                        }
606                }
607                else if (transaction != null) {
608                        // Transaction active but no synchronization active.
609                        Object suspendedResources = doSuspend(transaction);
610                        return new SuspendedResourcesHolder(suspendedResources);
611                }
612                else {
613                        // Neither transaction nor synchronization active.
614                        return null;
615                }
616        }
617
618        /**
619         * Resume the given transaction. Delegates to the {@code doResume}
620         * template method first, then resuming transaction synchronization.
621         * @param transaction the current transaction object
622         * @param resourcesHolder the object that holds suspended resources,
623         * as returned by {@code suspend} (or {@code null} to just
624         * resume synchronizations, if any)
625         * @see #doResume
626         * @see #suspend
627         */
628        protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder)
629                        throws TransactionException {
630
631                if (resourcesHolder != null) {
632                        Object suspendedResources = resourcesHolder.suspendedResources;
633                        if (suspendedResources != null) {
634                                doResume(transaction, suspendedResources);
635                        }
636                        List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
637                        if (suspendedSynchronizations != null) {
638                                TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
639                                TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
640                                TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
641                                TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
642                                doResumeSynchronization(suspendedSynchronizations);
643                        }
644                }
645        }
646
647        /**
648         * Resume outer transaction after inner transaction begin failed.
649         */
650        private void resumeAfterBeginException(
651                        Object transaction, SuspendedResourcesHolder suspendedResources, Throwable beginEx) {
652
653                String exMessage = "Inner transaction begin exception overridden by outer transaction resume exception";
654                try {
655                        resume(transaction, suspendedResources);
656                }
657                catch (RuntimeException resumeEx) {
658                        logger.error(exMessage, beginEx);
659                        throw resumeEx;
660                }
661                catch (Error resumeErr) {
662                        logger.error(exMessage, beginEx);
663                        throw resumeErr;
664                }
665        }
666
667        /**
668         * Suspend all current synchronizations and deactivate transaction
669         * synchronization for the current thread.
670         * @return the List of suspended TransactionSynchronization objects
671         */
672        private List<TransactionSynchronization> doSuspendSynchronization() {
673                List<TransactionSynchronization> suspendedSynchronizations =
674                                TransactionSynchronizationManager.getSynchronizations();
675                for (TransactionSynchronization synchronization : suspendedSynchronizations) {
676                        synchronization.suspend();
677                }
678                TransactionSynchronizationManager.clearSynchronization();
679                return suspendedSynchronizations;
680        }
681
682        /**
683         * Reactivate transaction synchronization for the current thread
684         * and resume all given synchronizations.
685         * @param suspendedSynchronizations a List of TransactionSynchronization objects
686         */
687        private void doResumeSynchronization(List<TransactionSynchronization> suspendedSynchronizations) {
688                TransactionSynchronizationManager.initSynchronization();
689                for (TransactionSynchronization synchronization : suspendedSynchronizations) {
690                        synchronization.resume();
691                        TransactionSynchronizationManager.registerSynchronization(synchronization);
692                }
693        }
694
695
696        /**
697         * This implementation of commit handles participating in existing
698         * transactions and programmatic rollback requests.
699         * Delegates to {@code isRollbackOnly}, {@code doCommit}
700         * and {@code rollback}.
701         * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
702         * @see #doCommit
703         * @see #rollback
704         */
705        @Override
706        public final void commit(TransactionStatus status) throws TransactionException {
707                if (status.isCompleted()) {
708                        throw new IllegalTransactionStateException(
709                                        "Transaction is already completed - do not call commit or rollback more than once per transaction");
710                }
711
712                DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
713                if (defStatus.isLocalRollbackOnly()) {
714                        if (defStatus.isDebug()) {
715                                logger.debug("Transactional code has requested rollback");
716                        }
717                        processRollback(defStatus);
718                        return;
719                }
720                if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
721                        if (defStatus.isDebug()) {
722                                logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
723                        }
724                        processRollback(defStatus);
725                        // Throw UnexpectedRollbackException only at outermost transaction boundary
726                        // or if explicitly asked to.
727                        if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
728                                throw new UnexpectedRollbackException(
729                                                "Transaction rolled back because it has been marked as rollback-only");
730                        }
731                        return;
732                }
733
734                processCommit(defStatus);
735        }
736
737        /**
738         * Process an actual commit.
739         * Rollback-only flags have already been checked and applied.
740         * @param status object representing the transaction
741         * @throws TransactionException in case of commit failure
742         */
743        private void processCommit(DefaultTransactionStatus status) throws TransactionException {
744                try {
745                        boolean beforeCompletionInvoked = false;
746                        try {
747                                prepareForCommit(status);
748                                triggerBeforeCommit(status);
749                                triggerBeforeCompletion(status);
750                                beforeCompletionInvoked = true;
751                                boolean globalRollbackOnly = false;
752                                if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
753                                        globalRollbackOnly = status.isGlobalRollbackOnly();
754                                }
755                                if (status.hasSavepoint()) {
756                                        if (status.isDebug()) {
757                                                logger.debug("Releasing transaction savepoint");
758                                        }
759                                        status.releaseHeldSavepoint();
760                                }
761                                else if (status.isNewTransaction()) {
762                                        if (status.isDebug()) {
763                                                logger.debug("Initiating transaction commit");
764                                        }
765                                        doCommit(status);
766                                }
767                                // Throw UnexpectedRollbackException if we have a global rollback-only
768                                // marker but still didn't get a corresponding exception from commit.
769                                if (globalRollbackOnly) {
770                                        throw new UnexpectedRollbackException(
771                                                        "Transaction silently rolled back because it has been marked as rollback-only");
772                                }
773                        }
774                        catch (UnexpectedRollbackException ex) {
775                                // can only be caused by doCommit
776                                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
777                                throw ex;
778                        }
779                        catch (TransactionException ex) {
780                                // can only be caused by doCommit
781                                if (isRollbackOnCommitFailure()) {
782                                        doRollbackOnCommitException(status, ex);
783                                }
784                                else {
785                                        triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
786                                }
787                                throw ex;
788                        }
789                        catch (RuntimeException ex) {
790                                if (!beforeCompletionInvoked) {
791                                        triggerBeforeCompletion(status);
792                                }
793                                doRollbackOnCommitException(status, ex);
794                                throw ex;
795                        }
796                        catch (Error err) {
797                                if (!beforeCompletionInvoked) {
798                                        triggerBeforeCompletion(status);
799                                }
800                                doRollbackOnCommitException(status, err);
801                                throw err;
802                        }
803
804                        // Trigger afterCommit callbacks, with an exception thrown there
805                        // propagated to callers but the transaction still considered as committed.
806                        try {
807                                triggerAfterCommit(status);
808                        }
809                        finally {
810                                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
811                        }
812
813                }
814                finally {
815                        cleanupAfterCompletion(status);
816                }
817        }
818
819        /**
820         * This implementation of rollback handles participating in existing
821         * transactions. Delegates to {@code doRollback} and
822         * {@code doSetRollbackOnly}.
823         * @see #doRollback
824         * @see #doSetRollbackOnly
825         */
826        @Override
827        public final void rollback(TransactionStatus status) throws TransactionException {
828                if (status.isCompleted()) {
829                        throw new IllegalTransactionStateException(
830                                        "Transaction is already completed - do not call commit or rollback more than once per transaction");
831                }
832
833                DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
834                processRollback(defStatus);
835        }
836
837        /**
838         * Process an actual rollback.
839         * The completed flag has already been checked.
840         * @param status object representing the transaction
841         * @throws TransactionException in case of rollback failure
842         */
843        private void processRollback(DefaultTransactionStatus status) {
844                try {
845                        try {
846                                triggerBeforeCompletion(status);
847                                if (status.hasSavepoint()) {
848                                        if (status.isDebug()) {
849                                                logger.debug("Rolling back transaction to savepoint");
850                                        }
851                                        status.rollbackToHeldSavepoint();
852                                }
853                                else if (status.isNewTransaction()) {
854                                        if (status.isDebug()) {
855                                                logger.debug("Initiating transaction rollback");
856                                        }
857                                        doRollback(status);
858                                }
859                                else if (status.hasTransaction()) {
860                                        if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
861                                                if (status.isDebug()) {
862                                                        logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
863                                                }
864                                                doSetRollbackOnly(status);
865                                        }
866                                        else {
867                                                if (status.isDebug()) {
868                                                        logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
869                                                }
870                                        }
871                                }
872                                else {
873                                        logger.debug("Should roll back transaction but cannot - no transaction available");
874                                }
875                        }
876                        catch (RuntimeException ex) {
877                                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
878                                throw ex;
879                        }
880                        catch (Error err) {
881                                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
882                                throw err;
883                        }
884                        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
885                }
886                finally {
887                        cleanupAfterCompletion(status);
888                }
889        }
890
891        /**
892         * Invoke {@code doRollback}, handling rollback exceptions properly.
893         * @param status object representing the transaction
894         * @param ex the thrown application exception or error
895         * @throws TransactionException in case of rollback failure
896         * @see #doRollback
897         */
898        private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) throws TransactionException {
899                try {
900                        if (status.isNewTransaction()) {
901                                if (status.isDebug()) {
902                                        logger.debug("Initiating transaction rollback after commit exception", ex);
903                                }
904                                doRollback(status);
905                        }
906                        else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) {
907                                if (status.isDebug()) {
908                                        logger.debug("Marking existing transaction as rollback-only after commit exception", ex);
909                                }
910                                doSetRollbackOnly(status);
911                        }
912                }
913                catch (RuntimeException rbex) {
914                        logger.error("Commit exception overridden by rollback exception", ex);
915                        triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
916                        throw rbex;
917                }
918                catch (Error rberr) {
919                        logger.error("Commit exception overridden by rollback exception", ex);
920                        triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
921                        throw rberr;
922                }
923                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
924        }
925
926
927        /**
928         * Trigger {@code beforeCommit} callbacks.
929         * @param status object representing the transaction
930         */
931        protected final void triggerBeforeCommit(DefaultTransactionStatus status) {
932                if (status.isNewSynchronization()) {
933                        if (status.isDebug()) {
934                                logger.trace("Triggering beforeCommit synchronization");
935                        }
936                        TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());
937                }
938        }
939
940        /**
941         * Trigger {@code beforeCompletion} callbacks.
942         * @param status object representing the transaction
943         */
944        protected final void triggerBeforeCompletion(DefaultTransactionStatus status) {
945                if (status.isNewSynchronization()) {
946                        if (status.isDebug()) {
947                                logger.trace("Triggering beforeCompletion synchronization");
948                        }
949                        TransactionSynchronizationUtils.triggerBeforeCompletion();
950                }
951        }
952
953        /**
954         * Trigger {@code afterCommit} callbacks.
955         * @param status object representing the transaction
956         */
957        private void triggerAfterCommit(DefaultTransactionStatus status) {
958                if (status.isNewSynchronization()) {
959                        if (status.isDebug()) {
960                                logger.trace("Triggering afterCommit synchronization");
961                        }
962                        TransactionSynchronizationUtils.triggerAfterCommit();
963                }
964        }
965
966        /**
967         * Trigger {@code afterCompletion} callbacks.
968         * @param status object representing the transaction
969         * @param completionStatus completion status according to TransactionSynchronization constants
970         */
971        private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
972                if (status.isNewSynchronization()) {
973                        List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
974                        TransactionSynchronizationManager.clearSynchronization();
975                        if (!status.hasTransaction() || status.isNewTransaction()) {
976                                if (status.isDebug()) {
977                                        logger.trace("Triggering afterCompletion synchronization");
978                                }
979                                // No transaction or new transaction for the current scope ->
980                                // invoke the afterCompletion callbacks immediately
981                                invokeAfterCompletion(synchronizations, completionStatus);
982                        }
983                        else if (!synchronizations.isEmpty()) {
984                                // Existing transaction that we participate in, controlled outside
985                                // of the scope of this Spring transaction manager -> try to register
986                                // an afterCompletion callback with the existing (JTA) transaction.
987                                registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
988                        }
989                }
990        }
991
992        /**
993         * Actually invoke the {@code afterCompletion} methods of the
994         * given Spring TransactionSynchronization objects.
995         * <p>To be called by this abstract manager itself, or by special implementations
996         * of the {@code registerAfterCompletionWithExistingTransaction} callback.
997         * @param synchronizations a List of TransactionSynchronization objects
998         * @param completionStatus the completion status according to the
999         * constants in the TransactionSynchronization interface
1000         * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List)
1001         * @see TransactionSynchronization#STATUS_COMMITTED
1002         * @see TransactionSynchronization#STATUS_ROLLED_BACK
1003         * @see TransactionSynchronization#STATUS_UNKNOWN
1004         */
1005        protected final void invokeAfterCompletion(List<TransactionSynchronization> synchronizations, int completionStatus) {
1006                TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus);
1007        }
1008
1009        /**
1010         * Clean up after completion, clearing synchronization if necessary,
1011         * and invoking doCleanupAfterCompletion.
1012         * @param status object representing the transaction
1013         * @see #doCleanupAfterCompletion
1014         */
1015        private void cleanupAfterCompletion(DefaultTransactionStatus status) {
1016                status.setCompleted();
1017                if (status.isNewSynchronization()) {
1018                        TransactionSynchronizationManager.clear();
1019                }
1020                if (status.isNewTransaction()) {
1021                        doCleanupAfterCompletion(status.getTransaction());
1022                }
1023                if (status.getSuspendedResources() != null) {
1024                        if (status.isDebug()) {
1025                                logger.debug("Resuming suspended transaction after completion of inner transaction");
1026                        }
1027                        resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
1028                }
1029        }
1030
1031
1032        //---------------------------------------------------------------------
1033        // Template methods to be implemented in subclasses
1034        //---------------------------------------------------------------------
1035
1036        /**
1037         * Return a transaction object for the current transaction state.
1038         * <p>The returned object will usually be specific to the concrete transaction
1039         * manager implementation, carrying corresponding transaction state in a
1040         * modifiable fashion. This object will be passed into the other template
1041         * methods (e.g. doBegin and doCommit), either directly or as part of a
1042         * DefaultTransactionStatus instance.
1043         * <p>The returned object should contain information about any existing
1044         * transaction, that is, a transaction that has already started before the
1045         * current {@code getTransaction} call on the transaction manager.
1046         * Consequently, a {@code doGetTransaction} implementation will usually
1047         * look for an existing transaction and store corresponding state in the
1048         * returned transaction object.
1049         * @return the current transaction object
1050         * @throws org.springframework.transaction.CannotCreateTransactionException
1051         * if transaction support is not available
1052         * @throws TransactionException in case of lookup or system errors
1053         * @see #doBegin
1054         * @see #doCommit
1055         * @see #doRollback
1056         * @see DefaultTransactionStatus#getTransaction
1057         */
1058        protected abstract Object doGetTransaction() throws TransactionException;
1059
1060        /**
1061         * Check if the given transaction object indicates an existing transaction
1062         * (that is, a transaction which has already started).
1063         * <p>The result will be evaluated according to the specified propagation
1064         * behavior for the new transaction. An existing transaction might get
1065         * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction
1066         * might participate in the existing one (in case of PROPAGATION_REQUIRED).
1067         * <p>The default implementation returns {@code false}, assuming that
1068         * participating in existing transactions is generally not supported.
1069         * Subclasses are of course encouraged to provide such support.
1070         * @param transaction transaction object returned by doGetTransaction
1071         * @return if there is an existing transaction
1072         * @throws TransactionException in case of system errors
1073         * @see #doGetTransaction
1074         */
1075        protected boolean isExistingTransaction(Object transaction) throws TransactionException {
1076                return false;
1077        }
1078
1079        /**
1080         * Return whether to use a savepoint for a nested transaction.
1081         * <p>Default is {@code true}, which causes delegation to DefaultTransactionStatus
1082         * for creating and holding a savepoint. If the transaction object does not implement
1083         * the SavepointManager interface, a NestedTransactionNotSupportedException will be
1084         * thrown. Else, the SavepointManager will be asked to create a new savepoint to
1085         * demarcate the start of the nested transaction.
1086         * <p>Subclasses can override this to return {@code false}, causing a further
1087         * call to {@code doBegin} - within the context of an already existing transaction.
1088         * The {@code doBegin} implementation needs to handle this accordingly in such
1089         * a scenario. This is appropriate for JTA, for example.
1090         * @see DefaultTransactionStatus#createAndHoldSavepoint
1091         * @see DefaultTransactionStatus#rollbackToHeldSavepoint
1092         * @see DefaultTransactionStatus#releaseHeldSavepoint
1093         * @see #doBegin
1094         */
1095        protected boolean useSavepointForNestedTransaction() {
1096                return true;
1097        }
1098
1099        /**
1100         * Begin a new transaction with semantics according to the given transaction
1101         * definition. Does not have to care about applying the propagation behavior,
1102         * as this has already been handled by this abstract manager.
1103         * <p>This method gets called when the transaction manager has decided to actually
1104         * start a new transaction. Either there wasn't any transaction before, or the
1105         * previous transaction has been suspended.
1106         * <p>A special scenario is a nested transaction without savepoint: If
1107         * {@code useSavepointForNestedTransaction()} returns "false", this method
1108         * will be called to start a nested transaction when necessary. In such a context,
1109         * there will be an active transaction: The implementation of this method has
1110         * to detect this and start an appropriate nested transaction.
1111         * @param transaction transaction object returned by {@code doGetTransaction}
1112         * @param definition TransactionDefinition instance, describing propagation
1113         * behavior, isolation level, read-only flag, timeout, and transaction name
1114         * @throws TransactionException in case of creation or system errors
1115         */
1116        protected abstract void doBegin(Object transaction, TransactionDefinition definition)
1117                        throws TransactionException;
1118
1119        /**
1120         * Suspend the resources of the current transaction.
1121         * Transaction synchronization will already have been suspended.
1122         * <p>The default implementation throws a TransactionSuspensionNotSupportedException,
1123         * assuming that transaction suspension is generally not supported.
1124         * @param transaction transaction object returned by {@code doGetTransaction}
1125         * @return an object that holds suspended resources
1126         * (will be kept unexamined for passing it into doResume)
1127         * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException
1128         * if suspending is not supported by the transaction manager implementation
1129         * @throws TransactionException in case of system errors
1130         * @see #doResume
1131         */
1132        protected Object doSuspend(Object transaction) throws TransactionException {
1133                throw new TransactionSuspensionNotSupportedException(
1134                                "Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
1135        }
1136
1137        /**
1138         * Resume the resources of the current transaction.
1139         * Transaction synchronization will be resumed afterwards.
1140         * <p>The default implementation throws a TransactionSuspensionNotSupportedException,
1141         * assuming that transaction suspension is generally not supported.
1142         * @param transaction transaction object returned by {@code doGetTransaction}
1143         * @param suspendedResources the object that holds suspended resources,
1144         * as returned by doSuspend
1145         * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException
1146         * if resuming is not supported by the transaction manager implementation
1147         * @throws TransactionException in case of system errors
1148         * @see #doSuspend
1149         */
1150        protected void doResume(Object transaction, Object suspendedResources) throws TransactionException {
1151                throw new TransactionSuspensionNotSupportedException(
1152                                "Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
1153        }
1154
1155        /**
1156         * Return whether to call {@code doCommit} on a transaction that has been
1157         * marked as rollback-only in a global fashion.
1158         * <p>Does not apply if an application locally sets the transaction to rollback-only
1159         * via the TransactionStatus, but only to the transaction itself being marked as
1160         * rollback-only by the transaction coordinator.
1161         * <p>Default is "false": Local transaction strategies usually don't hold the rollback-only
1162         * marker in the transaction itself, therefore they can't handle rollback-only transactions
1163         * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger
1164         * a rollback in that case, throwing an UnexpectedRollbackException afterwards.
1165         * <p>Override this to return "true" if the concrete transaction manager expects a
1166         * {@code doCommit} call even for a rollback-only transaction, allowing for
1167         * special handling there. This will, for example, be the case for JTA, where
1168         * {@code UserTransaction.commit} will check the read-only flag itself and
1169         * throw a corresponding RollbackException, which might include the specific reason
1170         * (such as a transaction timeout).
1171         * <p>If this method returns "true" but the {@code doCommit} implementation does not
1172         * throw an exception, this transaction manager will throw an UnexpectedRollbackException
1173         * itself. This should not be the typical case; it is mainly checked to cover misbehaving
1174         * JTA providers that silently roll back even when the rollback has not been requested
1175         * by the calling code.
1176         * @see #doCommit
1177         * @see DefaultTransactionStatus#isGlobalRollbackOnly()
1178         * @see DefaultTransactionStatus#isLocalRollbackOnly()
1179         * @see org.springframework.transaction.TransactionStatus#setRollbackOnly()
1180         * @see org.springframework.transaction.UnexpectedRollbackException
1181         * @see javax.transaction.UserTransaction#commit()
1182         * @see javax.transaction.RollbackException
1183         */
1184        protected boolean shouldCommitOnGlobalRollbackOnly() {
1185                return false;
1186        }
1187
1188        /**
1189         * Make preparations for commit, to be performed before the
1190         * {@code beforeCommit} synchronization callbacks occur.
1191         * <p>Note that exceptions will get propagated to the commit caller
1192         * and cause a rollback of the transaction.
1193         * @param status the status representation of the transaction
1194         * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
1195         * (note: do not throw TransactionException subclasses here!)
1196         */
1197        protected void prepareForCommit(DefaultTransactionStatus status) {
1198        }
1199
1200        /**
1201         * Perform an actual commit of the given transaction.
1202         * <p>An implementation does not need to check the "new transaction" flag
1203         * or the rollback-only flag; this will already have been handled before.
1204         * Usually, a straight commit will be performed on the transaction object
1205         * contained in the passed-in status.
1206         * @param status the status representation of the transaction
1207         * @throws TransactionException in case of commit or system errors
1208         * @see DefaultTransactionStatus#getTransaction
1209         */
1210        protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException;
1211
1212        /**
1213         * Perform an actual rollback of the given transaction.
1214         * <p>An implementation does not need to check the "new transaction" flag;
1215         * this will already have been handled before. Usually, a straight rollback
1216         * will be performed on the transaction object contained in the passed-in status.
1217         * @param status the status representation of the transaction
1218         * @throws TransactionException in case of system errors
1219         * @see DefaultTransactionStatus#getTransaction
1220         */
1221        protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException;
1222
1223        /**
1224         * Set the given transaction rollback-only. Only called on rollback
1225         * if the current transaction participates in an existing one.
1226         * <p>The default implementation throws an IllegalTransactionStateException,
1227         * assuming that participating in existing transactions is generally not
1228         * supported. Subclasses are of course encouraged to provide such support.
1229         * @param status the status representation of the transaction
1230         * @throws TransactionException in case of system errors
1231         */
1232        protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
1233                throw new IllegalTransactionStateException(
1234                                "Participating in existing transactions is not supported - when 'isExistingTransaction' " +
1235                                "returns true, appropriate 'doSetRollbackOnly' behavior must be provided");
1236        }
1237
1238        /**
1239         * Register the given list of transaction synchronizations with the existing transaction.
1240         * <p>Invoked when the control of the Spring transaction manager and thus all Spring
1241         * transaction synchronizations end, without the transaction being completed yet. This
1242         * is for example the case when participating in an existing JTA or EJB CMT transaction.
1243         * <p>The default implementation simply invokes the {@code afterCompletion} methods
1244         * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no
1245         * chance to determine the actual outcome of the outer transaction.
1246         * @param transaction transaction object returned by {@code doGetTransaction}
1247         * @param synchronizations a List of TransactionSynchronization objects
1248         * @throws TransactionException in case of system errors
1249         * @see #invokeAfterCompletion(java.util.List, int)
1250         * @see TransactionSynchronization#afterCompletion(int)
1251         * @see TransactionSynchronization#STATUS_UNKNOWN
1252         */
1253        protected void registerAfterCompletionWithExistingTransaction(
1254                        Object transaction, List<TransactionSynchronization> synchronizations) throws TransactionException {
1255
1256                logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " +
1257                                "processing Spring after-completion callbacks immediately, with outcome status 'unknown'");
1258                invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN);
1259        }
1260
1261        /**
1262         * Cleanup resources after transaction completion.
1263         * <p>Called after {@code doCommit} and {@code doRollback} execution,
1264         * on any outcome. The default implementation does nothing.
1265         * <p>Should not throw any exceptions but just issue warnings on errors.
1266         * @param transaction transaction object returned by {@code doGetTransaction}
1267         */
1268        protected void doCleanupAfterCompletion(Object transaction) {
1269        }
1270
1271
1272        //---------------------------------------------------------------------
1273        // Serialization support
1274        //---------------------------------------------------------------------
1275
1276        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1277                // Rely on default serialization; just initialize state after deserialization.
1278                ois.defaultReadObject();
1279
1280                // Initialize transient fields.
1281                this.logger = LogFactory.getLog(getClass());
1282        }
1283
1284
1285        /**
1286         * Holder for suspended resources.
1287         * Used internally by {@code suspend} and {@code resume}.
1288         */
1289        protected static class SuspendedResourcesHolder {
1290
1291                private final Object suspendedResources;
1292
1293                private List<TransactionSynchronization> suspendedSynchronizations;
1294
1295                private String name;
1296
1297                private boolean readOnly;
1298
1299                private Integer isolationLevel;
1300
1301                private boolean wasActive;
1302
1303                private SuspendedResourcesHolder(Object suspendedResources) {
1304                        this.suspendedResources = suspendedResources;
1305                }
1306
1307                private SuspendedResourcesHolder(
1308                                Object suspendedResources, List<TransactionSynchronization> suspendedSynchronizations,
1309                                String name, boolean readOnly, Integer isolationLevel, boolean wasActive) {
1310
1311                        this.suspendedResources = suspendedResources;
1312                        this.suspendedSynchronizations = suspendedSynchronizations;
1313                        this.name = name;
1314                        this.readOnly = readOnly;
1315                        this.isolationLevel = isolationLevel;
1316                        this.wasActive = wasActive;
1317                }
1318        }
1319
1320}