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 }