001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.transaction.interceptor;
018
019import java.lang.reflect.Method;
020import java.util.Properties;
021import java.util.concurrent.ConcurrentMap;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026import org.springframework.beans.factory.BeanFactory;
027import org.springframework.beans.factory.BeanFactoryAware;
028import org.springframework.beans.factory.InitializingBean;
029import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
030import org.springframework.core.NamedThreadLocal;
031import org.springframework.transaction.NoTransactionException;
032import org.springframework.transaction.PlatformTransactionManager;
033import org.springframework.transaction.TransactionStatus;
034import org.springframework.transaction.TransactionSystemException;
035import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
036import org.springframework.transaction.support.TransactionCallback;
037import org.springframework.util.ClassUtils;
038import org.springframework.util.ConcurrentReferenceHashMap;
039import org.springframework.util.StringUtils;
040
041/**
042 * Base class for transactional aspects, such as the {@link TransactionInterceptor}
043 * or an AspectJ aspect.
044 *
045 * <p>This enables the underlying Spring transaction infrastructure to be used easily
046 * to implement an aspect for any aspect system.
047 *
048 * <p>Subclasses are responsible for calling methods in this class in the correct order.
049 *
050 * <p>If no transaction name has been specified in the {@code TransactionAttribute},
051 * the exposed name will be the {@code fully-qualified class name + "." + method name}
052 * (by default).
053 *
054 * <p>Uses the <b>Strategy</b> design pattern. A {@code PlatformTransactionManager}
055 * implementation will perform the actual transaction management, and a
056 * {@code TransactionAttributeSource} is used for determining transaction definitions.
057 *
058 * <p>A transaction aspect is serializable if its {@code PlatformTransactionManager}
059 * and {@code TransactionAttributeSource} are serializable.
060 *
061 * @author Rod Johnson
062 * @author Juergen Hoeller
063 * @author St茅phane Nicoll
064 * @author Sam Brannen
065 * @since 1.1
066 * @see #setTransactionManager
067 * @see #setTransactionAttributes
068 * @see #setTransactionAttributeSource
069 */
070public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
071
072        // NOTE: This class must not implement Serializable because it serves as base
073        // class for AspectJ aspects (which are not allowed to implement Serializable)!
074
075
076        /**
077         * Key to use to store the default transaction manager.
078         */
079        private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
080
081        /**
082         * Holder to support the {@code currentTransactionStatus()} method,
083         * and to support communication between different cooperating advices
084         * (e.g. before and after advice) if the aspect involves more than a
085         * single method (as will be the case for around advice).
086         */
087        private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
088                        new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");
089
090
091        /**
092         * Subclasses can use this to return the current TransactionInfo.
093         * Only subclasses that cannot handle all operations in one method,
094         * such as an AspectJ aspect involving distinct before and after advice,
095         * need to use this mechanism to get at the current TransactionInfo.
096         * An around advice such as an AOP Alliance MethodInterceptor can hold a
097         * reference to the TransactionInfo throughout the aspect method.
098         * <p>A TransactionInfo will be returned even if no transaction was created.
099         * The {@code TransactionInfo.hasTransaction()} method can be used to query this.
100         * <p>To find out about specific transaction characteristics, consider using
101         * TransactionSynchronizationManager's {@code isSynchronizationActive()}
102         * and/or {@code isActualTransactionActive()} methods.
103         * @return the TransactionInfo bound to this thread, or {@code null} if none
104         * @see TransactionInfo#hasTransaction()
105         * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive()
106         * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
107         */
108        protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {
109                return transactionInfoHolder.get();
110        }
111
112        /**
113         * Return the transaction status of the current method invocation.
114         * Mainly intended for code that wants to set the current transaction
115         * rollback-only but not throw an application exception.
116         * @throws NoTransactionException if the transaction info cannot be found,
117         * because the method was invoked outside an AOP invocation context
118         */
119        public static TransactionStatus currentTransactionStatus() throws NoTransactionException {
120                TransactionInfo info = currentTransactionInfo();
121                if (info == null || info.transactionStatus == null) {
122                        throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
123                }
124                return info.transactionStatus;
125        }
126
127
128        protected final Log logger = LogFactory.getLog(getClass());
129
130        private String transactionManagerBeanName;
131
132        private PlatformTransactionManager transactionManager;
133
134        private TransactionAttributeSource transactionAttributeSource;
135
136        private BeanFactory beanFactory;
137
138        private final ConcurrentMap<Object, PlatformTransactionManager> transactionManagerCache =
139                        new ConcurrentReferenceHashMap<Object, PlatformTransactionManager>(4);
140
141
142        /**
143         * Specify the name of the default transaction manager bean.
144         */
145        public void setTransactionManagerBeanName(String transactionManagerBeanName) {
146                this.transactionManagerBeanName = transactionManagerBeanName;
147        }
148
149        /**
150         * Return the name of the default transaction manager bean.
151         */
152        protected final String getTransactionManagerBeanName() {
153                return this.transactionManagerBeanName;
154        }
155
156        /**
157         * Specify the <em>default</em> transaction manager to use to drive transactions.
158         * <p>The default transaction manager will be used if a <em>qualifier</em>
159         * has not been declared for a given transaction or if an explicit name for the
160         * default transaction manager bean has not been specified.
161         * @see #setTransactionManagerBeanName
162         */
163        public void setTransactionManager(PlatformTransactionManager transactionManager) {
164                this.transactionManager = transactionManager;
165        }
166
167        /**
168         * Return the default transaction manager, or {@code null} if unknown.
169         */
170        public PlatformTransactionManager getTransactionManager() {
171                return this.transactionManager;
172        }
173
174        /**
175         * Set properties with method names as keys and transaction attribute
176         * descriptors (parsed via TransactionAttributeEditor) as values:
177         * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly".
178         * <p>Note: Method names are always applied to the target class,
179         * no matter if defined in an interface or the class itself.
180         * <p>Internally, a NameMatchTransactionAttributeSource will be
181         * created from the given properties.
182         * @see #setTransactionAttributeSource
183         * @see TransactionAttributeEditor
184         * @see NameMatchTransactionAttributeSource
185         */
186        public void setTransactionAttributes(Properties transactionAttributes) {
187                NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
188                tas.setProperties(transactionAttributes);
189                this.transactionAttributeSource = tas;
190        }
191
192        /**
193         * Set multiple transaction attribute sources which are used to find transaction
194         * attributes. Will build a CompositeTransactionAttributeSource for the given sources.
195         * @see CompositeTransactionAttributeSource
196         * @see MethodMapTransactionAttributeSource
197         * @see NameMatchTransactionAttributeSource
198         * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
199         */
200        public void setTransactionAttributeSources(TransactionAttributeSource... transactionAttributeSources) {
201                this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
202        }
203
204        /**
205         * Set the transaction attribute source which is used to find transaction
206         * attributes. If specifying a String property value, a PropertyEditor
207         * will create a MethodMapTransactionAttributeSource from the value.
208         * @see TransactionAttributeSourceEditor
209         * @see MethodMapTransactionAttributeSource
210         * @see NameMatchTransactionAttributeSource
211         * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
212         */
213        public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
214                this.transactionAttributeSource = transactionAttributeSource;
215        }
216
217        /**
218         * Return the transaction attribute source.
219         */
220        public TransactionAttributeSource getTransactionAttributeSource() {
221                return this.transactionAttributeSource;
222        }
223
224        /**
225         * Set the BeanFactory to use for retrieving PlatformTransactionManager beans.
226         */
227        @Override
228        public void setBeanFactory(BeanFactory beanFactory) {
229                this.beanFactory = beanFactory;
230        }
231
232        /**
233         * Return the BeanFactory to use for retrieving PlatformTransactionManager beans.
234         */
235        protected final BeanFactory getBeanFactory() {
236                return this.beanFactory;
237        }
238
239        /**
240         * Check that required properties were set.
241         */
242        @Override
243        public void afterPropertiesSet() {
244                if (getTransactionManager() == null && this.beanFactory == null) {
245                        throw new IllegalStateException(
246                                        "Set the 'transactionManager' property or make sure to run within a BeanFactory " +
247                                        "containing a PlatformTransactionManager bean!");
248                }
249                if (getTransactionAttributeSource() == null) {
250                        throw new IllegalStateException(
251                                        "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
252                                        "If there are no transactional methods, then don't use a transaction aspect.");
253                }
254        }
255
256
257        /**
258         * General delegate for around-advice-based subclasses, delegating to several other template
259         * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
260         * as well as regular {@link PlatformTransactionManager} implementations.
261         * @param method the Method being invoked
262         * @param targetClass the target class that we're invoking the method on
263         * @param invocation the callback to use for proceeding with the target invocation
264         * @return the return value of the method, if any
265         * @throws Throwable propagated from the target invocation
266         */
267        protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
268                        throws Throwable {
269
270                // If the transaction attribute is null, the method is non-transactional.
271                final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
272                final PlatformTransactionManager tm = determineTransactionManager(txAttr);
273                final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
274
275                if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
276                        // Standard transaction demarcation with getTransaction and commit/rollback calls.
277                        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
278
279                        Object retVal;
280                        try {
281                                // This is an around advice: Invoke the next interceptor in the chain.
282                                // This will normally result in a target object being invoked.
283                                retVal = invocation.proceedWithInvocation();
284                        }
285                        catch (Throwable ex) {
286                                // target invocation exception
287                                completeTransactionAfterThrowing(txInfo, ex);
288                                throw ex;
289                        }
290                        finally {
291                                cleanupTransactionInfo(txInfo);
292                        }
293                        commitTransactionAfterReturning(txInfo);
294                        return retVal;
295                }
296
297                else {
298                        Object result;
299                        final ThrowableHolder throwableHolder = new ThrowableHolder();
300
301                        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
302                        try {
303                                result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
304                                                new TransactionCallback<Object>() {
305                                                        @Override
306                                                        public Object doInTransaction(TransactionStatus status) {
307                                                                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
308                                                                try {
309                                                                        return invocation.proceedWithInvocation();
310                                                                }
311                                                                catch (Throwable ex) {
312                                                                        if (txAttr.rollbackOn(ex)) {
313                                                                                // A RuntimeException: will lead to a rollback.
314                                                                                if (ex instanceof RuntimeException) {
315                                                                                        throw (RuntimeException) ex;
316                                                                                }
317                                                                                else {
318                                                                                        throw new ThrowableHolderException(ex);
319                                                                                }
320                                                                        }
321                                                                        else {
322                                                                                // A normal return value: will lead to a commit.
323                                                                                throwableHolder.throwable = ex;
324                                                                                return null;
325                                                                        }
326                                                                }
327                                                                finally {
328                                                                        cleanupTransactionInfo(txInfo);
329                                                                }
330                                                        }
331                                                });
332                        }
333                        catch (ThrowableHolderException ex) {
334                                throw ex.getCause();
335                        }
336                        catch (TransactionSystemException ex2) {
337                                if (throwableHolder.throwable != null) {
338                                        logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
339                                        ex2.initApplicationException(throwableHolder.throwable);
340                                }
341                                throw ex2;
342                        }
343                        catch (Throwable ex2) {
344                                if (throwableHolder.throwable != null) {
345                                        logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
346                                }
347                                throw ex2;
348                        }
349
350                        // Check result state: It might indicate a Throwable to rethrow.
351                        if (throwableHolder.throwable != null) {
352                                throw throwableHolder.throwable;
353                        }
354                        return result;
355                }
356        }
357
358        /**
359         * Clear the transaction manager cache.
360         */
361        protected void clearTransactionManagerCache() {
362                this.transactionManagerCache.clear();
363                this.beanFactory = null;
364        }
365
366        /**
367         * Determine the specific transaction manager to use for the given transaction.
368         */
369        protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
370                // Do not attempt to lookup tx manager if no tx attributes are set
371                if (txAttr == null || this.beanFactory == null) {
372                        return getTransactionManager();
373                }
374
375                String qualifier = txAttr.getQualifier();
376                if (StringUtils.hasText(qualifier)) {
377                        return determineQualifiedTransactionManager(qualifier);
378                }
379                else if (StringUtils.hasText(this.transactionManagerBeanName)) {
380                        return determineQualifiedTransactionManager(this.transactionManagerBeanName);
381                }
382                else {
383                        PlatformTransactionManager defaultTransactionManager = getTransactionManager();
384                        if (defaultTransactionManager == null) {
385                                defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
386                                if (defaultTransactionManager == null) {
387                                        defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
388                                        this.transactionManagerCache.putIfAbsent(
389                                                        DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
390                                }
391                        }
392                        return defaultTransactionManager;
393                }
394        }
395
396        private PlatformTransactionManager determineQualifiedTransactionManager(String qualifier) {
397                PlatformTransactionManager txManager = this.transactionManagerCache.get(qualifier);
398                if (txManager == null) {
399                        txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
400                                        this.beanFactory, PlatformTransactionManager.class, qualifier);
401                        this.transactionManagerCache.putIfAbsent(qualifier, txManager);
402                }
403                return txManager;
404        }
405
406        private String methodIdentification(Method method, Class<?> targetClass, TransactionAttribute txAttr) {
407                String methodIdentification = methodIdentification(method, targetClass);
408                if (methodIdentification == null) {
409                        if (txAttr instanceof DefaultTransactionAttribute) {
410                                methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor();
411                        }
412                        if (methodIdentification == null) {
413                                methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
414                        }
415                }
416                return methodIdentification;
417        }
418
419        /**
420         * Convenience method to return a String representation of this Method
421         * for use in logging. Can be overridden in subclasses to provide a
422         * different identifier for the given method.
423         * <p>The default implementation returns {@code null}, indicating the
424         * use of {@link DefaultTransactionAttribute#getDescriptor()} instead,
425         * ending up as {@link ClassUtils#getQualifiedMethodName(Method, Class)}.
426         * @param method the method we're interested in
427         * @param targetClass the class that the method is being invoked on
428         * @return a String representation identifying this method
429         * @see org.springframework.util.ClassUtils#getQualifiedMethodName
430         */
431        protected String methodIdentification(Method method, Class<?> targetClass) {
432                return null;
433        }
434
435        /**
436         * Create a transaction if necessary based on the given TransactionAttribute.
437         * <p>Allows callers to perform custom TransactionAttribute lookups through
438         * the TransactionAttributeSource.
439         * @param txAttr the TransactionAttribute (may be {@code null})
440         * @param joinpointIdentification the fully qualified method name
441         * (used for monitoring and logging purposes)
442         * @return a TransactionInfo object, whether or not a transaction was created.
443         * The {@code hasTransaction()} method on TransactionInfo can be used to
444         * tell if there was a transaction created.
445         * @see #getTransactionAttributeSource()
446         */
447        @SuppressWarnings("serial")
448        protected TransactionInfo createTransactionIfNecessary(
449                        PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
450
451                // If no name specified, apply method identification as transaction name.
452                if (txAttr != null && txAttr.getName() == null) {
453                        txAttr = new DelegatingTransactionAttribute(txAttr) {
454                                @Override
455                                public String getName() {
456                                        return joinpointIdentification;
457                                }
458                        };
459                }
460
461                TransactionStatus status = null;
462                if (txAttr != null) {
463                        if (tm != null) {
464                                status = tm.getTransaction(txAttr);
465                        }
466                        else {
467                                if (logger.isDebugEnabled()) {
468                                        logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
469                                                        "] because no transaction manager has been configured");
470                                }
471                        }
472                }
473                return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
474        }
475
476        /**
477         * Prepare a TransactionInfo for the given attribute and status object.
478         * @param txAttr the TransactionAttribute (may be {@code null})
479         * @param joinpointIdentification the fully qualified method name
480         * (used for monitoring and logging purposes)
481         * @param status the TransactionStatus for the current transaction
482         * @return the prepared TransactionInfo object
483         */
484        protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
485                        TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
486
487                TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
488                if (txAttr != null) {
489                        // We need a transaction for this method...
490                        if (logger.isTraceEnabled()) {
491                                logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
492                        }
493                        // The transaction manager will flag an error if an incompatible tx already exists.
494                        txInfo.newTransactionStatus(status);
495                }
496                else {
497                        // The TransactionInfo.hasTransaction() method will return false. We created it only
498                        // to preserve the integrity of the ThreadLocal stack maintained in this class.
499                        if (logger.isTraceEnabled()) {
500                                logger.trace("No need to create transaction for [" + joinpointIdentification +
501                                                "]: This method is not transactional.");
502                        }
503                }
504
505                // We always bind the TransactionInfo to the thread, even if we didn't create
506                // a new transaction here. This guarantees that the TransactionInfo stack
507                // will be managed correctly even if no transaction was created by this aspect.
508                txInfo.bindToThread();
509                return txInfo;
510        }
511
512        /**
513         * Execute after successful completion of call, but not after an exception was handled.
514         * Do nothing if we didn't create a transaction.
515         * @param txInfo information about the current transaction
516         */
517        protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
518                if (txInfo != null && txInfo.hasTransaction()) {
519                        if (logger.isTraceEnabled()) {
520                                logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
521                        }
522                        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
523                }
524        }
525
526        /**
527         * Handle a throwable, completing the transaction.
528         * We may commit or roll back, depending on the configuration.
529         * @param txInfo information about the current transaction
530         * @param ex throwable encountered
531         */
532        protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
533                if (txInfo != null && txInfo.hasTransaction()) {
534                        if (logger.isTraceEnabled()) {
535                                logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
536                                                "] after exception: " + ex);
537                        }
538                        if (txInfo.transactionAttribute.rollbackOn(ex)) {
539                                try {
540                                        txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
541                                }
542                                catch (TransactionSystemException ex2) {
543                                        logger.error("Application exception overridden by rollback exception", ex);
544                                        ex2.initApplicationException(ex);
545                                        throw ex2;
546                                }
547                                catch (RuntimeException ex2) {
548                                        logger.error("Application exception overridden by rollback exception", ex);
549                                        throw ex2;
550                                }
551                                catch (Error err) {
552                                        logger.error("Application exception overridden by rollback error", ex);
553                                        throw err;
554                                }
555                        }
556                        else {
557                                // We don't roll back on this exception.
558                                // Will still roll back if TransactionStatus.isRollbackOnly() is true.
559                                try {
560                                        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
561                                }
562                                catch (TransactionSystemException ex2) {
563                                        logger.error("Application exception overridden by commit exception", ex);
564                                        ex2.initApplicationException(ex);
565                                        throw ex2;
566                                }
567                                catch (RuntimeException ex2) {
568                                        logger.error("Application exception overridden by commit exception", ex);
569                                        throw ex2;
570                                }
571                                catch (Error err) {
572                                        logger.error("Application exception overridden by commit error", ex);
573                                        throw err;
574                                }
575                        }
576                }
577        }
578
579        /**
580         * Reset the TransactionInfo ThreadLocal.
581         * <p>Call this in all cases: exception or normal return!
582         * @param txInfo information about the current transaction (may be {@code null})
583         */
584        protected void cleanupTransactionInfo(TransactionInfo txInfo) {
585                if (txInfo != null) {
586                        txInfo.restoreThreadLocalStatus();
587                }
588        }
589
590
591        /**
592         * Opaque object used to hold transaction information. Subclasses
593         * must pass it back to methods on this class, but not see its internals.
594         */
595        protected final class TransactionInfo {
596
597                private final PlatformTransactionManager transactionManager;
598
599                private final TransactionAttribute transactionAttribute;
600
601                private final String joinpointIdentification;
602
603                private TransactionStatus transactionStatus;
604
605                private TransactionInfo oldTransactionInfo;
606
607                public TransactionInfo(PlatformTransactionManager transactionManager,
608                                TransactionAttribute transactionAttribute, String joinpointIdentification) {
609
610                        this.transactionManager = transactionManager;
611                        this.transactionAttribute = transactionAttribute;
612                        this.joinpointIdentification = joinpointIdentification;
613                }
614
615                public PlatformTransactionManager getTransactionManager() {
616                        return this.transactionManager;
617                }
618
619                public TransactionAttribute getTransactionAttribute() {
620                        return this.transactionAttribute;
621                }
622
623                /**
624                 * Return a String representation of this joinpoint (usually a Method call)
625                 * for use in logging.
626                 */
627                public String getJoinpointIdentification() {
628                        return this.joinpointIdentification;
629                }
630
631                public void newTransactionStatus(TransactionStatus status) {
632                        this.transactionStatus = status;
633                }
634
635                public TransactionStatus getTransactionStatus() {
636                        return this.transactionStatus;
637                }
638
639                /**
640                 * Return whether a transaction was created by this aspect,
641                 * or whether we just have a placeholder to keep ThreadLocal stack integrity.
642                 */
643                public boolean hasTransaction() {
644                        return (this.transactionStatus != null);
645                }
646
647                private void bindToThread() {
648                        // Expose current TransactionStatus, preserving any existing TransactionStatus
649                        // for restoration after this transaction is complete.
650                        this.oldTransactionInfo = transactionInfoHolder.get();
651                        transactionInfoHolder.set(this);
652                }
653
654                private void restoreThreadLocalStatus() {
655                        // Use stack to restore old transaction TransactionInfo.
656                        // Will be null if none was set.
657                        transactionInfoHolder.set(this.oldTransactionInfo);
658                }
659
660                @Override
661                public String toString() {
662                        return this.transactionAttribute.toString();
663                }
664        }
665
666
667        /**
668         * Simple callback interface for proceeding with the target invocation.
669         * Concrete interceptors/aspects adapt this to their invocation mechanism.
670         */
671        protected interface InvocationCallback {
672
673                Object proceedWithInvocation() throws Throwable;
674        }
675
676
677        /**
678         * Internal holder class for a Throwable in a callback transaction model.
679         */
680        private static class ThrowableHolder {
681
682                public Throwable throwable;
683        }
684
685
686        /**
687         * Internal holder class for a Throwable, used as a RuntimeException to be
688         * thrown from a TransactionCallback (and subsequently unwrapped again).
689         */
690        @SuppressWarnings("serial")
691        private static class ThrowableHolderException extends RuntimeException {
692
693                public ThrowableHolderException(Throwable throwable) {
694                        super(throwable);
695                }
696
697                @Override
698                public String toString() {
699                        return getCause().toString();
700                }
701        }
702
703}