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.transaction.TransactionSystemException;
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, then don't use a transaction aspect.");
208: }
209: }
210:
211: /**
212: * Create a transaction if necessary, based on the given method and class.
213: * <p>Performs a default TransactionAttribute lookup for the given method.
214: * @param method method about to execute
215: * @param targetClass class the method is on
216: * @return a TransactionInfo object, whether or not a transaction was created.
217: * The hasTransaction() method on TransactionInfo can be used to tell if there
218: * was a transaction created.
219: * @see #getTransactionAttributeSource()
220: */
221: protected TransactionInfo createTransactionIfNecessary(
222: Method method, Class targetClass) {
223: // If the transaction attribute is null, the method is non-transactional.
224: TransactionAttribute txAttr = getTransactionAttributeSource()
225: .getTransactionAttribute(method, targetClass);
226: return createTransactionIfNecessary(txAttr,
227: methodIdentification(method));
228: }
229:
230: /**
231: * Convenience method to return a String representation of this Method
232: * for use in logging. Can be overridden in subclasses to provide a
233: * different identifier for the given method.
234: * @param method the method we're interested in
235: * @return log message identifying this method
236: * @see org.springframework.util.ClassUtils#getQualifiedMethodName
237: */
238: protected String methodIdentification(Method method) {
239: return ClassUtils.getQualifiedMethodName(method);
240: }
241:
242: /**
243: * Create a transaction if necessary based on the given TransactionAttribute.
244: * <p>Allows callers to perform custom TransactionAttribute lookups through
245: * the TransactionAttributeSource.
246: * @param txAttr the TransactionAttribute (may be <code>null</code>)
247: * @param joinpointIdentification the fully qualified method name
248: * (used for monitoring and logging purposes)
249: * @return a TransactionInfo object, whether or not a transaction was created.
250: * The <code>hasTransaction()</code> method on TransactionInfo can be used to
251: * tell if there was a transaction created.
252: * @see #getTransactionAttributeSource()
253: */
254: protected TransactionInfo createTransactionIfNecessary(
255: TransactionAttribute txAttr,
256: final String joinpointIdentification) {
257:
258: // If no name specified, apply method identification as transaction name.
259: if (txAttr != null && txAttr.getName() == null) {
260: txAttr = new DelegatingTransactionAttribute(txAttr) {
261: public String getName() {
262: return joinpointIdentification;
263: }
264: };
265: }
266:
267: TransactionStatus status = null;
268: if (txAttr != null) {
269: PlatformTransactionManager tm = getTransactionManager();
270: if (tm != null) {
271: status = tm.getTransaction(txAttr);
272: } else {
273: if (logger.isDebugEnabled()) {
274: logger
275: .debug("Skipping transactional joinpoint ["
276: + joinpointIdentification
277: + "] because no transaction manager has been configured");
278: }
279: }
280: }
281: return prepareTransactionInfo(txAttr, joinpointIdentification,
282: status);
283: }
284:
285: /**
286: * Prepare a TransactionInfo for the given attribute and status object.
287: * @param txAttr the TransactionAttribute (may be <code>null</code>)
288: * @param joinpointIdentification the fully qualified method name
289: * (used for monitoring and logging purposes)
290: * @param status the TransactionStatus for the current transaction
291: * @return the prepared TransactionInfo object
292: */
293: protected TransactionInfo prepareTransactionInfo(
294: TransactionAttribute txAttr,
295: String joinpointIdentification, TransactionStatus status) {
296:
297: TransactionInfo txInfo = new TransactionInfo(txAttr,
298: joinpointIdentification);
299: if (txAttr != null) {
300: // We need a transaction for this method
301: if (logger.isTraceEnabled()) {
302: logger.trace("Getting transaction for ["
303: + txInfo.getJoinpointIdentification() + "]");
304: }
305: // The transaction manager will flag an error if an incompatible tx already exists
306: txInfo.newTransactionStatus(status);
307: } else {
308: // The TransactionInfo.hasTransaction() method will return
309: // false. We created it only to preserve the integrity of
310: // the ThreadLocal stack maintained in this class.
311: if (logger.isTraceEnabled())
312: logger.trace("Don't need to create transaction for ["
313: + joinpointIdentification
314: + "]: This method isn't transactional.");
315: }
316:
317: // We always bind the TransactionInfo to the thread, even if we didn't create
318: // a new transaction here. This guarantees that the TransactionInfo stack
319: // will be managed correctly even if no transaction was created by this aspect.
320: txInfo.bindToThread();
321: return txInfo;
322: }
323:
324: /**
325: * Execute after successful completion of call, but not after an exception was handled.
326: * Do nothing if we didn't create a transaction.
327: * @param txInfo information about the current transaction
328: */
329: protected void commitTransactionAfterReturning(
330: TransactionInfo txInfo) {
331: if (txInfo != null && txInfo.hasTransaction()) {
332: if (logger.isTraceEnabled()) {
333: logger.trace("Completing transaction for ["
334: + txInfo.getJoinpointIdentification() + "]");
335: }
336: getTransactionManager().commit(
337: txInfo.getTransactionStatus());
338: }
339: }
340:
341: /**
342: * Handle a throwable, completing the transaction.
343: * We may commit or roll back, depending on the configuration.
344: * @param txInfo information about the current transaction
345: * @param ex throwable encountered
346: */
347: protected void completeTransactionAfterThrowing(
348: TransactionInfo txInfo, Throwable ex) {
349: if (txInfo != null && txInfo.hasTransaction()) {
350: if (logger.isTraceEnabled()) {
351: logger.trace("Completing transaction for ["
352: + txInfo.getJoinpointIdentification()
353: + "] after exception: " + ex);
354: }
355: if (txInfo.transactionAttribute.rollbackOn(ex)) {
356: try {
357: getTransactionManager().rollback(
358: txInfo.getTransactionStatus());
359: } catch (TransactionSystemException ex2) {
360: logger
361: .error(
362: "Application exception overridden by rollback exception",
363: ex);
364: ex2.initApplicationException(ex);
365: throw ex2;
366: } catch (RuntimeException ex2) {
367: logger
368: .error(
369: "Application exception overridden by rollback exception",
370: ex);
371: throw ex2;
372: } catch (Error err) {
373: logger
374: .error(
375: "Application exception overridden by rollback error",
376: ex);
377: throw err;
378: }
379: } else {
380: // We don't roll back on this exception.
381: // Will still roll back if TransactionStatus.isRollbackOnly() is true.
382: try {
383: getTransactionManager().commit(
384: txInfo.getTransactionStatus());
385: } catch (TransactionSystemException ex2) {
386: logger
387: .error(
388: "Application exception overridden by commit exception",
389: ex);
390: ex2.initApplicationException(ex);
391: throw ex2;
392: } catch (RuntimeException ex2) {
393: logger
394: .error(
395: "Application exception overridden by commit exception",
396: ex);
397: throw ex2;
398: } catch (Error err) {
399: logger
400: .error(
401: "Application exception overridden by commit error",
402: ex);
403: throw err;
404: }
405: }
406: }
407: }
408:
409: /**
410: * Reset the TransactionInfo ThreadLocal.
411: * <p>Call this in all cases: exception or normal return!
412: * @param txInfo information about the current transaction (may be <code>null</code>)
413: */
414: protected void cleanupTransactionInfo(TransactionInfo txInfo) {
415: if (txInfo != null) {
416: txInfo.restoreThreadLocalStatus();
417: }
418: }
419:
420: /**
421: * Opaque object used to hold Transaction information. Subclasses
422: * must pass it back to methods on this class, but not see its internals.
423: */
424: protected class TransactionInfo {
425:
426: private final TransactionAttribute transactionAttribute;
427:
428: private final String joinpointIdentification;
429:
430: private TransactionStatus transactionStatus;
431:
432: private TransactionInfo oldTransactionInfo;
433:
434: public TransactionInfo(
435: TransactionAttribute transactionAttribute,
436: String joinpointIdentification) {
437: this .transactionAttribute = transactionAttribute;
438: this .joinpointIdentification = joinpointIdentification;
439: }
440:
441: public TransactionAttribute getTransactionAttribute() {
442: return this .transactionAttribute;
443: }
444:
445: /**
446: * Return a String representation of this joinpoint (usually a Method call)
447: * for use in logging.
448: */
449: public String getJoinpointIdentification() {
450: return this .joinpointIdentification;
451: }
452:
453: public void newTransactionStatus(TransactionStatus status) {
454: this .transactionStatus = status;
455: }
456:
457: public TransactionStatus getTransactionStatus() {
458: return this .transactionStatus;
459: }
460:
461: /**
462: * Return whether a transaction was created by this aspect,
463: * or whether we just have a placeholder to keep ThreadLocal stack integrity.
464: */
465: public boolean hasTransaction() {
466: return (this .transactionStatus != null);
467: }
468:
469: private void bindToThread() {
470: // Expose current TransactionStatus, preserving any existing TransactionStatus
471: // for restoration after this transaction is complete.
472: this .oldTransactionInfo = (TransactionInfo) transactionInfoHolder
473: .get();
474: transactionInfoHolder.set(this );
475: }
476:
477: private void restoreThreadLocalStatus() {
478: // Use stack to restore old transaction TransactionInfo.
479: // Will be null if none was set.
480: transactionInfoHolder.set(this.oldTransactionInfo);
481: }
482: }
483:
484: }
|