org.springframework.cache.interceptor.YJFCacheAspectSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cache.interceptor.YJFCacheAspectSupport.java

Source

/*
 * 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;

import com.yjf.common.lang.result.Result;

/**
 * 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 YJFCacheAspectSupport 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()) {
                //?webservice ?
                if (getWebServiceEnable()) {
                    if (retVal instanceof Result) {
                        Result result = (Result) retVal;
                        if (result.isSuccess()) {
                            update(updates, retVal);
                        }
                    } else {
                        update(updates, retVal);
                    }
                } else {
                    update(updates, retVal);
                }
            }

            return retVal;
        }

        return invoker.invoke();
    }

    public abstract boolean getWebServiceEnable();

    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 = YJFCacheAspectSupport.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;
        }
    }
}