Open Source Repository

Home /spring/spring-transaction-3.0.5 | Repository Home



org/springframework/transaction/interceptor/TransactionAspectSupport.java
/*
 * Copyright 2002-2010 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 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.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
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.util.ClassUtils;
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</code>,
 * the exposed name will be the <code>fully-qualified class name + "." + method name</code>
 * (by default).
 *
 <p>Uses the <b>Strategy</b> design pattern. A <code>PlatformTransactionManager</code>
 * implementation will perform the actual transaction management, and a
 <code>TransactionAttributeSource</code> is used for determining transaction definitions.
 *
 <p>A transaction aspect is serializable if its <code>PlatformTransactionManager</code>
 * and <code>TransactionAttributeSource</code> 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()</code> 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");


  /**
   * 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()</code> method can be used to query this.
   <p>To find out about specific transaction characteristics, consider using
   * TransactionSynchronizationManager's <code>isSynchronizationActive()</code>
   * and/or <code>isActualTransactionActive()</code> methods.
   @return TransactionInfo bound to this thread, or <code>null</code> 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) {
      throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
    }
    return currentTransactionInfo().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.
   */
  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.
   */
  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.");
    }
  }


  /**
   * 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)) {
      return TransactionAspectUtils.getTransactionManager(this.beanFactory, qualifier);
    }
    else if (this.transactionManagerBeanName != null) {
      return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class);
    }
    else if (this.beanFactory instanceof ListableBeanFactory) {
      return BeanFactoryUtils.beanOfTypeIncludingAncestors(((ListableBeanFactorythis.beanFactory), PlatformTransactionManager.class);
    }
    else {
      throw new IllegalStateException(
          "Cannot retrieve PlatformTransactionManager beans from non-listable BeanFactory: " this.beanFactory);
    }
  }

  /**
   * Create a transaction if necessary, based on the given method and class.
   <p>Performs a default TransactionAttribute lookup for the given method.
   @param method method about to execute
   @param targetClass class the method is on
   @return a TransactionInfo object, whether or not a transaction was created.
   * The hasTransaction() method on TransactionInfo can be used to tell if there
   * was a transaction created.
   @see #getTransactionAttributeSource()
   */
  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));
  }

  /**
   * 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 class the method is on
   @return log message 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;
    }
    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
    return ClassUtils.getQualifiedMethodName(specificMethod);
  }

  /**
   * 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 log message 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 TransactionAttribute.
   <p>Allows callers to perform custom TransactionAttribute lookups through
   * the TransactionAttributeSource.
   @param txAttr the TransactionAttribute (may be <code>null</code>)
   @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()</code> method on TransactionInfo can be used to
   * tell if there was a transaction created.
   @see #getTransactionAttributeSource()
   */
  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</code>)
   @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</code>)
   */
  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();
    }
  }

}