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}