1 /*
2  * Copyright 2002-2011 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.cache.interceptor;
18 
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Map;
24 import java.util.concurrent.ConcurrentHashMap;
25 
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.springframework.core.BridgeMethodResolver;
29 import org.springframework.util.ClassUtils;
30 import org.springframework.util.ObjectUtils;
31 
32 /**
33  * Abstract implementation of {@link CacheOperation} that caches
34  * attributes for methods and implements a fallback policy: 1. specific
35  * target method; 2. target class; 3. declaring method; 4. declaring
36  * class/interface.
37  *
38  * <p>Defaults to using the target class's caching attribute if none is
39  * associated with the target method. Any caching attribute associated
40  * with the target method completely overrides a class caching attribute.
41  * If none found on the target class, the interface that the invoked
42  * method has been called through (in case of a JDK proxy) will be
43  * checked.
44  *
45  * <p>This implementation caches attributes by method after they are
46  * first used. If it is ever desirable to allow dynamic changing of
47  * cacheable attributes (which is very unlikely), caching could be made
48  * configurable.
49  *
50  * @author Costin Leau
51  * @since 3.1
52  */
53 public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
54 
55 	/**
56 	 * Canonical value held in cache to indicate no caching attribute was
57 	 * found for this method and we don't need to look again.
58 	 */
59 	private final static Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
60 
61 	/**
62 	 * Logger available to subclasses.
63 	 * <p>As this base class is not marked Serializable, the logger will be recreated
64 	 * after serialization - provided that the concrete subclass is Serializable.
65 	 */
66 	protected final Log logger = LogFactory.getLog(getClass());
67 
68 	/**
69 	 * Cache of CacheOperations, keyed by DefaultCacheKey (Method + target Class).
70 	 * <p>As this base class is not marked Serializable, the cache will be recreated
71 	 * after serialization - provided that the concrete subclass is Serializable.
72 	 */
73 	final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<Object, Collection<CacheOperation>>();
74 
75 
76 	/**
77 	 * Determine the caching attribute for this method invocation.
78 	 * <p>Defaults to the class's caching attribute if no method attribute is found.
79 	 * @param method the method for the current invocation (never {@code null})
80 	 * @param targetClass the target class for this invocation (may be {@code null})
81 	 * @return {@link CacheOperation} for this method, or {@code null} if the method
82 	 * is not cacheable
83 	 */
getCacheOperations(Method method, Class<?> targetClass)84 	public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
85 		// First, see if we have a cached value.
86 		Object cacheKey = getCacheKey(method, targetClass);
87 		Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
88 		if (cached != null) {
89 			if (cached == NULL_CACHING_ATTRIBUTE) {
90 				return null;
91 			}
92 			// Value will either be canonical value indicating there is no caching attribute,
93 			// or an actual caching attribute.
94 			return cached;
95 		}
96 		else {
97 			// We need to work it out.
98 			Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
99 			// Put it in the cache.
100 			if (cacheOps == null) {
101 				this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
102 			}
103 			else {
104 				if (logger.isDebugEnabled()) {
105 					logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
106 				}
107 				this.attributeCache.put(cacheKey, cacheOps);
108 			}
109 			return cacheOps;
110 		}
111 	}
112 
113 	/**
114 	 * Determine a cache key for the given method and target class.
115 	 * <p>Must not produce same key for overloaded methods.
116 	 * Must produce same key for different instances of the same method.
117 	 * @param method the method (never {@code null})
118 	 * @param targetClass the target class (may be {@code null})
119 	 * @return the cache key (never {@code null})
120 	 */
getCacheKey(Method method, Class<?> targetClass)121 	protected Object getCacheKey(Method method, Class<?> targetClass) {
122 		return new DefaultCacheKey(method, targetClass);
123 	}
124 
computeCacheOperations(Method method, Class<?> targetClass)125 	private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
126 		// Don't allow no-public methods as required.
127 		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
128 			return null;
129 		}
130 
131 		// The method may be on an interface, but we need attributes from the target class.
132 		// If the target class is null, the method will be unchanged.
133 		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
134 		// If we are dealing with method with generic parameters, find the original method.
135 		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
136 
137 		// First try is the method in the target class.
138 		Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
139 		if (opDef != null) {
140 			return opDef;
141 		}
142 
143 		// Second try is the caching operation on the target class.
144 		opDef = findCacheOperations(specificMethod.getDeclaringClass());
145 		if (opDef != null) {
146 			return opDef;
147 		}
148 
149 		if (specificMethod != method) {
150 			// Fall back is to look at the original method.
151 			opDef = findCacheOperations(method);
152 			if (opDef != null) {
153 				return opDef;
154 			}
155 			// Last fall back is the class of the original method.
156 			return findCacheOperations(method.getDeclaringClass());
157 		}
158 		return null;
159 	}
160 
161 
162 	/**
163 	 * Subclasses need to implement this to return the caching attribute
164 	 * for the given method, if any.
165 	 * @param method the method to retrieve the attribute for
166 	 * @return all caching attribute associated with this method
167 	 * (or {@code null} if none)
168 	 */
findCacheOperations(Method method)169 	protected abstract Collection<CacheOperation> findCacheOperations(Method method);
170 
171 	/**
172 	 * Subclasses need to implement this to return the caching attribute
173 	 * for the given class, if any.
174 	 * @param clazz the class to retrieve the attribute for
175 	 * @return all caching attribute associated with this class
176 	 * (or {@code null} if none)
177 	 */
findCacheOperations(Class<?> clazz)178 	protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
179 
180 	/**
181 	 * Should only public methods be allowed to have caching semantics?
182 	 * <p>The default implementation returns {@code false}.
183 	 */
allowPublicMethodsOnly()184 	protected boolean allowPublicMethodsOnly() {
185 		return false;
186 	}
187 
188 
189 	/**
190 	 * Default cache key for the CacheOperation cache.
191 	 */
192 	private static class DefaultCacheKey {
193 
194 		private final Method method;
195 
196 		private final Class<?> targetClass;
197 
DefaultCacheKey(Method method, Class<?> targetClass)198 		public DefaultCacheKey(Method method, Class<?> targetClass) {
199 			this.method = method;
200 			this.targetClass = targetClass;
201 		}
202 
203 		@Override
equals(Object other)204 		public boolean equals(Object other) {
205 			if (this == other) {
206 				return true;
207 			}
208 			if (!(other instanceof DefaultCacheKey)) {
209 				return false;
210 			}
211 			DefaultCacheKey otherKey = (DefaultCacheKey) other;
212 			return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass,
213 					otherKey.targetClass));
214 		}
215 
216 		@Override
hashCode()217 		public int hashCode() {
218 			return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
219 		}
220 	}
221 }
222