Package org.springframework.cache.interceptor

Source Code of org.springframework.cache.interceptor.CacheAspectSupport$Invoker

/*
* Copyright 2002-2011 the original author or authors.
*
* 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 org.springframework.cache.interceptor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
* Base class for caching aspects, such as the {@link CacheInterceptor}
* or an AspectJ aspect.
*
* <p>This enables the underlying Spring caching infrastructure to be
* used easily to implement an aspect for any aspect system.
*
* <p>Subclasses are responsible for calling methods in this class in
* the correct order.
*
* <p>Uses the <b>Strategy</b> design pattern. A {@link CacheManager}
* implementation will perform the actual cache management, and a
* {@link CacheOperationSource} is used for determining caching
* operations.
*
* <p>A cache aspect is serializable if its {@code CacheManager} and
* {@code CacheOperationSource} are serializable.
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.1
*/
public abstract class CacheAspectSupport implements InitializingBean {

  public interface Invoker {
    Object invoke();
  }

  protected final Log logger = LogFactory.getLog(getClass());

  private CacheManager cacheManager;

  private CacheOperationSource cacheOperationSource;

  private final ExpressionEvaluator evaluator = new ExpressionEvaluator();

  private KeyGenerator keyGenerator = new DefaultKeyGenerator();

  private boolean initialized = false;

  private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict";

  /**
   * Set the CacheManager that this cache aspect should delegate to.
   */
  public void setCacheManager(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }

  /**
   * Return the CacheManager that this cache aspect delegates to.
   */
  public CacheManager getCacheManager() {
    return this.cacheManager;
  }

  /**
   * Set one or more cache operation sources which are used to find the cache
   * attributes. If more than one source is provided, they will be aggregated using a
   * {@link CompositeCacheOperationSource}.
   * @param cacheOperationSources must not be {@code null}
   */
  public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) {
    Assert.notEmpty(cacheOperationSources);
    this.cacheOperationSource =
        (cacheOperationSources.length > 1 ?
            new CompositeCacheOperationSource(cacheOperationSources) :
            cacheOperationSources[0]);
  }

  /**
   * Return the CacheOperationSource for this cache aspect.
   */
  public CacheOperationSource getCacheOperationSource() {
    return this.cacheOperationSource;
  }

  /**
   * Set the KeyGenerator for this cache aspect.
   * Default is {@link DefaultKeyGenerator}.
   */
  public void setKeyGenerator(KeyGenerator keyGenerator) {
    this.keyGenerator = keyGenerator;
  }

  /**
   * Return the KeyGenerator for this cache aspect,
   */
  public KeyGenerator getKeyGenerator() {
    return this.keyGenerator;
  }

  public void afterPropertiesSet() {
    if (this.cacheManager == null) {
      throw new IllegalStateException("'cacheManager' is required");
    }
    if (this.cacheOperationSource == null) {
      throw new IllegalStateException("The 'cacheOperationSources' property is required: "
          + "If there are no cacheable methods, then don't use a cache aspect.");
    }

    this.initialized = true;
  }

  /**
   * Convenience method to return a String representation of this Method
   * for use in logging. Can be overridden in subclasses to provide a
   * different identifier for the given method.
   * @param method the method we're interested in
   * @param targetClass class the method is on
   * @return log message identifying this method
   * @see org.springframework.util.ClassUtils#getQualifiedMethodName
   */
  protected String methodIdentification(Method method, Class<?> targetClass) {
    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
    return ClassUtils.getQualifiedMethodName(specificMethod);
  }

  protected Collection<Cache> getCaches(CacheOperation operation) {
    Set<String> cacheNames = operation.getCacheNames();
    Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
    for (String cacheName : cacheNames) {
      Cache cache = this.cacheManager.getCache(cacheName);
      if (cache == null) {
        throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation);
      }
      caches.add(cache);
    }
    return caches;
  }

  protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args,
      Object target, Class<?> targetClass) {

    return new CacheOperationContext(operation, method, args, target, targetClass);
  }

  protected Object execute(Invoker invoker, Object target, Method method, Object[] args) {
    // check whether aspect is enabled
    // to cope with cases where the AJ is pulled in automatically
    if (!this.initialized) {
      return invoker.invoke();
    }

    // get backing class
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
    if (targetClass == null && target != null) {
      targetClass = target.getClass();
    }
    final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);

    // analyze caching information
    if (!CollectionUtils.isEmpty(cacheOp)) {
      Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass);

      // start with evictions
      inspectBeforeCacheEvicts(ops.get(EVICT));

      // follow up with cacheable
      CacheStatus status = inspectCacheables(ops.get(CACHEABLE));

      Object retVal = null;
      Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE));

      if (status != null) {
        if (status.updateRequired) {
          updates.putAll(status.cUpdates);
        }
        // return cached object
        else {
          return status.retVal;
        }
      }

      retVal = invoker.invoke();

      inspectAfterCacheEvicts(ops.get(EVICT));

      if (!updates.isEmpty()) {
        update(updates, retVal);
      }

      return retVal;
    }

    return invoker.invoke();
  }
 
  private void inspectBeforeCacheEvicts(Collection<CacheOperationContext> evictions) {
    inspectCacheEvicts(evictions, true);
  }

  private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions) {
    inspectCacheEvicts(evictions, false);
  }

  private void inspectCacheEvicts(Collection<CacheOperationContext> evictions, boolean beforeInvocation) {

    if (!evictions.isEmpty()) {

      boolean log = logger.isTraceEnabled();

      for (CacheOperationContext context : evictions) {
        CacheEvictOperation evictOp = (CacheEvictOperation) context.operation;

        if (beforeInvocation == evictOp.isBeforeInvocation()) {
          if (context.isConditionPassing()) {
            // for each cache
            // lazy key initialization
            Object key = null;

            for (Cache cache : context.getCaches()) {
              // cache-wide flush
              if (evictOp.isCacheWide()) {
                cache.clear();
                if (log) {
                  logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method);
                }
              } else {
                // check key
                if (key == null) {
                  key = context.generateKey();
                }
                if (log) {
                  logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method);
                }
                cache.evict(key);
              }
            }
          } else {
            if (log) {
              logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
            }
          }
        }
      }
    }
  }

  private CacheStatus inspectCacheables(Collection<CacheOperationContext> cacheables) {
    Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(cacheables.size());

    boolean updateRequire = false;
    Object retVal = null;

    if (!cacheables.isEmpty()) {
      boolean log = logger.isTraceEnabled();
      boolean atLeastOnePassed = false;

      for (CacheOperationContext context : cacheables) {
        if (context.isConditionPassing()) {
          atLeastOnePassed = true;
          Object key = context.generateKey();

          if (log) {
            logger.trace("Computed cache key " + key + " for operation " + context.operation);
          }
          if (key == null) {
            throw new IllegalArgumentException(
                "Null key returned for cache operation (maybe you are using named params on classes without debug info?) "
                    + context.operation);
          }

          // add op/key (in case an update is discovered later on)
          cUpdates.put(context, key);

          boolean localCacheHit = false;

          // check whether the cache needs to be inspected or not (the method will be invoked anyway)
          if (!updateRequire) {
            for (Cache cache : context.getCaches()) {
              Cache.ValueWrapper wrapper = cache.get(key);
              if (wrapper != null) {
                retVal = wrapper.get();
                localCacheHit = true;
                break;
              }
            }
          }

          if (!localCacheHit) {
            updateRequire = true;
          }
        }
        else {
          if (log) {
            logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
          }
        }
      }
     
      // return a status only if at least on cacheable matched
      if (atLeastOnePassed) {
        return new CacheStatus(cUpdates, updateRequire, retVal);
      }
    }

    return null;
  }

  private static class CacheStatus {
    // caches/key
    final Map<CacheOperationContext, Object> cUpdates;
    final boolean updateRequired;
    final Object retVal;

    CacheStatus(Map<CacheOperationContext, Object> cUpdates, boolean updateRequired, Object retVal) {
      this.cUpdates = cUpdates;
      this.updateRequired = updateRequired;
      this.retVal = retVal;
    }
  }

  private Map<CacheOperationContext, Object> inspectCacheUpdates(Collection<CacheOperationContext> updates) {

    Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(updates.size());

    if (!updates.isEmpty()) {
      boolean log = logger.isTraceEnabled();

      for (CacheOperationContext context : updates) {
        if (context.isConditionPassing()) {

          Object key = context.generateKey();

          if (log) {
            logger.trace("Computed cache key " + key + " for operation " + context.operation);
          }
          if (key == null) {
            throw new IllegalArgumentException(
                "Null key returned for cache operation (maybe you are using named params on classes without debug info?) "
                    + context.operation);
          }

          // add op/key (in case an update is discovered later on)
          cUpdates.put(context, key);
        }
        else {
          if (log) {
            logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
          }
        }
      }
    }

    return cUpdates;
  }

  private void update(Map<CacheOperationContext, Object> updates, Object retVal) {
    for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) {
      for (Cache cache : entry.getKey().getCaches()) {
        cache.put(entry.getValue(), retVal);
      }
    }
  }

  private Map<String, Collection<CacheOperationContext>> createOperationContext(Collection<CacheOperation> cacheOp,
      Method method, Object[] args, Object target, Class<?> targetClass) {
    Map<String, Collection<CacheOperationContext>> map = new LinkedHashMap<String, Collection<CacheOperationContext>>(3);

    Collection<CacheOperationContext> cacheables = new ArrayList<CacheOperationContext>();
    Collection<CacheOperationContext> evicts = new ArrayList<CacheOperationContext>();
    Collection<CacheOperationContext> updates = new ArrayList<CacheOperationContext>();

    for (CacheOperation cacheOperation : cacheOp) {
      CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass);

      if (cacheOperation instanceof CacheableOperation) {
        cacheables.add(opContext);
      }

      if (cacheOperation instanceof CacheEvictOperation) {
        evicts.add(opContext);
      }

      if (cacheOperation instanceof CachePutOperation) {
        updates.add(opContext);
      }
    }

    map.put(CACHEABLE, cacheables);
    map.put(EVICT, evicts);
    map.put(UPDATE, updates);

    return map;
  }

  protected class CacheOperationContext {

    private final CacheOperation operation;

    private final Collection<Cache> caches;

    private final Object target;

    private final Method method;

    private final Object[] args;

    // context passed around to avoid multiple creations
    private final EvaluationContext evalContext;

    public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
      this.operation = operation;
      this.caches = CacheAspectSupport.this.getCaches(operation);
      this.target = target;
      this.method = method;
      this.args = args;

      this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass);
    }

    protected boolean isConditionPassing() {
      if (StringUtils.hasText(this.operation.getCondition())) {
        return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext);
      }
      return true;
    }

    /**
     * Computes the key for the given caching operation.
     * @return generated key (null if none can be generated)
     */
    protected Object generateKey() {
      if (StringUtils.hasText(this.operation.getKey())) {
        return evaluator.key(this.operation.getKey(), this.method, this.evalContext);
      }
      return keyGenerator.generate(this.target, this.method, this.args);
    }

    protected Collection<Cache> getCaches() {
      return this.caches;
    }
  }
}
TOP

Related Classes of org.springframework.cache.interceptor.CacheAspectSupport$Invoker

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.