Package org.springframework.transaction.interceptor

Source Code of org.springframework.transaction.interceptor.TransactionAspectSupport$ThrowableHolderException

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

package org.springframework.transaction.interceptor;

import java.lang.reflect.Method;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.NamedThreadLocal;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.util.StringUtils;

/**
* Base class for transactional aspects, such as the {@link TransactionInterceptor}
* or an AspectJ aspect.
*
* <p>This enables the underlying Spring transaction infrastructure to be used easily
* to implement an aspect for any aspect system.
*
* <p>Subclasses are responsible for calling methods in this class in the correct order.
*
* <p>If no transaction name has been specified in the {@code TransactionAttribute},
* the exposed name will be the {@code fully-qualified class name + "." + method name}
* (by default).
*
* <p>Uses the <b>Strategy</b> design pattern. A {@code PlatformTransactionManager}
* implementation will perform the actual transaction management, and a
* {@code TransactionAttributeSource} is used for determining transaction definitions.
*
* <p>A transaction aspect is serializable if its {@code PlatformTransactionManager}
* and {@code TransactionAttributeSource} are serializable.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 1.1
* @see #setTransactionManager
* @see #setTransactionAttributes
* @see #setTransactionAttributeSource
*/
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

  // NOTE: This class must not implement Serializable because it serves as base
  // class for AspectJ aspects (which are not allowed to implement Serializable)!

  /**
   * Holder to support the {@code currentTransactionStatus()} method,
   * and to support communication between different cooperating advices
   * (e.g. before and after advice) if the aspect involves more than a
   * single method (as will be the case for around advice).
   */
  private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
      new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");


  private final ConcurrentHashMap<String, PlatformTransactionManager> transactionManagerCache =
      new ConcurrentHashMap<String, PlatformTransactionManager>();

  /**
   * Subclasses can use this to return the current TransactionInfo.
   * Only subclasses that cannot handle all operations in one method,
   * such as an AspectJ aspect involving distinct before and after advice,
   * need to use this mechanism to get at the current TransactionInfo.
   * An around advice such as an AOP Alliance MethodInterceptor can hold a
   * reference to the TransactionInfo throughout the aspect method.
   * <p>A TransactionInfo will be returned even if no transaction was created.
   * The {@code TransactionInfo.hasTransaction()} method can be used to query this.
   * <p>To find out about specific transaction characteristics, consider using
   * TransactionSynchronizationManager's {@code isSynchronizationActive()}
   * and/or {@code isActualTransactionActive()} methods.
   * @return TransactionInfo bound to this thread, or {@code null} if none
   * @see TransactionInfo#hasTransaction()
   * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive()
   * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
   */
  protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {
    return transactionInfoHolder.get();
  }

  /**
   * Return the transaction status of the current method invocation.
   * Mainly intended for code that wants to set the current transaction
   * rollback-only but not throw an application exception.
   * @throws NoTransactionException if the transaction info cannot be found,
   * because the method was invoked outside an AOP invocation context
   */
  public static TransactionStatus currentTransactionStatus() throws NoTransactionException {
    TransactionInfo info = currentTransactionInfo();
    if (info == null || info.transactionStatus == null) {
      throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
    }
    return info.transactionStatus;
  }


  protected final Log logger = LogFactory.getLog(getClass());

  private String transactionManagerBeanName;

  private PlatformTransactionManager transactionManager;

  private TransactionAttributeSource transactionAttributeSource;

  private BeanFactory beanFactory;


  /**
   * Specify the name of the default transaction manager bean.
   */
  public void setTransactionManagerBeanName(String transactionManagerBeanName) {
    this.transactionManagerBeanName = transactionManagerBeanName;
  }

  /**
   * Return the name of the default transaction manager bean.
   */
  protected final String getTransactionManagerBeanName() {
    return this.transactionManagerBeanName;
  }

  /**
   * Specify the target transaction manager.
   */
  public void setTransactionManager(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }

  /**
   * Return the transaction manager, if specified.
   */
  public PlatformTransactionManager getTransactionManager() {
    return this.transactionManager;
  }

  /**
   * Set properties with method names as keys and transaction attribute
   * descriptors (parsed via TransactionAttributeEditor) as values:
   * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly".
   * <p>Note: Method names are always applied to the target class,
   * no matter if defined in an interface or the class itself.
   * <p>Internally, a NameMatchTransactionAttributeSource will be
   * created from the given properties.
   * @see #setTransactionAttributeSource
   * @see TransactionAttributeEditor
   * @see NameMatchTransactionAttributeSource
   */
  public void setTransactionAttributes(Properties transactionAttributes) {
    NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
    tas.setProperties(transactionAttributes);
    this.transactionAttributeSource = tas;
  }

  /**
   * Set multiple transaction attribute sources which are used to find transaction
   * attributes. Will build a CompositeTransactionAttributeSource for the given sources.
   * @see CompositeTransactionAttributeSource
   * @see MethodMapTransactionAttributeSource
   * @see NameMatchTransactionAttributeSource
   * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
   */
  public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) {
    this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
  }

  /**
   * Set the transaction attribute source which is used to find transaction
   * attributes. If specifying a String property value, a PropertyEditor
   * will create a MethodMapTransactionAttributeSource from the value.
   * @see TransactionAttributeSourceEditor
   * @see MethodMapTransactionAttributeSource
   * @see NameMatchTransactionAttributeSource
   * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
   */
  public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
    this.transactionAttributeSource = transactionAttributeSource;
  }

  /**
   * Return the transaction attribute source.
   */
  public TransactionAttributeSource getTransactionAttributeSource() {
    return this.transactionAttributeSource;
  }

  /**
   * Set the BeanFactory to use for retrieving PlatformTransactionManager beans.
   */
  @Override
  public void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

  /**
   * Return the BeanFactory to use for retrieving PlatformTransactionManager beans.
   */
  protected final BeanFactory getBeanFactory() {
    return this.beanFactory;
  }

  /**
   * Check that required properties were set.
   */
  @Override
  public void afterPropertiesSet() {
    if (this.transactionManager == null && this.beanFactory == null) {
      throw new IllegalStateException(
          "Setting the property 'transactionManager' or running in a ListableBeanFactory is required");
    }
    if (this.transactionAttributeSource == null) {
      throw new IllegalStateException(
          "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
          "If there are no transactional methods, then don't use a transaction aspect.");
    }
  }


  /**
   * General delegate for around-advice-based subclasses, delegating to several other template
   * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
   * as well as regular {@link PlatformTransactionManager} implementations.
   * @param method the Method being invoked
   * @param targetClass the target class that we're invoking the method on
   * @param invocation the callback to use for proceeding with the target invocation
   * @return the return value of the method, if any
   * @throws Throwable propagated from the target invocation
   */
  protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
      throws Throwable {

    // If the transaction attribute is null, the method is non-transactional.
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
      Object retVal = null;
      try {
        // This is an around advice: Invoke the next interceptor in the chain.
        // This will normally result in a target object being invoked.
        retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
        // target invocation exception
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
      }
      finally {
        cleanupTransactionInfo(txInfo);
      }
      commitTransactionAfterReturning(txInfo);
      return retVal;
    }

    else {
      // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
      try {
        Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
            new TransactionCallback<Object>() {
              @Override
              public Object doInTransaction(TransactionStatus status) {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                  return invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                  if (txAttr.rollbackOn(ex)) {
                    // A RuntimeException: will lead to a rollback.
                    if (ex instanceof RuntimeException) {
                      throw (RuntimeException) ex;
                    }
                    else {
                      throw new ThrowableHolderException(ex);
                    }
                  }
                  else {
                    // A normal return value: will lead to a commit.
                    return new ThrowableHolder(ex);
                  }
                }
                finally {
                  cleanupTransactionInfo(txInfo);
                }
              }
            });

        // Check result: It might indicate a Throwable to rethrow.
        if (result instanceof ThrowableHolder) {
          throw ((ThrowableHolder) result).getThrowable();
        }
        else {
          return result;
        }
      }
      catch (ThrowableHolderException ex) {
        throw ex.getCause();
      }
    }
  }

  /**
   * Determine the specific transaction manager to use for the given transaction.
   */
  protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
    if (this.transactionManager != null || this.beanFactory == null || txAttr == null) {
      return this.transactionManager;
    }
    String qualifier = txAttr.getQualifier();
    if (StringUtils.hasLength(qualifier)) {
      PlatformTransactionManager txManager = this.transactionManagerCache.get(qualifier);
      if (txManager == null) {
        txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
            this.beanFactory, PlatformTransactionManager.class, qualifier);
        this.transactionManagerCache.putIfAbsent(qualifier, txManager);
      }
      return txManager;
    }
    else if (this.transactionManagerBeanName != null) {
      PlatformTransactionManager txManager  = this.transactionManagerCache.get(this.transactionManagerBeanName);
      if (txManager == null) {
        txManager = this.beanFactory.getBean(
            this.transactionManagerBeanName, PlatformTransactionManager.class);
        this.transactionManagerCache.putIfAbsent(this.transactionManagerBeanName, txManager);
      }
      return txManager;
    }
    else {
      // Lookup the default transaction manager and store it for next call
      this.transactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
      return this.transactionManager;
    }
  }

  /**
   * Convenience method to return a String representation of this Method
   * for use in logging. Can be overridden in subclasses to provide a
   * different identifier for the given method.
   * @param method the method we're interested in
   * @param targetClass the class that the method is being invoked on
   * @return a String representation identifying this method
   * @see org.springframework.util.ClassUtils#getQualifiedMethodName
   */
  protected String methodIdentification(Method method, Class<?> targetClass) {
    String simpleMethodId = methodIdentification(method);
    if (simpleMethodId != null) {
      return simpleMethodId;
    }
    return (targetClass != null ? targetClass : method.getDeclaringClass()).getName() + "." + method.getName();
  }

  /**
   * Convenience method to return a String representation of this Method
   * for use in logging. Can be overridden in subclasses to provide a
   * different identifier for the given method.
   * @param method the method we're interested in
   * @return a String representation identifying this method
   * @deprecated in favor of {@link #methodIdentification(Method, Class)}
   */
  @Deprecated
  protected String methodIdentification(Method method) {
    return null;
  }

  /**
   * Create a transaction if necessary, based on the given method and class.
   * <p>Performs a default TransactionAttribute lookup for the given method.
   * @param method the method about to execute
   * @param targetClass the class that the method is being invoked on
   * @return a TransactionInfo object, whether or not a transaction was created.
   * The {@code hasTransaction()} method on TransactionInfo can be used to
   * tell if there was a transaction created.
   * @see #getTransactionAttributeSource()
   * @deprecated in favor of
   * {@link #createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String)}
   */
  @Deprecated
  protected TransactionInfo createTransactionIfNecessary(Method method, Class<?> targetClass) {
    // If the transaction attribute is null, the method is non-transactional.
    TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    PlatformTransactionManager tm = determineTransactionManager(txAttr);
    return createTransactionIfNecessary(tm, txAttr, methodIdentification(method, targetClass));
  }

  /**
   * Create a transaction if necessary based on the given TransactionAttribute.
   * <p>Allows callers to perform custom TransactionAttribute lookups through
   * the TransactionAttributeSource.
   * @param txAttr the TransactionAttribute (may be {@code null})
   * @param joinpointIdentification the fully qualified method name
   * (used for monitoring and logging purposes)
   * @return a TransactionInfo object, whether or not a transaction was created.
   * The {@code hasTransaction()} method on TransactionInfo can be used to
   * tell if there was a transaction created.
   * @see #getTransactionAttributeSource()
   */
  @SuppressWarnings("serial")
  protected TransactionInfo createTransactionIfNecessary(
      PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {

    // If no name specified, apply method identification as transaction name.
    if (txAttr != null && txAttr.getName() == null) {
      txAttr = new DelegatingTransactionAttribute(txAttr) {
        @Override
        public String getName() {
          return joinpointIdentification;
        }
      };
    }

    TransactionStatus status = null;
    if (txAttr != null) {
      if (tm != null) {
        status = tm.getTransaction(txAttr);
      }
      else {
        if (logger.isDebugEnabled()) {
          logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
              "] because no transaction manager has been configured");
        }
      }
    }
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
  }

  /**
   * Prepare a TransactionInfo for the given attribute and status object.
   * @param txAttr the TransactionAttribute (may be {@code null})
   * @param joinpointIdentification the fully qualified method name
   * (used for monitoring and logging purposes)
   * @param status the TransactionStatus for the current transaction
   * @return the prepared TransactionInfo object
   */
  protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
      TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {

    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
      // We need a transaction for this method
      if (logger.isTraceEnabled()) {
        logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
      }
      // The transaction manager will flag an error if an incompatible tx already exists
      txInfo.newTransactionStatus(status);
    }
    else {
      // The TransactionInfo.hasTransaction() method will return
      // false. We created it only to preserve the integrity of
      // the ThreadLocal stack maintained in this class.
      if (logger.isTraceEnabled())
        logger.trace("Don't need to create transaction for [" + joinpointIdentification +
            "]: This method isn't transactional.");
    }

    // We always bind the TransactionInfo to the thread, even if we didn't create
    // a new transaction here. This guarantees that the TransactionInfo stack
    // will be managed correctly even if no transaction was created by this aspect.
    txInfo.bindToThread();
    return txInfo;
  }

  /**
   * Execute after successful completion of call, but not after an exception was handled.
   * Do nothing if we didn't create a transaction.
   * @param txInfo information about the current transaction
   */
  protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
    if (txInfo != null && txInfo.hasTransaction()) {
      if (logger.isTraceEnabled()) {
        logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
      }
      txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
  }

  /**
   * Handle a throwable, completing the transaction.
   * We may commit or roll back, depending on the configuration.
   * @param txInfo information about the current transaction
   * @param ex throwable encountered
   */
  protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.hasTransaction()) {
      if (logger.isTraceEnabled()) {
        logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
            "] after exception: " + ex);
      }
      if (txInfo.transactionAttribute.rollbackOn(ex)) {
        try {
          txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
        }
        catch (TransactionSystemException ex2) {
          logger.error("Application exception overridden by rollback exception", ex);
          ex2.initApplicationException(ex);
          throw ex2;
        }
        catch (RuntimeException ex2) {
          logger.error("Application exception overridden by rollback exception", ex);
          throw ex2;
        }
        catch (Error err) {
          logger.error("Application exception overridden by rollback error", ex);
          throw err;
        }
      }
      else {
        // We don't roll back on this exception.
        // Will still roll back if TransactionStatus.isRollbackOnly() is true.
        try {
          txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
        catch (TransactionSystemException ex2) {
          logger.error("Application exception overridden by commit exception", ex);
          ex2.initApplicationException(ex);
          throw ex2;
        }
        catch (RuntimeException ex2) {
          logger.error("Application exception overridden by commit exception", ex);
          throw ex2;
        }
        catch (Error err) {
          logger.error("Application exception overridden by commit error", ex);
          throw err;
        }
      }
    }
  }

  /**
   * Reset the TransactionInfo ThreadLocal.
   * <p>Call this in all cases: exception or normal return!
   * @param txInfo information about the current transaction (may be {@code null})
   */
  protected void cleanupTransactionInfo(TransactionInfo txInfo) {
    if (txInfo != null) {
      txInfo.restoreThreadLocalStatus();
    }
  }


  /**
   * Opaque object used to hold Transaction information. Subclasses
   * must pass it back to methods on this class, but not see its internals.
   */
  protected final class TransactionInfo {

    private final PlatformTransactionManager transactionManager;

    private final TransactionAttribute transactionAttribute;

    private final String joinpointIdentification;

    private TransactionStatus transactionStatus;

    private TransactionInfo oldTransactionInfo;

    public TransactionInfo(PlatformTransactionManager transactionManager,
        TransactionAttribute transactionAttribute, String joinpointIdentification) {
      this.transactionManager = transactionManager;
      this.transactionAttribute = transactionAttribute;
      this.joinpointIdentification = joinpointIdentification;
    }

    public PlatformTransactionManager getTransactionManager() {
      return this.transactionManager;
    }

    public TransactionAttribute getTransactionAttribute() {
      return this.transactionAttribute;
    }

    /**
     * Return a String representation of this joinpoint (usually a Method call)
     * for use in logging.
     */
    public String getJoinpointIdentification() {
      return this.joinpointIdentification;
    }

    public void newTransactionStatus(TransactionStatus status) {
      this.transactionStatus = status;
    }

    public TransactionStatus getTransactionStatus() {
      return this.transactionStatus;
    }

    /**
     * Return whether a transaction was created by this aspect,
     * or whether we just have a placeholder to keep ThreadLocal stack integrity.
     */
    public boolean hasTransaction() {
      return (this.transactionStatus != null);
    }

    private void bindToThread() {
      // Expose current TransactionStatus, preserving any existing TransactionStatus
      // for restoration after this transaction is complete.
      this.oldTransactionInfo = transactionInfoHolder.get();
      transactionInfoHolder.set(this);
    }

    private void restoreThreadLocalStatus() {
      // Use stack to restore old transaction TransactionInfo.
      // Will be null if none was set.
      transactionInfoHolder.set(this.oldTransactionInfo);
    }

    @Override
    public String toString() {
      return this.transactionAttribute.toString();
    }
  }


  /**
   * Simple callback interface for proceeding with the target invocation.
   * Concrete interceptors/aspects adapt this to their invocation mechanism.
   */
  protected interface InvocationCallback {

    Object proceedWithInvocation() throws Throwable;
  }


  /**
   * Internal holder class for a Throwable, used as a return value
   * from a TransactionCallback (to be subsequently unwrapped again).
   */
  private static class ThrowableHolder {

    private final Throwable throwable;

    public ThrowableHolder(Throwable throwable) {
      this.throwable = throwable;
    }

    public final Throwable getThrowable() {
      return this.throwable;
    }
  }


  /**
   * Internal holder class for a Throwable, used as a RuntimeException to be
   * thrown from a TransactionCallback (and subsequently unwrapped again).
   */
  @SuppressWarnings("serial")
  private static class ThrowableHolderException extends RuntimeException {

    public ThrowableHolderException(Throwable throwable) {
      super(throwable);
    }

    @Override
    public String toString() {
      return getCause().toString();
    }
  }

}
TOP

Related Classes of org.springframework.transaction.interceptor.TransactionAspectSupport$ThrowableHolderException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.