Package grails.plugin.cache

Source Code of grails.plugin.cache.GrailsAnnotationCacheOperationSource$DefaultCacheKey

/* Copyright 2012 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());
    }
  }
}
TOP

Related Classes of grails.plugin.cache.GrailsAnnotationCacheOperationSource$DefaultCacheKey

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.