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.lang.reflect.Modifier;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import org.springframework.core.BridgeMethodResolver;
028: import org.springframework.core.JdkVersion;
029: import org.springframework.util.ClassUtils;
030: import org.springframework.util.ObjectUtils;
031:
032: /**
033: * Abstract implementation of {@link TransactionAttributeSource} that caches
034: * attributes for methods and implements a fallback policy: 1. specific target
035: * method; 2. target class; 3. declaring method; 4. declaring class/interface.
036: *
037: * <p>Defaults to using the target class's transaction attribute if none is
038: * associated with the target method. Any transaction attribute associated with
039: * the target method completely overrides a class transaction attribute.
040: * If none found on the target class, the interface that the invoked method
041: * has been called through (in case of a JDK proxy) will be checked.
042: *
043: * <p>This implementation caches attributes by method after they are first used.
044: * If it is ever desirable to allow dynamic changing of transaction attributes
045: * (which is very unlikely), caching could be made configurable. Caching is
046: * desirable because of the cost of evaluating rollback rules.
047: *
048: * @author Rod Johnson
049: * @author Juergen Hoeller
050: * @since 1.1
051: */
052: public abstract class AbstractFallbackTransactionAttributeSource
053: implements TransactionAttributeSource {
054:
055: /**
056: * Canonical value held in cache to indicate no transaction attribute was
057: * found for this method, and we don't need to look again.
058: */
059: private final static Object NULL_TRANSACTION_ATTRIBUTE = new Object();
060:
061: /**
062: * Logger available to subclasses.
063: * <p>As this base class is not marked Serializable, the logger will be recreated
064: * after serialization - provided that the concrete subclass is Serializable.
065: */
066: protected final Log logger = LogFactory.getLog(getClass());
067:
068: /**
069: * Cache of TransactionAttributes, keyed by DefaultCacheKey (Method + target Class).
070: * <p>As this base class is not marked Serializable, the cache will be recreated
071: * after serialization - provided that the concrete subclass is Serializable.
072: */
073: final Map attributeCache = new HashMap();
074:
075: /**
076: * Determine the transaction attribute for this method invocation.
077: * <p>Defaults to the class's transaction attribute if no method attribute is found.
078: * @param method the method for the current invocation (never <code>null</code>)
079: * @param targetClass the target class for this invocation (may be <code>null</code>)
080: * @return TransactionAttribute for this method, or <code>null</code> if the method
081: * is not transactional
082: */
083: public TransactionAttribute getTransactionAttribute(Method method,
084: Class targetClass) {
085: // First, see if we have a cached value.
086: Object cacheKey = getCacheKey(method, targetClass);
087: synchronized (this .attributeCache) {
088: Object cached = this .attributeCache.get(cacheKey);
089: if (cached != null) {
090: // Value will either be canonical value indicating there is no transaction attribute,
091: // or an actual transaction attribute.
092: if (cached == NULL_TRANSACTION_ATTRIBUTE) {
093: return null;
094: } else {
095: return (TransactionAttribute) cached;
096: }
097: } else {
098: // We need to work it out.
099: TransactionAttribute txAtt = computeTransactionAttribute(
100: method, targetClass);
101: // Put it in the cache.
102: if (txAtt == null) {
103: this .attributeCache.put(cacheKey,
104: NULL_TRANSACTION_ATTRIBUTE);
105: } else {
106: if (logger.isDebugEnabled()) {
107: logger.debug("Adding transactional method ["
108: + method.getName()
109: + "] with attribute [" + txAtt + "]");
110: }
111: this .attributeCache.put(cacheKey, txAtt);
112: }
113: return txAtt;
114: }
115: }
116: }
117:
118: /**
119: * Determine a cache key for the given method and target class.
120: * <p>Must not produce same key for overloaded methods.
121: * Must produce same key for different instances of the same method.
122: * @param method the method (never <code>null</code>)
123: * @param targetClass the target class (may be <code>null</code>)
124: * @return the cache key (never <code>null</code>)
125: */
126: protected Object getCacheKey(Method method, Class targetClass) {
127: return new DefaultCacheKey(method, targetClass);
128: }
129:
130: /**
131: * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
132: * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
133: * @see #getTransactionAttribute
134: */
135: private TransactionAttribute computeTransactionAttribute(
136: Method method, Class targetClass) {
137: // Don't allow no-public methods as required.
138: if (allowPublicMethodsOnly()
139: && !Modifier.isPublic(method.getModifiers())) {
140: return null;
141: }
142:
143: // The method may be on an interface, but we need attributes from the target class.
144: // If the target class is null, the method will be unchanged.
145: Method specificMethod = ClassUtils.getMostSpecificMethod(
146: method, targetClass);
147: // If we are dealing with method with generic parameters, find the original method.
148: if (JdkVersion.isAtLeastJava15()) {
149: specificMethod = BridgeMethodResolver
150: .findBridgedMethod(specificMethod);
151: }
152:
153: // First try is the method in the target class.
154: TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
155: if (txAtt != null) {
156: return txAtt;
157: }
158:
159: // Second try is the transaction attribute on the target class.
160: txAtt = findTransactionAttribute(specificMethod
161: .getDeclaringClass());
162: if (txAtt != null) {
163: return txAtt;
164: }
165:
166: if (specificMethod != method) {
167: // Fallback is to look at the original method.
168: txAtt = findTransactionAttribute(method);
169: if (txAtt != null) {
170: return txAtt;
171: }
172: // Last fallback is the class of the original method.
173: return findTransactionAttribute(method.getDeclaringClass());
174: }
175: return null;
176: }
177:
178: /**
179: * Subclasses need to implement this to return the transaction attribute
180: * for the given method, if any.
181: * @param method the method to retrieve the attribute for
182: * @return all transaction attribute associated with this method
183: * (or <code>null</code> if none)
184: */
185: protected abstract TransactionAttribute findTransactionAttribute(
186: Method method);
187:
188: /**
189: * Subclasses need to implement this to return the transaction attribute
190: * for the given class, if any.
191: * @param clazz the class to retrieve the attribute for
192: * @return all transaction attribute associated with this class
193: * (or <code>null</code> if none)
194: */
195: protected abstract TransactionAttribute findTransactionAttribute(
196: Class clazz);
197:
198: /**
199: * Should only public methods be allowed to have transactional semantics?
200: * <p>The default implementation returns <code>false</code>.
201: */
202: protected boolean allowPublicMethodsOnly() {
203: return false;
204: }
205:
206: /**
207: * Default cache key for the TransactionAttribute cache.
208: */
209: private static class DefaultCacheKey {
210:
211: private final Method method;
212:
213: private final Class targetClass;
214:
215: public DefaultCacheKey(Method method, Class targetClass) {
216: this .method = method;
217: this .targetClass = targetClass;
218: }
219:
220: public boolean equals(Object other) {
221: if (this == other) {
222: return true;
223: }
224: if (!(other instanceof DefaultCacheKey)) {
225: return false;
226: }
227: DefaultCacheKey otherKey = (DefaultCacheKey) other;
228: return (this .method.equals(otherKey.method) && ObjectUtils
229: .nullSafeEquals(this .targetClass,
230: otherKey.targetClass));
231: }
232:
233: public int hashCode() {
234: return this .method.hashCode()
235: * 29
236: + (this .targetClass != null ? this .targetClass
237: .hashCode() : 0);
238: }
239: }
240:
241: }
|