001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.transaction.interceptor;
018
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021import java.util.Map;
022import java.util.concurrent.ConcurrentHashMap;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.aop.support.AopUtils;
028import org.springframework.core.MethodClassKey;
029import org.springframework.lang.Nullable;
030import org.springframework.util.ClassUtils;
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 */
052public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
053
054        /**
055         * Canonical value held in cache to indicate no transaction attribute was
056         * found for this method, and we don't need to look again.
057         */
058        @SuppressWarnings("serial")
059        private static final TransactionAttribute NULL_TRANSACTION_ATTRIBUTE = new DefaultTransactionAttribute() {
060                @Override
061                public String toString() {
062                        return "null";
063                }
064        };
065
066
067        /**
068         * Logger available to subclasses.
069         * <p>As this base class is not marked Serializable, the logger will be recreated
070         * after serialization - provided that the concrete subclass is Serializable.
071         */
072        protected final Log logger = LogFactory.getLog(getClass());
073
074        /**
075         * Cache of TransactionAttributes, keyed by method on a specific target class.
076         * <p>As this base class is not marked Serializable, the cache will be recreated
077         * after serialization - provided that the concrete subclass is Serializable.
078         */
079        private final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>(1024);
080
081
082        /**
083         * Determine the transaction attribute for this method invocation.
084         * <p>Defaults to the class's transaction attribute if no method attribute is found.
085         * @param method the method for the current invocation (never {@code null})
086         * @param targetClass the target class for this invocation (may be {@code null})
087         * @return a TransactionAttribute for this method, or {@code null} if the method
088         * is not transactional
089         */
090        @Override
091        @Nullable
092        public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
093                if (method.getDeclaringClass() == Object.class) {
094                        return null;
095                }
096
097                // First, see if we have a cached value.
098                Object cacheKey = getCacheKey(method, targetClass);
099                TransactionAttribute cached = this.attributeCache.get(cacheKey);
100                if (cached != null) {
101                        // Value will either be canonical value indicating there is no transaction attribute,
102                        // or an actual transaction attribute.
103                        if (cached == NULL_TRANSACTION_ATTRIBUTE) {
104                                return null;
105                        }
106                        else {
107                                return cached;
108                        }
109                }
110                else {
111                        // We need to work it out.
112                        TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
113                        // Put it in the cache.
114                        if (txAttr == null) {
115                                this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
116                        }
117                        else {
118                                String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
119                                if (txAttr instanceof DefaultTransactionAttribute) {
120                                        ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
121                                }
122                                if (logger.isTraceEnabled()) {
123                                        logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
124                                }
125                                this.attributeCache.put(cacheKey, txAttr);
126                        }
127                        return txAttr;
128                }
129        }
130
131        /**
132         * Determine a cache key for the given method and target class.
133         * <p>Must not produce same key for overloaded methods.
134         * Must produce same key for different instances of the same method.
135         * @param method the method (never {@code null})
136         * @param targetClass the target class (may be {@code null})
137         * @return the cache key (never {@code null})
138         */
139        protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) {
140                return new MethodClassKey(method, targetClass);
141        }
142
143        /**
144         * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
145         * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
146         * <p>As of 4.1.8, this method can be overridden.
147         * @since 4.1.8
148         * @see #getTransactionAttribute
149         */
150        @Nullable
151        protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
152                // Don't allow no-public methods as required.
153                if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
154                        return null;
155                }
156
157                // The method may be on an interface, but we need attributes from the target class.
158                // If the target class is null, the method will be unchanged.
159                Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
160
161                // First try is the method in the target class.
162                TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
163                if (txAttr != null) {
164                        return txAttr;
165                }
166
167                // Second try is the transaction attribute on the target class.
168                txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
169                if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
170                        return txAttr;
171                }
172
173                if (specificMethod != method) {
174                        // Fallback is to look at the original method.
175                        txAttr = findTransactionAttribute(method);
176                        if (txAttr != null) {
177                                return txAttr;
178                        }
179                        // Last fallback is the class of the original method.
180                        txAttr = findTransactionAttribute(method.getDeclaringClass());
181                        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
182                                return txAttr;
183                        }
184                }
185
186                return null;
187        }
188
189
190        /**
191         * Subclasses need to implement this to return the transaction attribute for the
192         * given class, if any.
193         * @param clazz the class to retrieve the attribute for
194         * @return all transaction attribute associated with this class, or {@code null} if none
195         */
196        @Nullable
197        protected abstract TransactionAttribute findTransactionAttribute(Class<?> clazz);
198
199        /**
200         * Subclasses need to implement this to return the transaction attribute for the
201         * given method, if any.
202         * @param method the method to retrieve the attribute for
203         * @return all transaction attribute associated with this method, or {@code null} if none
204         */
205        @Nullable
206        protected abstract TransactionAttribute findTransactionAttribute(Method method);
207
208        /**
209         * Should only public methods be allowed to have transactional semantics?
210         * <p>The default implementation returns {@code false}.
211         */
212        protected boolean allowPublicMethodsOnly() {
213                return false;
214        }
215
216}