KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > transaction > support > AbstractPlatformTransactionManager


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.transaction.support;
18
19 import java.io.IOException JavaDoc;
20 import java.io.ObjectInputStream JavaDoc;
21 import java.io.Serializable JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import org.springframework.core.Constants;
29 import org.springframework.transaction.IllegalTransactionStateException;
30 import org.springframework.transaction.InvalidTimeoutException;
31 import org.springframework.transaction.NestedTransactionNotSupportedException;
32 import org.springframework.transaction.PlatformTransactionManager;
33 import org.springframework.transaction.TransactionDefinition;
34 import org.springframework.transaction.TransactionException;
35 import org.springframework.transaction.TransactionStatus;
36 import org.springframework.transaction.TransactionSuspensionNotSupportedException;
37 import org.springframework.transaction.UnexpectedRollbackException;
38
39 /**
40  * Abstract base class that implements Spring's standard transaction workflow,
41  * serving as basis for concrete platform transaction managers like
42  * {@link org.springframework.transaction.jta.JtaTransactionManager} and
43  * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
44  *
45  * <p>This base class provides the following workflow handling:
46  * <ul>
47  * <li>determines if there is an existing transaction;
48  * <li>applies the appropriate propagation behavior;
49  * <li>suspends and resumes transactions if necessary;
50  * <li>checks the rollback-only flag on commit;
51  * <li>applies the appropriate modification on rollback
52  * (actual rollback or setting rollback-only);
53  * <li>triggers registered synchronization callbacks
54  * (if transaction synchronization is active).
55  * </ul>
56  *
57  * <p>Subclasses have to implement specific template methods for specific
58  * states of a transaction, e.g.: begin, suspend, resume, commit, rollback.
59  * The most important of them are abstract and must be provided by a concrete
60  * implementation; for the rest, defaults are provided, so overriding is optional.
61  *
62  * <p>Transaction synchronization is a generic mechanism for registering callbacks
63  * that get invoked at transaction completion time. This is mainly used internally
64  * by the data access support classes for JDBC, Hibernate, JDO, etc when running
65  * within a JTA transaction: They register resources that are opened within the
66  * transaction for closing at transaction completion time, allowing e.g. for reuse
67  * of the same Hibernate Session within the transaction. The same mechanism can
68  * also be leveraged for custom synchronization needs in an application.
69  *
70  * <p>The state of this class is serializable, to allow for serializing the
71  * transaction strategy along with proxies that carry a transaction interceptor.
72  * It is up to subclasses if they wish to make their state to be serializable too.
73  * They should implement the <code>java.io.Serializable</code> marker interface in
74  * that case, and potentially a private <code>readObject()</code> method (according
75  * to Java serialization rules) if they need to restore any transient state.
76  *
77  * @author Juergen Hoeller
78  * @since 28.03.2003
79  * @see #setTransactionSynchronization
80  * @see TransactionSynchronizationManager
81  * @see org.springframework.transaction.jta.JtaTransactionManager
82  * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
83  * @see org.springframework.orm.hibernate.HibernateTransactionManager
84  */

85 public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable JavaDoc {
86
87     /**
88      * Always activate transaction synchronization, even for "empty" transactions
89      * that result from PROPAGATION_SUPPORTS with no existing backend transaction.
90      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS
91      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED
92      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER
93      */

94     public static final int SYNCHRONIZATION_ALWAYS = 0;
95
96     /**
97      * Activate transaction synchronization only for actual transactions,
98      * that is, not for empty ones that result from PROPAGATION_SUPPORTS with
99      * no existing backend transaction.
100      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
101      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY
102      * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
103      */

104     public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
105
106     /**
107      * Never active transaction synchronization, not even for actual transactions.
108      */

109     public static final int SYNCHRONIZATION_NEVER = 2;
110
111
112     /** Constants instance for AbstractPlatformTransactionManager */
113     private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class);
114
115
116     /** Transient to optimize serialization */
117     protected transient Log logger = LogFactory.getLog(getClass());
118
119     private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
120
121     private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT;
122
123     private boolean nestedTransactionAllowed = false;
124
125     private boolean globalRollbackOnParticipationFailure = true;
126
127     private boolean failEarlyOnGlobalRollbackOnly = false;
128
129     private boolean rollbackOnCommitFailure = false;
130
131
132     /**
133      * Set the transaction synchronization by the name of the corresponding constant
134      * in this class, e.g. "SYNCHRONIZATION_ALWAYS".
135      * @param constantName name of the constant
136      * @see #SYNCHRONIZATION_ALWAYS
137      */

138     public final void setTransactionSynchronizationName(String JavaDoc constantName) {
139         setTransactionSynchronization(constants.asNumber(constantName).intValue());
140     }
141
142     /**
143      * Set when this transaction manager should activate the thread-bound
144      * transaction synchronization support. Default is "always".
145      * <p>Note that transaction synchronization isn't supported for
146      * multiple concurrent transactions by different transaction managers.
147      * Only one transaction manager is allowed to activate it at any time.
148      * @see #SYNCHRONIZATION_ALWAYS
149      * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
150      * @see #SYNCHRONIZATION_NEVER
151      * @see TransactionSynchronizationManager
152      * @see TransactionSynchronization
153      */

154     public final void setTransactionSynchronization(int transactionSynchronization) {
155         this.transactionSynchronization = transactionSynchronization;
156     }
157
158     /**
159      * Return if this transaction manager should activate the thread-bound
160      * transaction synchronization support.
161      */

162     public final int getTransactionSynchronization() {
163         return this.transactionSynchronization;
164     }
165
166     /**
167      * Specify the default timeout that this transaction manager should apply
168      * if there is no timeout specified at the transaction level, in seconds.
169      * <p>Default is the underlying transaction infrastructure's default timeout,
170      * e.g. typically 30 seconds in case of a JTA provider, indicated by the
171      * <code>TransactionDefinition.TIMEOUT_DEFAULT</code> value.
172      * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT
173      */

174     public final void setDefaultTimeout(int defaultTimeout) {
175         if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) {
176             throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout);
177         }
178         this.defaultTimeout = defaultTimeout;
179     }
180
181     /**
182      * Return the default timeout that this transaction manager should apply
183      * if there is no timeout specified at the transaction level, in seconds.
184      * <p>Returns <code>TransactionDefinition.TIMEOUT_DEFAULT</code> to indicate
185      * the underlying transaction infrastructure's default timeout.
186      */

187     public final int getDefaultTimeout() {
188         return this.defaultTimeout;
189     }
190
191     /**
192      * Set whether nested transactions are allowed. Default is "false".
193      * <p>Typically initialized with an appropriate default by the
194      * concrete transaction manager subclass.
195      */

196     public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) {
197         this.nestedTransactionAllowed = nestedTransactionAllowed;
198     }
199
200     /**
201      * Return whether nested transactions are allowed.
202      */

203     public final boolean isNestedTransactionAllowed() {
204         return this.nestedTransactionAllowed;
205     }
206
207     /**
208      * Set whether to globally mark an existing transaction as rollback-only
209      * after a participating transaction failed.
210      * <p>Default is "true": If a participating transaction (e.g. with
211      * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing
212      * transaction) fails, the transaction will be globally marked as rollback-only.
213      * The only possible outcome of such a transaction is a rollback: The
214      * transaction originator <i>cannot</i> make the transaction commit anymore.
215      * <p>Switch this to "false" to let the transaction originator make the rollback
216      * decision. If a participating transaction fails with an exception, the caller
217      * can still decide to continue with a different path within the transaction.
218      * However, note that this will only work as long as all participating resources
219      * are capable of continuing towards a transaction commit even after a data access
220      * failure: This is generally not the case for a Hibernate Session, for example;
221      * neither is it for a sequence of JDBC insert/update/delete operations.
222      * <p><b>Note:</b>This flag only applies to an explicit rollback attempt for a
223      * subtransaction, typically caused by an exception thrown by a data access operation
224      * (where TransactionInterceptor will trigger a <code>PlatformTransactionManager.rollback()</code>
225      * call according to a rollback rule). If the flag is off, the caller can handle the exception
226      * and decide on a rollback, independent of the rollback rules of the subtransaction.
227      * This flag does, however, <i>not</i> apply to explicit <code>setRollbackOnly</code>
228      * calls on a <code>TransactionStatus</code>, which will always cause an eventual
229      * global rollback (as it might not throw an exception after the rollback-only call).
230      * <p>The recommended solution for handling failure of a subtransaction
231      * is a "nested transaction", where the global transaction can be rolled
232      * back to a savepoint taken at the beginning of the subtransaction.
233      * PROPAGATION_NESTED provides exactly those semantics; however, it will
234      * only work when nested transaction support is available. This is the case
235      * with DataSourceTransactionManager, but not with JtaTransactionManager.
236      * @see #setNestedTransactionAllowed
237      * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
238      * @see org.springframework.transaction.jta.JtaTransactionManager
239      */

240     public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) {
241         this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure;
242     }
243
244     /**
245      * Return whether to globally mark an existing transaction as rollback-only
246      * after a participating transaction failed.
247      */

248     public final boolean isGlobalRollbackOnParticipationFailure() {
249         return this.globalRollbackOnParticipationFailure;
250     }
251
252     /**
253      * Set whether to fail early in case of the transaction being globally marked
254      * as rollback-only.
255      * <p>Default is "false", only causing an UnexpectedRollbackException at the
256      * outermost transaction boundary. Switch this flag on to cause an
257      * UnexpectedRollbackException as early as the global rollback-only marker
258      * has been first detected, even from within an inner transaction boundary.
259      * <p>Note that, as of Spring 2.0, the fail-early behavior for global
260      * rollback-only markers has been unified: All transaction managers will by
261      * default only cause UnexpectedRollbackException at the outermost transaction
262      * boundary. This allows, for example, to continue unit tests even after an
263      * operation failed and the transaction will never be completed. All transaction
264      * managers will only fail earlier if this flag has explicitly been set to "true".
265      * @see org.springframework.transaction.UnexpectedRollbackException
266      */

267     public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) {
268         this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly;
269     }
270
271     /**
272      * Return whether to fail early in case of the transaction being globally marked
273      * as rollback-only.
274      */

275     public final boolean isFailEarlyOnGlobalRollbackOnly() {
276         return this.failEarlyOnGlobalRollbackOnly;
277     }
278
279     /**
280      * Set whether <code>doRollback</code> should be performed on failure of the
281      * <code>doCommit</code> call. Typically not necessary and thus to be avoided,
282      * as it can potentially override the commit exception with a subsequent
283      * rollback exception.
284      * <p>Default is "false".
285      * @see #doCommit
286      * @see #doRollback
287      */

288     public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) {
289         this.rollbackOnCommitFailure = rollbackOnCommitFailure;
290     }
291
292     /**
293      * Return whether <code>doRollback</code> should be performed on failure of the
294      * <code>doCommit</code> call.
295      */

296     public final boolean isRollbackOnCommitFailure() {
297         return this.rollbackOnCommitFailure;
298     }
299
300
301     //---------------------------------------------------------------------
302
// Implementation of PlatformTransactionManager
303
//---------------------------------------------------------------------
304

305     /**
306      * This implementation handles propagation behavior. Delegates to
307      * <code>doGetTransaction</code>, <code>isExistingTransaction</code>
308      * and <code>doBegin</code>.
309      * @see #doGetTransaction
310      * @see #isExistingTransaction
311      * @see #doBegin
312      */

313     public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
314         Object JavaDoc transaction = doGetTransaction();
315
316         // Cache debug flag to avoid repeated checks.
317
boolean debugEnabled = logger.isDebugEnabled();
318         if (debugEnabled) {
319             logger.debug("Using transaction object [" + transaction + "]");
320         }
321
322         if (definition == null) {
323             // Use defaults if no transaction definition given.
324
definition = new DefaultTransactionDefinition();
325         }
326
327         if (isExistingTransaction(transaction)) {
328             // Existing transaction found -> check propagation behavior to find out how to behave.
329
return handleExistingTransaction(definition, transaction, debugEnabled);
330         }
331
332         // Check definition settings for new transaction.
333
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
334             throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
335         }
336
337         // No existing transaction found -> check propagation behavior to find out how to proceed.
338
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
339             throw new IllegalTransactionStateException(
340                     "No existing transaction found for transaction marked with propagation 'mandatory'");
341         }
342         else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
343                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
344             definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
345             Object JavaDoc suspendedResources = suspend(null);
346             if (debugEnabled) {
347                 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
348             }
349             doBegin(transaction, definition);
350             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
351             return newTransactionStatus(
352                     definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
353         }
354         else {
355             // Create "empty" transaction: no actual transaction, but potentially synchronization.
356
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
357             return newTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
358         }
359     }
360
361     /**
362      * Create a TransactionStatus for an existing transaction.
363      */

364     private TransactionStatus handleExistingTransaction(
365             TransactionDefinition definition, Object JavaDoc transaction, boolean debugEnabled)
366             throws TransactionException {
367
368         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
369             throw new IllegalTransactionStateException(
370                     "Existing transaction found for transaction marked with propagation 'never'");
371         }
372
373         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
374             if (debugEnabled) {
375                 logger.debug("Suspending current transaction");
376             }
377             Object JavaDoc suspendedResources = suspend(transaction);
378             boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
379             return newTransactionStatus(
380                     definition, null, false, newSynchronization, debugEnabled, suspendedResources);
381         }
382
383         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
384             if (debugEnabled) {
385                 logger.debug("Suspending current transaction, creating new transaction with name [" +
386                         definition.getName() + "]");
387             }
388             SuspendedResourcesHolder suspendedResources = suspend(transaction);
389             try {
390                 doBegin(transaction, definition);
391             }
392             catch (TransactionException beginEx) {
393                 try {
394                     resume(transaction, suspendedResources);
395                 }
396                 catch (TransactionException resumeEx) {
397                     logger.error(
398                             "Inner transaction begin exception overridden by outer transaction resume exception", beginEx);
399                     throw resumeEx;
400                 }
401                 throw beginEx;
402             }
403             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
404             return newTransactionStatus(
405                     definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
406         }
407
408         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
409             if (!isNestedTransactionAllowed()) {
410                 throw new NestedTransactionNotSupportedException(
411                         "Transaction manager does not allow nested transactions by default - " +
412                         "specify 'nestedTransactionAllowed' property with value 'true'");
413             }
414             if (debugEnabled) {
415                 logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
416             }
417             if (useSavepointForNestedTransaction()) {
418                 // Create savepoint within existing Spring-managed transaction,
419
// through the SavepointManager API implemented by TransactionStatus.
420
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
421
DefaultTransactionStatus status =
422                         newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
423                 status.createAndHoldSavepoint();
424                 return status;
425             }
426             else {
427                 // Nested transaction through nested begin and commit/rollback calls.
428
// Usually only for JTA: Spring synchronization might get activated here
429
// in case of a pre-existing JTA transaction.
430
doBegin(transaction, definition);
431                 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
432                 return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
433             }
434         }
435
436         // Assumably PROPAGATION_SUPPORTS.
437
if (debugEnabled) {
438             logger.debug("Participating in existing transaction");
439         }
440         boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
441         return newTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
442     }
443
444     /**
445      * Create a new TransactionStatus for the given arguments,
446      * initializing transaction synchronization as appropriate.
447      */

448     protected DefaultTransactionStatus newTransactionStatus(
449             TransactionDefinition definition, Object JavaDoc transaction, boolean newTransaction,
450             boolean newSynchronization, boolean debug, Object JavaDoc suspendedResources) {
451
452         boolean actualNewSynchronization = newSynchronization &&
453                 !TransactionSynchronizationManager.isSynchronizationActive();
454         if (actualNewSynchronization) {
455             TransactionSynchronizationManager.setActualTransactionActive(transaction != null);
456             TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
457                     (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ?
458                             new Integer JavaDoc(definition.getIsolationLevel()) : null);
459             TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
460             TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
461             TransactionSynchronizationManager.initSynchronization();
462         }
463         return new DefaultTransactionStatus(
464                 transaction, newTransaction, actualNewSynchronization,
465                 definition.isReadOnly(), debug, suspendedResources);
466     }
467
468     /**
469      * Determine the actual timeout to use for the given definition.
470      * Will fall back to this manager's default timeout if the
471      * transaction definition doesn't specify a non-default value.
472      * @param definition the transaction definition
473      * @return the actual timeout to use
474      * @see org.springframework.transaction.TransactionDefinition#getTimeout()
475      * @see #setDefaultTimeout
476      */

477     protected int determineTimeout(TransactionDefinition definition) {
478         if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
479             return definition.getTimeout();
480         }
481         return this.defaultTimeout;
482     }
483
484
485     /**
486      * Suspend the given transaction. Suspends transaction synchronization first,
487      * then delegates to the <code>doSuspend</code> template method.
488      * @param transaction the current transaction object
489      * (or <code>null</code> to just suspend active synchronizations, if any)
490      * @return an object that holds suspended resources
491      * (or <code>null</code> if neither transaction nor synchronization active)
492      * @see #doSuspend
493      * @see #resume
494      */

495     protected final SuspendedResourcesHolder suspend(Object JavaDoc transaction) throws TransactionException {
496         if (TransactionSynchronizationManager.isSynchronizationActive()) {
497             List JavaDoc suspendedSynchronizations = doSuspendSynchronization();
498             try {
499                 Object JavaDoc suspendedResources = null;
500                 if (transaction != null) {
501                     suspendedResources = doSuspend(transaction);
502                 }
503                 String JavaDoc name = TransactionSynchronizationManager.getCurrentTransactionName();
504                 TransactionSynchronizationManager.setCurrentTransactionName(null);
505                 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
506                 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
507                 Integer JavaDoc isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
508                 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
509                 TransactionSynchronizationManager.setActualTransactionActive(false);
510                 return new SuspendedResourcesHolder(
511                         suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel);
512             }
513             catch (TransactionException ex) {
514                 // doSuspend failed - original transaction is still active...
515
doResumeSynchronization(suspendedSynchronizations);
516                 throw ex;
517             }
518         }
519         else if (transaction != null) {
520             // Transaction active but no synchronization active.
521
Object JavaDoc suspendedResources = doSuspend(transaction);
522             return new SuspendedResourcesHolder(suspendedResources, null, null, false, null);
523         }
524         else {
525             // Neither transaction nor synchronization active.
526
return null;
527         }
528     }
529
530     /**
531      * Resume the given transaction. Delegates to the <code>doResume</code>
532      * template method first, then resuming transaction synchronization.
533      * @param transaction the current transaction object
534      * @param resourcesHolder the object that holds suspended resources,
535      * as returned by <code>suspend</code> (or <code>null</code> to just
536      * resume synchronizations, if any)
537      * @see #doResume
538      * @see #suspend
539      */

540     protected final void resume(Object JavaDoc transaction, SuspendedResourcesHolder resourcesHolder) throws TransactionException {
541         Object JavaDoc suspendedResources = resourcesHolder.suspendedResources;
542         if (suspendedResources != null) {
543             doResume(transaction, suspendedResources);
544         }
545         List JavaDoc suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
546         if (suspendedSynchronizations != null) {
547             TransactionSynchronizationManager.setActualTransactionActive(suspendedResources != null);
548             TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
549             TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
550             TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
551             doResumeSynchronization(suspendedSynchronizations);
552         }
553     }
554
555     /**
556      * Suspend all current synchronizations and deactivate transaction
557      * synchronization for the current thread.
558      * @return the List of suspended TransactionSynchronization objects
559      */

560     private List JavaDoc doSuspendSynchronization() {
561         List JavaDoc suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations();
562         for (Iterator JavaDoc it = suspendedSynchronizations.iterator(); it.hasNext();) {
563             ((TransactionSynchronization) it.next()).suspend();
564         }
565         TransactionSynchronizationManager.clearSynchronization();
566         return suspendedSynchronizations;
567     }
568
569     /**
570      * Reactivate transaction synchronization for the current thread
571      * and resume all given synchronizations.
572      * @param suspendedSynchronizations List of TransactionSynchronization objects
573      */

574     private void doResumeSynchronization(List JavaDoc suspendedSynchronizations) {
575         TransactionSynchronizationManager.initSynchronization();
576         for (Iterator JavaDoc it = suspendedSynchronizations.iterator(); it.hasNext();) {
577             TransactionSynchronization synchronization = (TransactionSynchronization) it.next();
578             synchronization.resume();
579             TransactionSynchronizationManager.registerSynchronization(synchronization);
580         }
581     }
582
583
584     /**
585      * This implementation of commit handles participating in existing
586      * transactions and programmatic rollback requests.
587      * Delegates to <code>isRollbackOnly</code>, <code>doCommit</code>
588      * and <code>rollback</code>.
589      * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
590      * @see #doCommit
591      * @see #rollback
592      */

593     public final void commit(TransactionStatus status) throws TransactionException {
594         if (status.isCompleted()) {
595             throw new IllegalTransactionStateException(
596                     "Transaction is already completed - do not call commit or rollback more than once per transaction");
597         }
598
599         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
600         if (defStatus.isLocalRollbackOnly()) {
601             if (defStatus.isDebug()) {
602                 logger.debug("Transactional code has requested rollback");
603             }
604             processRollback(defStatus);
605             return;
606         }
607         if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
608             if (defStatus.isDebug()) {
609                 logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
610             }
611             processRollback(defStatus);
612             // Throw UnexpectedRollbackException only at outermost transaction boundary
613
// or if explicitly asked to.
614
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
615                 throw new UnexpectedRollbackException(
616                         "Transaction rolled back because it has been marked as rollback-only");
617             }
618             return;
619         }
620
621         processCommit(defStatus);
622     }
623
624     /**
625      * Process an actual commit.
626      * Rollback-only flags have already been checked and applied.
627      * @param status object representing the transaction
628      * @throws TransactionException in case of commit failure
629      */

630     private void processCommit(DefaultTransactionStatus status) throws TransactionException {
631         try {
632             boolean beforeCompletionInvoked = false;
633             try {
634                 triggerBeforeCommit(status);
635                 triggerBeforeCompletion(status);
636                 beforeCompletionInvoked = true;
637                 boolean globalRollbackOnly = false;
638                 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
639                     globalRollbackOnly = status.isGlobalRollbackOnly();
640                 }
641                 if (status.hasSavepoint()) {
642                     if (status.isDebug()) {
643                         logger.debug("Releasing transaction savepoint");
644                     }
645                     status.releaseHeldSavepoint();
646                 }
647                 else if (status.isNewTransaction()) {
648                     if (status.isDebug()) {
649                         logger.debug("Initiating transaction commit");
650                     }
651                     doCommit(status);
652                 }
653                 // Throw UnexpectedRollbackException if we have a global rollback-only
654
// marker but still didn't get a corresponding exception from commit.
655
if (globalRollbackOnly) {
656                     throw new UnexpectedRollbackException(
657                             "Transaction silently rolled back because it has been marked as rollback-only");
658                 }
659             }
660             catch (UnexpectedRollbackException ex) {
661                 // can only be caused by doCommit
662
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
663                 throw ex;
664             }
665             catch (TransactionException ex) {
666                 // can only be caused by doCommit
667
if (isRollbackOnCommitFailure()) {
668                     doRollbackOnCommitException(status, ex);
669                 }
670                 else {
671                     triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
672                 }
673                 throw ex;
674             }
675             catch (RuntimeException JavaDoc ex) {
676                 if (!beforeCompletionInvoked) {
677                     triggerBeforeCompletion(status);
678                 }
679                 doRollbackOnCommitException(status, ex);
680                 throw ex;
681             }
682             catch (Error JavaDoc err) {
683                 if (!beforeCompletionInvoked) {
684                     triggerBeforeCompletion(status);
685                 }
686                 doRollbackOnCommitException(status, err);
687                 throw err;
688             }
689
690             // Trigger afterCommit callbacks, with an exception thrown there
691
// propagated to callers but the transaction still considered as committed.
692
try {
693                 triggerAfterCommit(status);
694             }
695             finally {
696                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
697             }
698
699         }
700         finally {
701             cleanupAfterCompletion(status);
702         }
703     }
704
705     /**
706      * This implementation of rollback handles participating in existing
707      * transactions. Delegates to <code>doRollback</code> and
708      * <code>doSetRollbackOnly</code>.
709      * @see #doRollback
710      * @see #doSetRollbackOnly
711      */

712     public final void rollback(TransactionStatus status) throws TransactionException {
713         if (status.isCompleted()) {
714             throw new IllegalTransactionStateException(
715                     "Transaction is already completed - do not call commit or rollback more than once per transaction");
716         }
717
718         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
719         processRollback(defStatus);
720     }
721
722     /**
723      * Process an actual rollback.
724      * The completed flag has already been checked.
725      * @param status object representing the transaction
726      * @throws TransactionException in case of rollback failure
727      */

728     private void processRollback(DefaultTransactionStatus status) {
729         try {
730             try {
731                 triggerBeforeCompletion(status);
732                 if (status.hasSavepoint()) {
733                     if (status.isDebug()) {
734                         logger.debug("Rolling back transaction to savepoint");
735                     }
736                     status.rollbackToHeldSavepoint();
737                 }
738                 else if (status.isNewTransaction()) {
739                     if (status.isDebug()) {
740                         logger.debug("Initiating transaction rollback");
741                     }
742                     doRollback(status);
743                 }
744                 else if (status.hasTransaction()) {
745                     if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
746                         if (status.isDebug()) {
747                             logger.debug(
748                                     "Participating transaction failed - marking existing transaction as rollback-only");
749                         }
750                         doSetRollbackOnly(status);
751                     }
752                     else {
753                         if (status.isDebug()) {
754                             logger.debug(
755                                     "Participating transaction failed - letting transaction originator decide on rollback");
756                         }
757                     }
758                 }
759                 else {
760                     logger.debug("Should roll back transaction but cannot - no transaction available");
761                 }
762             }
763             catch (RuntimeException JavaDoc ex) {
764                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
765                 throw ex;
766             }
767             catch (Error JavaDoc err) {
768                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
769                 throw err;
770             }
771             triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
772         }
773         finally {
774             cleanupAfterCompletion(status);
775         }
776     }
777
778     /**
779      * Invoke <code>doRollback</code>, handling rollback exceptions properly.
780      * @param status object representing the transaction
781      * @param ex the thrown application exception or error
782      * @throws TransactionException in case of rollback failure
783      * @see #doRollback
784      */

785     private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable JavaDoc ex)
786         throws TransactionException {
787         try {
788             if (status.isNewTransaction()) {
789                 if (status.isDebug()) {
790                     logger.debug("Initiating transaction rollback after commit exception", ex);
791                 }
792                 doRollback(status);
793             }
794             else