001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.transaction.interceptor;
018:
019: import java.lang.reflect.Method;
020: import java.util.Properties;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: import org.springframework.beans.factory.InitializingBean;
026: import org.springframework.transaction.NoTransactionException;
027: import org.springframework.transaction.PlatformTransactionManager;
028: import org.springframework.transaction.TransactionStatus;
029: import org.springframework.util.Assert;
030: import org.springframework.util.ClassUtils;
031:
032: /**
033: * Base class for transactional aspects, such as the AOP Alliance
034: * {@link TransactionInterceptor} or an AspectJ aspect.
035: *
036: * <p>This enables the underlying Spring transaction infrastructure to be used
037: * easily to implement an aspect for any aspect system.
038: *
039: * <p>Subclasses are responsible for calling methods in this class in the
040: * correct order.
041: *
042: * <p>If no transaction name has been specified in the
043: * <code>TransactionAttribute</code>, the exposed name will be the
044: * <code>fully-qualified class name + "." + method name</code>
045: * (by default).
046: *
047: * <p>Uses the <b>Strategy</b> design pattern. A
048: * <code>PlatformTransactionManager</code> implementation will perform the
049: * actual transaction management, and a <code>TransactionAttributeSource</code>
050: * is used for determining transaction definitions.
051: *
052: * <p>A transaction aspect is serializable if it's
053: * <code>PlatformTransactionManager</code> and
054: * <code>TransactionAttributeSource</code> are serializable.
055: *
056: * @author Rod Johnson
057: * @author Juergen Hoeller
058: * @since 1.1
059: * @see #setTransactionManager
060: * @see #setTransactionAttributes
061: * @see #setTransactionAttributeSource
062: */
063: public abstract class TransactionAspectSupport implements
064: InitializingBean {
065:
066: // NOTE: This class must not implement Serializable because it serves as base
067: // class for AspectJ aspects (which are not allowed to implement Serializable)!
068:
069: /**
070: * Holder to support the <code>currentTransactionStatus()</code> method,
071: * and to support communication between different cooperating advices
072: * (e.g. before and after advice) if the aspect involves more than a
073: * single method (as will be the case for around advice).
074: */
075: private static final ThreadLocal transactionInfoHolder = new ThreadLocal();
076:
077: /**
078: * Subclasses can use this to return the current TransactionInfo.
079: * Only subclasses that cannot handle all operations in one method,
080: * such as an AspectJ aspect involving distinct before and after advice,
081: * need to use this mechanism to get at the current TransactionInfo.
082: * An around advice such as an AOP Alliance MethodInterceptor can hold a
083: * reference to the TransactionInfo throughout the aspect method.
084: * <p>A TransactionInfo will be returned even if no transaction was created.
085: * The <code>TransactionInfo.hasTransaction()</code> method can be used to query this.
086: * <p>To find out about specific transaction characteristics, consider using
087: * TransactionSynchronizationManager's <code>isSynchronizationActive()</code>
088: * and/or <code>isActualTransactionActive()</code> methods.
089: * @return TransactionInfo bound to this thread, or <code>null</code> if none
090: * @see TransactionInfo#hasTransaction()
091: * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive()
092: * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
093: */
094: protected static TransactionInfo currentTransactionInfo()
095: throws NoTransactionException {
096: return (TransactionInfo) transactionInfoHolder.get();
097: }
098:
099: /**
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()
107: throws NoTransactionException {
108: TransactionInfo info = currentTransactionInfo();
109: if (info == null) {
110: throw new NoTransactionException(
111: "No transaction aspect-managed TransactionStatus in scope");
112: }
113: return currentTransactionInfo().transactionStatus;
114: }
115:
116: protected final Log logger = LogFactory.getLog(getClass());
117:
118: /** Delegate used to create, commit and rollback transactions */
119: private PlatformTransactionManager transactionManager;
120:
121: /** Helper used to find transaction attributes */
122: private TransactionAttributeSource transactionAttributeSource;
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(
129: PlatformTransactionManager transactionManager) {
130: this .transactionManager = transactionManager;
131: }
132:
133: /**
134: * Return the transaction manager.
135: */
136: public PlatformTransactionManager getTransactionManager() {
137: return this .transactionManager;
138: }
139:
140: /**
141: * Set properties with method names as keys and transaction attribute
142: * descriptors (parsed via TransactionAttributeEditor) as values:
143: * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly".
144: * <p>Note: Method names are always applied to the target class,
145: * no matter if defined in an interface or the class itself.
146: * <p>Internally, a NameMatchTransactionAttributeSource will be
147: * created from the given properties.
148: * @see #setTransactionAttributeSource
149: * @see TransactionAttributeEditor
150: * @see NameMatchTransactionAttributeSource
151: */
152: public void setTransactionAttributes(
153: Properties transactionAttributes) {
154: NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
155: tas.setProperties(transactionAttributes);
156: this .transactionAttributeSource = tas;
157: }
158:
159: /**
160: * Set multiple transaction attribute sources which are used to find transaction
161: * attributes. Will build a CompositeTransactionAttributeSource for the given sources.
162: * @see CompositeTransactionAttributeSource
163: * @see MethodMapTransactionAttributeSource
164: * @see NameMatchTransactionAttributeSource
165: * @see AttributesTransactionAttributeSource
166: * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
167: */
168: public void setTransactionAttributeSources(
169: TransactionAttributeSource[] transactionAttributeSources) {
170: this .transactionAttributeSource = new CompositeTransactionAttributeSource(
171: transactionAttributeSources);
172: }
173:
174: /**
175: * Set the transaction attribute source which is used to find transaction
176: * attributes. If specifying a String property value, a PropertyEditor
177: * will create a MethodMapTransactionAttributeSource from the value.
178: * @see TransactionAttributeSourceEditor
179: * @see MethodMapTransactionAttributeSource
180: * @see NameMatchTransactionAttributeSource
181: * @see AttributesTransactionAttributeSource
182: * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
183: */
184: public void setTransactionAttributeSource(
185: TransactionAttributeSource transactionAttributeSource) {
186: this .transactionAttributeSource = transactionAttributeSource;
187: }
188:
189: /**
190: * Return the transaction attribute source.
191: */
192: public TransactionAttributeSource getTransactionAttributeSource() {
193: return this .transactionAttributeSource;
194: }
195:
196: /**
197: * Check that required properties were set.
198: */
199: public void afterPropertiesSet() {
200: if (getTransactionManager() == null) {
201: throw new IllegalArgumentException(
202: "Property 'transactionManager' is required");
203: }
204: if (getTransactionAttributeSource() == null) {
205: throw new IllegalArgumentException(
206: "Either 'transactionAttributeSource' or 'transactionAttributes' is required: "
207: + "If there are no transactional methods, don't use a TransactionInterceptor "
208: + "or TransactionProxyFactoryBean.");
209: }
210: }
211:
212: /**
213: * Create a transaction if necessary, based on the given method and class.
214: * <p>Performs a default TransactionAttribute lookup for the given method.
215: * @param method method about to execute
216: * @param targetClass class the method is on
217: * @return a TransactionInfo object, whether or not a transaction was created.
218: * The hasTransaction() method on TransactionInfo can be used to tell if there
219: * was a transaction created.
220: * @see #getTransactionAttributeSource()
221: */
222: protected TransactionInfo createTransactionIfNecessary(
223: Method method, Class targetClass) {
224: // If the transaction attribute is null, the method is non-transactional.
225: TransactionAttribute txAttr = getTransactionAttributeSource()
226: .getTransactionAttribute(method, targetClass);
227: return createTransactionIfNecessary(txAttr,
228: methodIdentification(method));
229: }
230:
231: /**
232: * Convenience method to return a String representation of this Method
233: * for use in logging. Can be overridden in subclasses to provide a
234: * different identifier for the given method.
235: * @param method the method we're interested in
236: * @return log message identifying this method
237: * @see org.springframework.util.ClassUtils#getQualifiedMethodName
238: */
239: protected String methodIdentification(Method method) {
240: return ClassUtils.getQualifiedMethodName(method);
241: }
242:
243: /**
244: * Create a transaction if necessary based on the given TransactionAttribute.
245: * <p>Allows callers to perform custom TransactionAttribute lookups through
246: * the TransactionAttributeSource.
247: * @param txAttr the TransactionAttribute (may be <code>null</code>)
248: * @param joinpointIdentification the fully qualified method name
249: * (used for monitoring and logging purposes)
250: * @return a TransactionInfo object, whether or not a transaction was created.
251: * The <code>hasTransaction()</code> method on TransactionInfo can be used to
252: * tell if there was a transaction created.
253: * @see #getTransactionAttributeSource()
254: */
255: protected TransactionInfo createTransactionIfNecessary(
256: TransactionAttribute txAttr,
257: final String joinpointIdentification) {
258:
259: // If no name specified, apply method identification as transaction name.
260: if (txAttr != null && txAttr.getName() == null) {
261: txAttr = new DelegatingTransactionAttribute(txAttr) {
262: public String getName() {
263: return joinpointIdentification;
264: }
265: };
266: }
267:
268: TransactionStatus status = null;
269: if (txAttr != null) {
270: PlatformTransactionManager tm = getTransactionManager();
271: Assert
272: .state(tm != null,
273: "Property 'transactionManager' must be set on transaction aspect");
274: status = tm.getTransaction(txAttr);
275: }
276: return prepareTransactionInfo(txAttr, joinpointIdentification,
277: status);
278: }
279:
280: /**
281: * Prepare a TransactionInfo for the given attribute and status object.
282: * @param txAttr the TransactionAttribute (may be <code>null</code>)
283: * @param joinpointIdentification the fully qualified method name
284: * (used for monitoring and logging purposes)
285: * @param status the TransactionStatus for the current transaction
286: * @return the prepared TransactionInfo object
287: */
288: protected TransactionInfo prepareTransactionInfo(
289: TransactionAttribute txAttr,
290: String joinpointIdentification, TransactionStatus status) {
291:
292: TransactionInfo txInfo = new TransactionInfo(txAttr,
293: joinpointIdentification);
294: if (txAttr != null) {
295: // We need a transaction for this method
296: if (logger.isDebugEnabled()) {
297: logger.debug("Getting transaction for ["
298: + txInfo.getJoinpointIdentification() + "]");
299: }
300:
301: // The transaction manager will flag an error if an incompatible tx already exists
302: txInfo.newTransactionStatus(status);
303: } else {
304: // The TransactionInfo.hasTransaction() method will return
305: // false. We created it only to preserve the integrity of
306: // the ThreadLocal stack maintained in this class.
307: if (logger.isDebugEnabled())
308: logger.debug("Don't need to create transaction for ["
309: + joinpointIdentification
310: + "]: This method isn't transactional.");
311: }
312:
313: // We always bind the TransactionInfo to the thread, even if we didn't create
314: // a new transaction here. This guarantees that the TransactionInfo stack
315: // will be managed correctly even if no transaction was created by this aspect.
316: txInfo.bindToThread();
317: return txInfo;
318: }
319:
320: /**
321: * Execute after successful completion of call, but not after an exception was handled.
322: * Do nothing if we didn't create a transaction.
323: * @param txInfo information about the current transaction
324: */
325: protected void commitTransactionAfterReturning(
326: TransactionInfo txInfo) {
327: if (txInfo != null && txInfo.hasTransaction()) {
328: if (logger.isDebugEnabled()) {
329: logger.debug("Completing transaction for ["
330: + txInfo.getJoinpointIdentification() + "]");
331: }
332: getTransactionManager().commit(
333: txInfo.getTransactionStatus());
334: }
335: }
336:
337: /**
338: * Handle a throwable, completing the transaction.
339: * We may commit or roll back, depending on the configuration.
340: * @param txInfo information about the current transaction
341: * @param ex throwable encountered
342: */
343: protected void completeTransactionAfterThrowing(
344: TransactionInfo txInfo, Throwable ex) {
345: if (txInfo != null && txInfo.hasTransaction()) {
346: if (logger.isDebugEnabled()) {
347: logger.debug("Completing transaction for ["
348: + txInfo.getJoinpointIdentification()
349: + "] after exception: " + ex);
350: }
351: if (txInfo.transactionAttribute.rollbackOn(ex)) {
352: try {
353: getTransactionManager().rollback(
354: txInfo.getTransactionStatus());
355: } catch (RuntimeException ex2) {
356: logger
357: .error(
358: "Application exception overridden by rollback exception",
359: ex);
360: throw ex2;
361: } catch (Error err) {
362: logger
363: .error(
364: "Application exception overridden by rollback error",
365: ex);
366: throw err;
367: }
368: } else {
369: // We don't roll back on this exception.
370: // Will still roll back if TransactionStatus.isRollbackOnly() is true.
371: try {
372: getTransactionManager().commit(
373: txInfo.getTransactionStatus());
374: } catch (RuntimeException ex2) {
375: logger
376: .error(
377: "Application exception overridden by commit exception",
378: ex);
379: throw ex2;
380: } catch (Error err) {
381: logger
382: .error(
383: "Application exception overridden by commit error",
384: ex);
385: throw err;
386: }
387: }
388: }
389: }
390:
391: /**
392: * Reset the TransactionInfo ThreadLocal.
393: * <p>Call this in all cases: exception or normal return!
394: * @param txInfo information about the current transaction (may be <code>null</code>)
395: */
396: protected void cleanupTransactionInfo(TransactionInfo txInfo) {
397: if (txInfo != null) {
398: txInfo.restoreThreadLocalStatus();
399: }
400: }
401:
402: /**
403: * Opaque object used to hold Transaction information. Subclasses
404: * must pass it back to methods on this class, but not see its internals.
405: */
406: protected class TransactionInfo {
407:
408: private final TransactionAttribute transactionAttribute;
409:
410: private final String joinpointIdentification;
411:
412: private TransactionStatus transactionStatus;
413:
414: private TransactionInfo oldTransactionInfo;
415:
416: public TransactionInfo(
417: TransactionAttribute transactionAttribute,
418: String joinpointIdentification) {
419: this .transactionAttribute = transactionAttribute;
420: this .joinpointIdentification = joinpointIdentification;
421: }
422:
423: public TransactionAttribute getTransactionAttribute() {
424: return this .transactionAttribute;
425: }
426:
427: /**
428: * Return a String representation of this joinpoint (usually a Method call)
429: * for use in logging.
430: */
431: public String getJoinpointIdentification() {
432: return this .joinpointIdentification;
433: }
434:
435: public void newTransactionStatus(TransactionStatus status) {
436: this .transactionStatus = status;
437: }
438:
439: public TransactionStatus getTransactionStatus() {
440: return this .transactionStatus;
441: }
442:
443: /**
444: * Return whether a transaction was created by this aspect,
445: * or whether we just have a placeholder to keep ThreadLocal stack integrity.
446: */
447: public boolean hasTransaction() {
448: return (this .transactionStatus != null);
449: }
450:
451: private void bindToThread() {
452: // Expose current TransactionStatus, preserving any existing TransactionStatus
453: // for restoration after this transaction is complete.
454: this .oldTransactionInfo = (TransactionInfo) transactionInfoHolder
455: .get();
456: transactionInfoHolder.set(this );
457: }
458:
459: private void restoreThreadLocalStatus() {
460: // Use stack to restore old transaction TransactionInfo.
461: // Will be null if none was set.
462: transactionInfoHolder.set(this.oldTransactionInfo);
463: }
464: }
465:
466: }
|