Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » transaction » interceptor » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.transaction.interceptor;
   18   
   19   import java.lang.reflect.Method;
   20   import java.util.Properties;
   21   
   22   import org.apache.commons.logging.Log;
   23   import org.apache.commons.logging.LogFactory;
   24   
   25   import org.springframework.beans.factory.InitializingBean;
   26   import org.springframework.core.NamedThreadLocal;
   27   import org.springframework.transaction.NoTransactionException;
   28   import org.springframework.transaction.PlatformTransactionManager;
   29   import org.springframework.transaction.TransactionStatus;
   30   import org.springframework.transaction.TransactionSystemException;
   31   import org.springframework.util.ClassUtils;
   32   
   33   /**
   34    * Base class for transactional aspects, such as the AOP Alliance
   35    * {@link TransactionInterceptor} or an AspectJ aspect.
   36    *
   37    * <p>This enables the underlying Spring transaction infrastructure to be used
   38    * easily to implement an aspect for any aspect system.
   39    *
   40    * <p>Subclasses are responsible for calling methods in this class in the
   41    * correct order.
   42    *
   43    * <p>If no transaction name has been specified in the
   44    * <code>TransactionAttribute</code>, the exposed name will be the
   45    * <code>fully-qualified class name + "." + method name</code>
   46    * (by default).
   47    *
   48    * <p>Uses the <b>Strategy</b> design pattern. A
   49    * <code>PlatformTransactionManager</code> implementation will perform the
   50    * actual transaction management, and a <code>TransactionAttributeSource</code>
   51    * is used for determining transaction definitions.
   52    *
   53    * <p>A transaction aspect is serializable if it's
   54    * <code>PlatformTransactionManager</code> and
   55    * <code>TransactionAttributeSource</code> are serializable.
   56    *
   57    * @author Rod Johnson
   58    * @author Juergen Hoeller
   59    * @since 1.1
   60    * @see #setTransactionManager
   61    * @see #setTransactionAttributes
   62    * @see #setTransactionAttributeSource
   63    */
   64   public abstract class TransactionAspectSupport implements InitializingBean {
   65   
   66   	// NOTE: This class must not implement Serializable because it serves as base
   67   	// class for AspectJ aspects (which are not allowed to implement Serializable)!
   68   
   69   	/**
   70   	 * Holder to support the <code>currentTransactionStatus()</code> method,
   71   	 * and to support communication between different cooperating advices
   72   	 * (e.g. before and after advice) if the aspect involves more than a
   73   	 * single method (as will be the case for around advice).
   74   	 */
   75   	private static final ThreadLocal transactionInfoHolder =
   76   			new NamedThreadLocal("Current aspect-driven transaction");
   77   
   78   
   79   	/**
   80   	 * Subclasses can use this to return the current TransactionInfo.
   81   	 * Only subclasses that cannot handle all operations in one method,
   82   	 * such as an AspectJ aspect involving distinct before and after advice,
   83   	 * need to use this mechanism to get at the current TransactionInfo.
   84   	 * An around advice such as an AOP Alliance MethodInterceptor can hold a
   85   	 * reference to the TransactionInfo throughout the aspect method.
   86   	 * <p>A TransactionInfo will be returned even if no transaction was created.
   87   	 * The <code>TransactionInfo.hasTransaction()</code> method can be used to query this.
   88   	 * <p>To find out about specific transaction characteristics, consider using
   89   	 * TransactionSynchronizationManager's <code>isSynchronizationActive()</code>
   90   	 * and/or <code>isActualTransactionActive()</code> methods.
   91   	 * @return TransactionInfo bound to this thread, or <code>null</code> if none
   92   	 * @see TransactionInfo#hasTransaction()
   93   	 * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive()
   94   	 * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
   95   	 */
   96   	protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {
   97   		return (TransactionInfo) transactionInfoHolder.get();
   98   	}
   99   	/**
  100   	 * Return the transaction status of the current method invocation.
  101   	 * Mainly intended for code that wants to set the current transaction
  102   	 * rollback-only but not throw an application exception.
  103   	 * @throws NoTransactionException if the transaction info cannot be found,
  104   	 * because the method was invoked outside an AOP invocation context
  105   	 */
  106   	public static TransactionStatus currentTransactionStatus() throws NoTransactionException {
  107   		TransactionInfo info = currentTransactionInfo();
  108   		if (info == null) {
  109   			throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
  110   		}
  111   		return currentTransactionInfo().transactionStatus;
  112   	}
  113   
  114   
  115   	protected final Log logger = LogFactory.getLog(getClass());
  116   
  117   	/** Delegate used to create, commit and rollback transactions */
  118   	private PlatformTransactionManager transactionManager;
  119   
  120   	/** Helper used to find transaction attributes */
  121   	private TransactionAttributeSource transactionAttributeSource;
  122   
  123   
  124   	/**
  125   	 * Set the transaction manager. This will perform actual
  126   	 * transaction management: This class is just a way of invoking it.
  127   	 */
  128   	public void setTransactionManager(PlatformTransactionManager transactionManager) {
  129   		this.transactionManager = transactionManager;
  130   	}
  131   
  132   	/**
  133   	 * Return the transaction manager.
  134   	 */
  135   	public PlatformTransactionManager getTransactionManager() {
  136   		return this.transactionManager;
  137   	}
  138   
  139   	/**
  140   	 * Set properties with method names as keys and transaction attribute
  141   	 * descriptors (parsed via TransactionAttributeEditor) as values:
  142   	 * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly".
  143   	 * <p>Note: Method names are always applied to the target class,
  144   	 * no matter if defined in an interface or the class itself.
  145   	 * <p>Internally, a NameMatchTransactionAttributeSource will be
  146   	 * created from the given properties.
  147   	 * @see #setTransactionAttributeSource
  148   	 * @see TransactionAttributeEditor
  149   	 * @see NameMatchTransactionAttributeSource
  150   	 */
  151   	public void setTransactionAttributes(Properties transactionAttributes) {
  152   		NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
  153   		tas.setProperties(transactionAttributes);
  154   		this.transactionAttributeSource = tas;
  155   	}
  156   
  157   	/**
  158   	 * Set multiple transaction attribute sources which are used to find transaction
  159   	 * attributes. Will build a CompositeTransactionAttributeSource for the given sources.
  160   	 * @see CompositeTransactionAttributeSource
  161   	 * @see MethodMapTransactionAttributeSource
  162   	 * @see NameMatchTransactionAttributeSource
  163   	 * @see AttributesTransactionAttributeSource
  164   	 * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
  165   	 */
  166   	public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) {
  167   		this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
  168   	}
  169   
  170   	/**
  171   	 * Set the transaction attribute source which is used to find transaction
  172   	 * attributes. If specifying a String property value, a PropertyEditor
  173   	 * will create a MethodMapTransactionAttributeSource from the value.
  174   	 * @see TransactionAttributeSourceEditor
  175   	 * @see MethodMapTransactionAttributeSource
  176   	 * @see NameMatchTransactionAttributeSource
  177   	 * @see AttributesTransactionAttributeSource
  178   	 * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
  179   	 */
  180   	public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
  181   		this.transactionAttributeSource = transactionAttributeSource;
  182   	}
  183   
  184   	/**
  185   	 * Return the transaction attribute source.
  186   	 */
  187   	public TransactionAttributeSource getTransactionAttributeSource() {
  188   		return this.transactionAttributeSource;
  189   	}
  190   
  191   
  192   	/**
  193   	 * Check that required properties were set.
  194   	 */
  195   	public void afterPropertiesSet() {
  196   		if (getTransactionManager() == null) {
  197   			throw new IllegalArgumentException("Property 'transactionManager' is required");
  198   		}
  199   		if (getTransactionAttributeSource() == null) {
  200   			throw new IllegalArgumentException(
  201   					"Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
  202   					"If there are no transactional methods, then don't use a transaction aspect.");
  203   		}
  204   	}
  205   
  206   
  207   	/**
  208   	 * Create a transaction if necessary, based on the given method and class.
  209   	 * <p>Performs a default TransactionAttribute lookup for the given method.
  210   	 * @param method method about to execute
  211   	 * @param targetClass class the method is on
  212   	 * @return a TransactionInfo object, whether or not a transaction was created.
  213   	 * The hasTransaction() method on TransactionInfo can be used to tell if there
  214   	 * was a transaction created.
  215   	 * @see #getTransactionAttributeSource()
  216   	 */
  217   	protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
  218   		// If the transaction attribute is null, the method is non-transactional.
  219   		TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
  220   		return createTransactionIfNecessary(txAttr, methodIdentification(method));
  221   	}
  222   
  223   	/**
  224   	 * Convenience method to return a String representation of this Method
  225   	 * for use in logging. Can be overridden in subclasses to provide a
  226   	 * different identifier for the given method.
  227   	 * @param method the method we're interested in
  228   	 * @return log message identifying this method
  229   	 * @see org.springframework.util.ClassUtils#getQualifiedMethodName
  230   	 */
  231   	protected String methodIdentification(Method method) {
  232   		return ClassUtils.getQualifiedMethodName(method);
  233   	}
  234   
  235   	/**
  236   	 * Create a transaction if necessary based on the given TransactionAttribute.
  237   	 * <p>Allows callers to perform custom TransactionAttribute lookups through
  238   	 * the TransactionAttributeSource.
  239   	 * @param txAttr the TransactionAttribute (may be <code>null</code>)
  240   	 * @param joinpointIdentification the fully qualified method name
  241   	 * (used for monitoring and logging purposes)
  242   	 * @return a TransactionInfo object, whether or not a transaction was created.
  243   	 * The <code>hasTransaction()</code> method on TransactionInfo can be used to
  244   	 * tell if there was a transaction created.
  245   	 * @see #getTransactionAttributeSource()
  246   	 */
  247   	protected TransactionInfo createTransactionIfNecessary(
  248   			TransactionAttribute txAttr, final String joinpointIdentification) {
  249   
  250   		// If no name specified, apply method identification as transaction name.
  251   		if (txAttr != null && txAttr.getName() == null) {
  252   			txAttr = new DelegatingTransactionAttribute(txAttr) {
  253   				public String getName() {
  254   					return joinpointIdentification;
  255   				}
  256   			};
  257   		}
  258   
  259   		TransactionStatus status = null;
  260   		if (txAttr != null) {
  261   			PlatformTransactionManager tm = getTransactionManager();
  262   			if (tm != null) {
  263   				status = tm.getTransaction(txAttr);
  264   			}
  265   			else {
  266   				if (logger.isDebugEnabled()) {
  267   					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
  268   							"] because no transaction manager has been configured");
  269   				}
  270   			}
  271   		}
  272   		return prepareTransactionInfo(txAttr, joinpointIdentification, status);
  273   	}
  274   
  275   	/**
  276   	 * Prepare a TransactionInfo for the given attribute and status object.
  277   	 * @param txAttr the TransactionAttribute (may be <code>null</code>)
  278   	 * @param joinpointIdentification the fully qualified method name
  279   	 * (used for monitoring and logging purposes)
  280   	 * @param status the TransactionStatus for the current transaction
  281   	 * @return the prepared TransactionInfo object
  282   	 */
  283   	protected TransactionInfo prepareTransactionInfo(
  284   			TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
  285   
  286   		TransactionInfo txInfo = new TransactionInfo(txAttr, joinpointIdentification);
  287   		if (txAttr != null) {
  288   			// We need a transaction for this method
  289   			if (logger.isTraceEnabled()) {
  290   				logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
  291   			}
  292   			// The transaction manager will flag an error if an incompatible tx already exists
  293   			txInfo.newTransactionStatus(status);
  294   		}
  295   		else {
  296   			// The TransactionInfo.hasTransaction() method will return
  297   			// false. We created it only to preserve the integrity of
  298   			// the ThreadLocal stack maintained in this class.
  299   			if (logger.isTraceEnabled())
  300   				logger.trace("Don't need to create transaction for [" + joinpointIdentification +
  301   						"]: This method isn't transactional.");
  302   		}
  303   
  304   		// We always bind the TransactionInfo to the thread, even if we didn't create
  305   		// a new transaction here. This guarantees that the TransactionInfo stack
  306   		// will be managed correctly even if no transaction was created by this aspect.
  307   		txInfo.bindToThread();
  308   		return txInfo;
  309   	}
  310   
  311   	/**
  312   	 * Execute after successful completion of call, but not after an exception was handled.
  313   	 * Do nothing if we didn't create a transaction.
  314   	 * @param txInfo information about the current transaction
  315   	 */
  316   	protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
  317   		if (txInfo != null && txInfo.hasTransaction()) {
  318   			if (logger.isTraceEnabled()) {
  319   				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
  320   			}
  321   			getTransactionManager().commit(txInfo.getTransactionStatus());
  322   		}
  323   	}
  324   
  325   	/**
  326   	 * Handle a throwable, completing the transaction.
  327   	 * We may commit or roll back, depending on the configuration.
  328   	 * @param txInfo information about the current transaction
  329   	 * @param ex throwable encountered
  330   	 */
  331   	protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
  332   		if (txInfo != null && txInfo.hasTransaction()) {
  333   			if (logger.isTraceEnabled()) {
  334   				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
  335   						"] after exception: " + ex);
  336   			}
  337   			if (txInfo.transactionAttribute.rollbackOn(ex)) {
  338   				try {
  339   					getTransactionManager().rollback(txInfo.getTransactionStatus());
  340   				}
  341   				catch (TransactionSystemException ex2) {
  342   					logger.error("Application exception overridden by rollback exception", ex);
  343   					ex2.initApplicationException(ex);
  344   					throw ex2;
  345   				}
  346   				catch (RuntimeException ex2) {
  347   					logger.error("Application exception overridden by rollback exception", ex);
  348   					throw ex2;
  349   				}
  350   				catch (Error err) {
  351   					logger.error("Application exception overridden by rollback error", ex);
  352   					throw err;
  353   				}
  354   			}
  355   			else {
  356   				// We don't roll back on this exception.
  357   				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
  358   				try {
  359   					getTransactionManager().commit(txInfo.getTransactionStatus());
  360   				}
  361   				catch (TransactionSystemException ex2) {
  362   					logger.error("Application exception overridden by commit exception", ex);
  363   					ex2.initApplicationException(ex);
  364   					throw ex2;
  365   				}
  366   				catch (RuntimeException ex2) {
  367   					logger.error("Application exception overridden by commit exception", ex);
  368   					throw ex2;
  369   				}
  370   				catch (Error err) {
  371   					logger.error("Application exception overridden by commit error", ex);
  372   					throw err;
  373   				}
  374   			}
  375   		}
  376   	}
  377   
  378   	/**
  379   	 * Reset the TransactionInfo ThreadLocal.
  380   	 * <p>Call this in all cases: exception or normal return!
  381   	 * @param txInfo information about the current transaction (may be <code>null</code>)
  382   	 */
  383   	protected void cleanupTransactionInfo(TransactionInfo txInfo) {
  384   		if (txInfo != null) {
  385   			txInfo.restoreThreadLocalStatus();
  386   		}
  387   	}
  388   
  389   
  390   	/**
  391   	 * Opaque object used to hold Transaction information. Subclasses
  392   	 * must pass it back to methods on this class, but not see its internals.
  393   	 */
  394   	protected class TransactionInfo {
  395   
  396   		private final TransactionAttribute transactionAttribute;
  397   
  398   		private final String joinpointIdentification;
  399   
  400   		private TransactionStatus transactionStatus;
  401   
  402   		private TransactionInfo oldTransactionInfo;
  403   
  404   		public TransactionInfo(TransactionAttribute transactionAttribute, String joinpointIdentification) {
  405   			this.transactionAttribute = transactionAttribute;
  406   			this.joinpointIdentification = joinpointIdentification;
  407   		}
  408   
  409   		public TransactionAttribute getTransactionAttribute() {
  410   			return this.transactionAttribute;
  411   		}
  412   
  413   		/**
  414   		 * Return a String representation of this joinpoint (usually a Method call)
  415   		 * for use in logging.
  416   		 */
  417   		public String getJoinpointIdentification() {
  418   			return this.joinpointIdentification;
  419   		}
  420   
  421   		public void newTransactionStatus(TransactionStatus status) {
  422   			this.transactionStatus = status;
  423   		}
  424   
  425   		public TransactionStatus getTransactionStatus() {
  426   			return this.transactionStatus;
  427   		}
  428   
  429   		/**
  430   		 * Return whether a transaction was created by this aspect,
  431   		 * or whether we just have a placeholder to keep ThreadLocal stack integrity.
  432   		 */
  433   		public boolean hasTransaction() {
  434   			return (this.transactionStatus != null);
  435   		}
  436   
  437   		private void bindToThread() {
  438   			// Expose current TransactionStatus, preserving any existing TransactionStatus
  439   			// for restoration after this transaction is complete.
  440   			this.oldTransactionInfo = (TransactionInfo) transactionInfoHolder.get();
  441   			transactionInfoHolder.set(this);
  442   		}
  443   
  444   		private void restoreThreadLocalStatus() {
  445   			// Use stack to restore old transaction TransactionInfo.
  446   			// Will be null if none was set.
  447   			transactionInfoHolder.set(this.oldTransactionInfo);
  448   		}
  449   
  450   		public String toString() {
  451   			return this.transactionAttribute.toString();
  452   		}
  453   	}
  454   
  455   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » transaction » interceptor » [javadoc | source]