KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > transaction > interceptor > TransactionAspectSupport


1 /*
2  * Copyright 2002-2006 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 JavaDoc;
20 import java.util.Properties JavaDoc;
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.transaction.NoTransactionException;
27 import org.springframework.transaction.PlatformTransactionManager;
28 import org.springframework.transaction.TransactionStatus;
29 import org.springframework.util.ClassUtils;
30
31 /**
32  * Superclass for transactional aspects, such as the AOP Alliance-compatible
33  * <code>TransactionInterceptor</code>, or an AspectJ aspect.
34  *
35  * <p>This enables the underlying Spring transaction infrastructure to be used
36  * easily to implement an aspect for any aspect system.
37  *
38  * <p>Subclasses are responsible for calling methods in this class in the
39  * correct order.
40  *
41  * <p>If no transaction name has been specified in the
42  * <code>TransactionAttribute</code>, the exposed name will be the
43  * <code>fully-qualified class name + "." + method name</code>
44  * (by default).
45  *
46  * <p>Uses the <b>Strategy</b> design pattern. A
47  * <code>PlatformTransactionManager</code> implementation will perform the
48  * actual transaction management, and a <code>TransactionAttributeSource</code>
49  * is used for determining transaction definitions.
50  *
51  * <p>A transaction aspect is serializable if it's
52  * <code>PlatformTransactionManager</code> and
53  * <code>TransactionAttributeSource</code> are serializable.
54  *
55  * @author Rod Johnson
56  * @author Juergen Hoeller
57  * @since 1.1
58  * @see #setTransactionManager
59  * @see #setTransactionAttributes
60  * @see #setTransactionAttributeSource
61  */

62 public abstract class TransactionAspectSupport implements InitializingBean {
63
64     // NOTE: This class must not implement Serializable because it serves as base
65
// class for AspectJ aspects (which are not allowed to implement Serializable)!
66

67     /**
68      * Holder to support the <code>currentTransactionStatus()</code> method,
69      * and to support communication between different cooperating advices
70      * (e.g. before and after advice) if the aspect involves more than a
71      * single method (as will be the case for around advice).
72      */

73     private static final ThreadLocal JavaDoc transactionInfoHolder = new ThreadLocal JavaDoc();
74
75
76     /**
77      * Subclasses can use this to return the current TransactionInfo.
78      * Only subclasses that cannot handle all operations in one method,
79      * such as an AspectJ aspect involving distinct before and after advice,
80      * need to use this mechanism to get at the current TransactionInfo.
81      * An around advice such as an AOP Alliance MethodInterceptor can hold a
82      * reference to the TransactionInfo throughout the aspect method.
83      * <p>A TransactionInfo will be returned even if no transaction was created.
84      * The <code>TransactionInfo.hasTransaction()</code> method can be used to query this.
85      * <p>To find out about specific transaction characteristics, consider using
86      * TransactionSynchronizationManager's <code>isSynchronizationActive()</code>
87      * and/or <code>isActualTransactionActive()</code> methods.
88      * @return TransactionInfo bound to this thread, or <code>null</code> if none
89      * @see TransactionInfo#hasTransaction()
90      * @see org.springframework.transaction.support.TransactionSynchronizationManager#isSynchronizationActive()
91      * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
92      */

93     protected static TransactionInfo currentTransactionInfo() throws NoTransactionException {
94         return (TransactionInfo) transactionInfoHolder.get();
95     }
96     /**
97      * Return the transaction status of the current method invocation.
98      * Mainly intended for code that wants to set the current transaction
99      * rollback-only but not throw an application exception.
100      * @throws NoTransactionException if the transaction info cannot be found,
101      * because the method was invoked outside an AOP invocation context
102      */

103     public static TransactionStatus currentTransactionStatus() throws NoTransactionException {
104         TransactionInfo info = currentTransactionInfo();
105         if (info == null) {
106             throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
107         }
108         return currentTransactionInfo().transactionStatus;
109     }
110
111
112     protected final Log logger = LogFactory.getLog(getClass());
113
114     /** Delegate used to create, commit and rollback transactions */
115     private PlatformTransactionManager transactionManager;
116
117     /** Helper used to find transaction attributes */
118     private TransactionAttributeSource transactionAttributeSource;
119
120
121     /**
122      * Set the transaction manager. This will perform actual
123      * transaction management: This class is just a way of invoking it.
124      */

125     public void setTransactionManager(PlatformTransactionManager transactionManager) {
126         this.transactionManager = transactionManager;
127     }
128
129     /**
130      * Return the transaction manager.
131      */

132     public PlatformTransactionManager getTransactionManager() {
133         return transactionManager;
134     }
135
136     /**
137      * Set properties with method names as keys and transaction attribute
138      * descriptors (parsed via TransactionAttributeEditor) as values:
139      * e.g. key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly".
140      * <p>Note: Method names are always applied to the target class,
141      * no matter if defined in an interface or the class itself.
142      * <p>Internally, a NameMatchTransactionAttributeSource will be
143      * created from the given properties.
144      * @see #setTransactionAttributeSource
145      * @see TransactionAttributeEditor
146      * @see NameMatchTransactionAttributeSource
147      */

148     public void setTransactionAttributes(Properties JavaDoc transactionAttributes) {
149         NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
150         tas.setProperties(transactionAttributes);
151         this.transactionAttributeSource = tas;
152     }
153
154     /**
155      * Set multiple transaction attribute sources which are used to find transaction
156      * attributes. Will build a CompositeTransactionAttributeSource for the given sources.
157      * @see CompositeTransactionAttributeSource
158      * @see MethodMapTransactionAttributeSource
159      * @see NameMatchTransactionAttributeSource
160      * @see AttributesTransactionAttributeSource
161      * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
162      */

163     public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) {
164         this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
165     }
166
167     /**
168      * Set the transaction attribute source which is used to find transaction
169      * attributes. If specifying a String property value, a PropertyEditor
170      * will create a MethodMapTransactionAttributeSource from the value.
171      * @see TransactionAttributeSourceEditor
172      * @see MethodMapTransactionAttributeSource
173      * @see NameMatchTransactionAttributeSource
174      * @see AttributesTransactionAttributeSource
175      * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
176      */

177     public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
178         this.transactionAttributeSource = transactionAttributeSource;
179     }
180
181     /**
182      * Return the transaction attribute source.
183      */

184     public TransactionAttributeSource getTransactionAttributeSource() {
185         return transactionAttributeSource;
186     }
187
188
189     /**
190      * Check that required properties were set.
191      */

192     public void afterPropertiesSet() {
193         if (getTransactionManager() == null) {
194             throw new IllegalArgumentException JavaDoc("transactionManager is required");
195         }
196         if (getTransactionAttributeSource() == null) {
197             throw new IllegalArgumentException JavaDoc(
198                     "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " +
199                     "If there are no transactional methods, don't use a TransactionInterceptor " +
200                     "or TransactionProxyFactoryBean.");
201         }
202     }
203
204
205     /**
206      * Create a transaction if necessary, based on the given method and class.
207      * <p>Performs a default TransactionAttribute lookup for the given method.
208      * @param method method about to execute
209      * @param targetClass class the method is on
210      * @return a TransactionInfo object, whether or not a transaction was created.
211      * The hasTransaction() method on TransactionInfo can be used to tell if there
212      * was a transaction created.
213      * @see #getTransactionAttributeSource()
214      */

215     protected TransactionInfo createTransactionIfNecessary(Method JavaDoc method, Class JavaDoc targetClass) {
216         // If the transaction attribute is null, the method is non-transactional.
217
TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
218         return createTransactionIfNecessary(txAttr, methodIdentification(method));
219     }
220
221     /**
222      * Convenience method to return a String representation of this Method
223      * for use in logging. Can be overridden in subclasses to provide a
224      * different identifier for the given method.
225      * @param method the method we're interested in
226      * @return log message identifying this method
227      * @see org.springframework.util.ClassUtils#getQualifiedMethodName
228      */

229     protected String JavaDoc methodIdentification(Method JavaDoc method) {
230         return ClassUtils.getQualifiedMethodName(method);
231     }
232
233     /**
234      * Create a transaction if necessary based on the given TransactionAttribute.
235      * <p>Allows callers to perform custom TransactionAttribute lookups through
236      * the TransactionAttributeSource.
237      * @param txAttr the TransactionAttribute (may be <code>null</code>)
238      * @param joinpointIdentification the fully qualified method name
239      * (used for monitoring and logging purposes)
240      * @return a TransactionInfo object, whether or not a transaction was created.
241      * The <code>hasTransaction()</code> method on TransactionInfo can be used to
242      * tell if there was a transaction created.
243      * @see #getTransactionAttributeSource()
244      */

245     protected TransactionInfo createTransactionIfNecessary(
246             TransactionAttribute txAttr, final String JavaDoc joinpointIdentification) {
247
248         // If no name specified, apply method identification as transaction name.
249
if (txAttr != null && txAttr.getName() == null) {
250             txAttr = new DelegatingTransactionAttribute(txAttr) {
251                 public String JavaDoc getName() {
252                     return joinpointIdentification;
253                 }
254             };
255         }
256
257         TransactionStatus status = null;
258         if (txAttr != null) {
259             status = getTransactionManager().getTransaction(txAttr);
260         }
261         return prepareTransactionInfo(txAttr, joinpointIdentification, status);
262     }
263
264     /**
265      * Prepare a TransactionInfo for the given attribute and status object.
266      * @param txAttr the TransactionAttribute (may be <code>null</code>)
267      * @param joinpointIdentification the fully qualified method name
268      * (used for monitoring and logging purposes)
269      * @param status the TransactionStatus for the current transaction
270      * @return the prepared TransactionInfo object
271      */

272     protected TransactionInfo prepareTransactionInfo(
273             TransactionAttribute txAttr, String JavaDoc joinpointIdentification, TransactionStatus status) {
274
275         TransactionInfo txInfo = new TransactionInfo(txAttr, joinpointIdentification);
276         if (txAttr != null) {
277             // We need a transaction for this method
278
if (logger.isDebugEnabled()) {
279                 logger.debug("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
280             }
281
282             // The transaction manager will flag an error if an incompatible tx already exists
283
txInfo.newTransactionStatus(status);
284         }
285         else {
286             // The TransactionInfo.hasTransaction() method will return
287
// false. We created it only to preserve the integrity of
288
// the ThreadLocal stack maintained in this class.
289
if (logger.isDebugEnabled())
290                 logger.debug("Don't need to create transaction for [" + joinpointIdentification +
291                         "]: This method isn't transactional.");
292         }
293
294         // We always bind the TransactionInfo to the thread, even if we didn't create
295
// a new transaction here. This guarantees that the TransactionInfo stack
296
// will be managed correctly even if no transaction was created by this aspect.
297
txInfo.bindToThread();
298         return txInfo;
299     }
300
301     /**
302      * Execute after successful completion of call, but not after an exception was handled.
303      * Do nothing if we didn't create a transaction.
304      * @param txInfo information about the current transaction
305      */

306     protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
307         if (txInfo != null && txInfo.hasTransaction()) {
308             if (logger.isDebugEnabled()) {
309                 logger.debug("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
310             }
311             this.transactionManager.commit(txInfo.getTransactionStatus());
312         }
313     }
314
315     /**
316      * Handle a throwable, completing the transaction.
317      * We may commit or roll back, depending on the configuration.
318      * @param txInfo information about the current transaction
319      * @param ex throwable encountered
320      */

321     protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable JavaDoc ex) {
322         if (txInfo != null && txInfo.hasTransaction()) {
323             if (logger.isDebugEnabled()) {
324                 logger.debug("Completing transaction for [" + txInfo.getJoinpointIdentification() +
325                         "] after exception: " + ex);
326             }
327             if (txInfo.transactionAttribute.rollbackOn(ex)) {
328                 try {
329                     this.transactionManager.rollback(txInfo.getTransactionStatus());
330                 }
331                 catch (RuntimeException JavaDoc ex2) {
332                     logger.error("Application exception overridden by rollback exception", ex);
333                     throw ex2;
334                 }
335                 catch (Error JavaDoc err) {
336                     logger.error("Application exception overridden by rollback error", ex);
337                     throw err;
338                 }
339             }
340             else {
341                 // We don't roll back on this exception.
342
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
343
try {
344                     this.transactionManager.commit(txInfo.getTransactionStatus());
345                 }
346                 catch (RuntimeException JavaDoc ex2) {
347                     logger.error("Application exception overridden by commit exception", ex);
348                     throw ex2;
349                 }
350                 catch (Error JavaDoc err) {
351                     logger.error("Application exception overridden by commit error", ex);
352                     throw err;
353                 }
354             }
355         }
356     }
357
358     /**
359      * Reset the TransactionInfo ThreadLocal.
360      * <p>Call this in all cases: exception or normal return!
361      * @param txInfo information about the current transaction (may be <code>null</code>)
362      */

363     protected void cleanupTransactionInfo(TransactionInfo txInfo) {
364         if (txInfo != null) {
365             txInfo.restoreThreadLocalStatus();
366         }
367     }
368
369
370     /**
371      * Opaque object used to hold Transaction information. Subclasses
372      * must pass it back to methods on this class, but not see its internals.
373      */

374     protected class TransactionInfo {
375
376         private final TransactionAttribute transactionAttribute;
377
378         private final String JavaDoc joinpointIdentification;
379
380         private TransactionStatus transactionStatus;
381
382         private TransactionInfo oldTransactionInfo;
383
384         public TransactionInfo(TransactionAttribute transactionAttribute, String JavaDoc joinpointIdentification) {
385             this.transactionAttribute = transactionAttribute;
386             this.joinpointIdentification = joinpointIdentification;
387         }
388
389         public TransactionAttribute getTransactionAttribute() {
390             return this.transactionAttribute;
391         }
392
393         /**
394          * Return a String representation of this joinpoint (usually a Method call)
395          * for use in logging.
396          */

397         public String JavaDoc getJoinpointIdentification() {
398             return this.joinpointIdentification;
399         }
400
401         public void newTransactionStatus(TransactionStatus status) {
402             this.transactionStatus = status;
403         }
404
405         public TransactionStatus getTransactionStatus() {
406             return this.transactionStatus;
407         }
408
409         /**
410          * Return whether a transaction was created by this aspect,
411          * or whether we just have a placeholder to keep ThreadLocal stack integrity.
412          */

413         public boolean hasTransaction() {
414             return (this.transactionStatus != null);
415         }
416
417         private void bindToThread() {
418             // Expose current TransactionStatus, preserving any existing TransactionStatus
419
// for restoration after this transaction is complete.
420
this.oldTransactionInfo = (TransactionInfo) transactionInfoHolder.get();
421             transactionInfoHolder.set(this);
422         }
423
424         private void restoreThreadLocalStatus() {
425             // Use stack to restore old transaction TransactionInfo.
426
// Will be null if none was set.
427
transactionInfoHolder.set(this.oldTransactionInfo);
428         }
429     }
430
431 }
432
Popular Tags