Java tutorial
/* Copyright 2012-2013 SpringSource. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package grails.plugin.cache; import java.io.Serializable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.codehaus.groovy.grails.commons.ControllerArtefactHandler; import org.codehaus.groovy.grails.commons.GrailsApplication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.CacheAnnotationParser; import org.springframework.cache.annotation.SpringCacheAnnotationParser; import org.springframework.cache.interceptor.CacheOperation; import org.springframework.cache.interceptor.CacheOperationSource; import org.springframework.core.BridgeMethodResolver; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** * getCacheOperations is called when beans are initialized and also from * PageFragmentCachingFilter during requests; the filter needs annotations on * controllers but if the standard lookup includes controllers, the return * values from the controller method calls are cached unnecessarily. * * Based on org.springframework.cache.annotation.AnnotationCacheOperationSource. * * @author Costin Leau * @author Burt Beckwith */ public class GrailsAnnotationCacheOperationSource implements CacheOperationSource, Serializable { private static final long serialVersionUID = 1; public static final String BEAN_NAME = "org.springframework.cache.annotation.AnnotationCacheOperationSource#0"; /** * Canonical value held in cache to indicate no caching attribute was * found for this method and we don't need to look again. */ protected static final Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList(); protected GrailsApplication application; protected boolean publicMethodsOnly = true; protected Logger logger = LoggerFactory.getLogger(getClass()); protected final Set<CacheAnnotationParser> annotationParsers = new LinkedHashSet<CacheAnnotationParser>(1); /** * Cache of CacheOperations, keyed by DefaultCacheKey (Method + target Class). */ protected final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<Object, Collection<CacheOperation>>(); /** * Constructor. */ public GrailsAnnotationCacheOperationSource() { annotationParsers.add(new SpringCacheAnnotationParser()); } public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass, boolean includeControllers) { if (!includeControllers && isControllerClass(targetClass)) { return null; } // will typically be called with includeControllers = true (i.e. from the filter) // so controller methods will be considered return doGetCacheOperations(method, targetClass); } public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) { // exclude controllers when called directly if (isControllerClass(targetClass)) { return null; } return doGetCacheOperations(method, targetClass); } /** * Determine the caching attribute for this method invocation. * <p>Defaults to the class's caching attribute if no method attribute is found. * @param method the method for the current invocation (never {@code null}) * @param targetClass the target class for this invocation (may be {@code null}) * @return {@link CacheOperation} for this method, or {@code null} if the method * is not cacheable */ protected Collection<CacheOperation> doGetCacheOperations(Method method, Class<?> targetClass) { // First, see if we have a cached value. Object cacheKey = getCacheKey(method, targetClass); Collection<CacheOperation> cached = attributeCache.get(cacheKey); if (cached == null) { // We need to work it out. Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass); // Put it in the cache. if (cacheOps == null) { attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE); } else { logger.debug("Adding cacheable method '{}' with attribute: {}", method.getName(), cacheOps); attributeCache.put(cacheKey, cacheOps); } return cacheOps; } if (cached == NULL_CACHING_ATTRIBUTE) { return null; } // Value will either be canonical value indicating there is no caching attribute, // or an actual caching attribute. return cached; } /** * For dev mode when rediscovering annotations is needed. */ public void reset() { attributeCache.clear(); } /** * Determine a cache key for the given method and target class. * <p>Must not produce same key for overloaded methods. * Must produce same key for different instances of the same method. * @param method the method (never {@code null}) * @param targetClass the target class (may be {@code null}) * @return the cache key (never {@code null}) */ protected Object getCacheKey(Method method, Class<?> targetClass) { return new DefaultCacheKey(method, targetClass); } protected Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) { // Don't allow no-public methods as required. if (publicMethodsOnly && !Modifier.isPublic(method.getModifiers())) { return null; } // The method may be on an interface, but we need attributes from the target class. // If the target class is null, the method will be unchanged. Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); // If we are dealing with method with generic parameters, find the original method. specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // First try is the method in the target class. Collection<CacheOperation> opDef = findCacheOperations(specificMethod); if (opDef != null) { return opDef; } // Second try is the caching operation on the target class. opDef = findCacheOperations(specificMethod.getDeclaringClass()); if (opDef != null) { return opDef; } if (specificMethod != method) { // Fall back is to look at the original method. opDef = findCacheOperations(method); if (opDef != null) { return opDef; } // Last fall back is the class of the original method. return findCacheOperations(method.getDeclaringClass()); } return null; } protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) { return determineCacheOperations(clazz); } protected Collection<CacheOperation> findCacheOperations(Method method) { return determineCacheOperations(method); } /** * Determine the cache operation(s) for the given method or class. * <p>This implementation delegates to configured * {@link CacheAnnotationParser}s for parsing known annotations into * Spring's metadata attribute class. * <p>Can be overridden to support custom annotations that carry * caching metadata. * @param ae the annotated method or class * @return the configured caching operations, or {@code null} if none found */ protected Collection<CacheOperation> determineCacheOperations(AnnotatedElement ae) { Collection<CacheOperation> ops = null; for (CacheAnnotationParser annotationParser : annotationParsers) { Collection<CacheOperation> annOps = annotationParser.parseCacheAnnotations(ae); if (annOps != null) { if (ops == null) { ops = new ArrayList<CacheOperation>(); } ops.addAll(annOps); } } return ops; } protected boolean isControllerClass(Class<?> targetClass) { return application.isArtefactOfType(ControllerArtefactHandler.TYPE, targetClass); } /** * Dependency injection for the grails application. * @param grailsApplication the app */ public void setGrailsApplication(GrailsApplication grailsApplication) { application = grailsApplication; } /** * Dependency injection for whether to only consider public methods * @param allow */ public void setAllowPublicMethodsOnly(boolean allow) { publicMethodsOnly = allow; } /** * Default cache key for the CacheOperation cache. */ protected static class DefaultCacheKey { protected final Method method; protected final Class<?> targetClass; public DefaultCacheKey(Method method, Class<?> targetClass) { this.method = method; this.targetClass = targetClass; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof DefaultCacheKey)) { return false; } DefaultCacheKey otherKey = (DefaultCacheKey) other; return method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(targetClass, otherKey.targetClass); } @Override public int hashCode() { return method.hashCode() * 29 + (targetClass == null ? 0 : targetClass.hashCode()); } } }