org.lexevs.cache.AbstractMethodCachingBean.java Source code

Java tutorial

Introduction

Here is the source code for org.lexevs.cache.AbstractMethodCachingBean.java

Source

/*
 * Copyright: (c) 2004-2010 Mayo Foundation for Medical Education and 
 * Research (MFMER). All rights reserved. MAYO, MAYO CLINIC, and the
 * triple-shield Mayo logo are trademarks and service marks of MFMER.
 *
 * Except as contained in the copyright notice above, or as used to identify 
 * MFMER as the author of this software, the trade names, trademarks, service
 * marks, or product names of the copyright holder shall not be used in
 * advertising, promotion or otherwise in connection with this software without
 * prior written authorization of the copyright holder.
 * 
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
 * 
 */
package org.lexevs.cache;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.LexGrid.LexBIG.Utility.logging.LgLoggerIF;
import org.apache.commons.lang.ClassUtils;
import org.lexevs.cache.CacheRegistry.CacheWrapper;
import org.lexevs.cache.annotation.CacheMethod;
import org.lexevs.cache.annotation.Cacheable;
import org.lexevs.cache.annotation.ClearCache;
import org.lexevs.cache.annotation.ParameterKey;
import org.lexevs.dao.database.utility.DaoUtility;
import org.lexevs.system.constants.SystemVariables;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

/**
 * The Class MethodCachingProxy.
 * 
 * @author <a href="mailto:kevin.peterson@mayo.edu">Kevin Peterson</a>
 */
public abstract class AbstractMethodCachingBean<T> {

    private static final boolean DEFAULT_ISOLATE_CACHES_ON_CLEAR = false;

    /** The logger. */
    private LgLoggerIF logger;

    private SystemVariables systemVariables;

    private static String NULL_VALUE_KEY = "null";

    private static String NULL_VALUE_CACHE_PLACEHOLDER = "NULL_VALUE_CACHE_PLACEHOLDER";

    private CacheRegistry cacheRegistry;

    private boolean isolateCachesOnClear = DEFAULT_ISOLATE_CACHES_ON_CLEAR;

    /**
     * Clear cache.
     * 
     * @param pjp the pjp
     * 
     * @return the object
     * 
     * @throws Throwable the throwable
     */
    protected Object clearCache(T joinPoint, Method method) throws Throwable {
        try {
            logger.debug("Clearing cache.");

            if (isolateCachesOnClear) {
                this.cacheRegistry.setInThreadCacheClearingState(true);
            }

            Object target = this.getTarget(joinPoint);

            Cacheable cacheableAnnotation = AnnotationUtils.findAnnotation(target.getClass(), Cacheable.class);

            ClearCache clearCacheAnnotation = method.getAnnotation(ClearCache.class);

            Object returnObj = this.proceed(joinPoint);

            clearCache(cacheableAnnotation, clearCacheAnnotation);

            return returnObj;
        } finally {
            if (isolateCachesOnClear) {
                this.cacheRegistry.setInThreadCacheClearingState(false);
            }
        }
    }

    private void clearCache(Cacheable cacheableAnnotation, ClearCache clearCacheAnnotation) {
        if (clearCacheAnnotation.clearAll()) {
            this.cacheRegistry.clearAll();
        } else {

            for (String cacheName : clearCacheAnnotation.clearCaches()) {
                CacheWrapper<String, Object> cache = this.getCacheFromName(cacheName, false);
                cache.clear();
            }

            CacheWrapper<String, Object> cache = this.getCacheFromName(cacheableAnnotation.cacheName(), false);

            cache.clear();
        }
    }

    public void clearAll() {
        cacheRegistry.clearAll();
    }

    /**
     * Cache method.
     * 
     * @param pjp the pjp
     * 
     * @return the object
     * 
     * @throws Throwable the throwable
     */
    protected Object doCacheMethod(T joinPoint) throws Throwable {

        if (!CacheSessionManager.getCachingStatus()) {
            return this.proceed(joinPoint);
        }

        Method method = this.getMethod(joinPoint);

        if (method.isAnnotationPresent(CacheMethod.class) && method.isAnnotationPresent(ClearCache.class)) {
            throw new RuntimeException("Cannot both Cache method results and clear the Cache in "
                    + "the same method. Please only use @CacheMethod OR @ClearCache -- not both. "
                    + " This occured on method: " + method.toString());
        }

        Object target = this.getTarget(joinPoint);

        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        String key = this.getKeyFromMethod(target.getClass().getName(), method.getName(),
                this.getArguments(joinPoint), parameterAnnotations);

        Cacheable cacheableAnnotation = AnnotationUtils.findAnnotation(target.getClass(), Cacheable.class);
        CacheMethod cacheMethodAnnotation = AnnotationUtils.findAnnotation(method, CacheMethod.class);

        CacheWrapper<String, Object> cache = this.getCacheFromName(cacheableAnnotation.cacheName(), true);

        if (method.isAnnotationPresent(ClearCache.class)) {
            return this.clearCache(joinPoint, method);
        }

        Object value = cache.get(key);
        if (value != null) {
            this.logger.debug("Cache hit on: " + key);
            if (value.equals(NULL_VALUE_CACHE_PLACEHOLDER)) {
                return null;
            } else {
                return returnResult(value, cacheMethodAnnotation);
            }
        } else {
            this.logger.debug("Caching miss on: " + key);
        }

        Object result = this.proceed(joinPoint);

        if (this.isolateCachesOnClear == false
                || (this.isolateCachesOnClear == true && (this.cacheRegistry.getInThreadCacheClearingState() == null
                        || this.cacheRegistry.getInThreadCacheClearingState() == false))) {
            this.logger.debug("Thread is not in @Clear state, caching can continue for key: " + key);

            if (result != null) {
                cache.put(key, result);
            } else {
                cache.put(key, NULL_VALUE_CACHE_PLACEHOLDER);
            }
        } else {
            this.logger.debug("Thread is in @Clear state, caching skipped for key: " + key);
        }

        return returnResult(result, cacheMethodAnnotation);
    }

    private Object returnResult(Object result, CacheMethod cacheMethodAnnotation) {
        if (result != null && cacheMethodAnnotation.cloneResult()
                && ClassUtils.isAssignable(result.getClass(), Serializable.class)) {
            return DaoUtility.deepClone((Serializable) result);
        } else {
            return result;
        }
    }

    protected abstract Method getMethod(T joinPoint);

    protected abstract Object getTarget(T joinPoint);

    protected abstract Object proceed(T joinPoint) throws Throwable;

    protected abstract Object[] getArguments(T joinPoint);

    public CacheWrapper<String, Object> getCacheFromName(String cacheName, boolean createIfNotPresent) {
        return this.cacheRegistry.getCache(cacheName, createIfNotPresent);
    }

    /**
     * Gets the key from method.
     * 
     * @param className the class name
     * @param signature the signature
     * @param arguments the arguments
     * @param parameterAnnotations 
     * 
     * @return the key from method
     */
    protected String getKeyFromMethod(String className, String signature, Object[] arguments,
            Annotation[][] parameterAnnotations) {
        StringBuffer sb = new StringBuffer();
        sb.append(className);
        sb.append(signature);
        for (int i = 0; i < arguments.length; i++) {
            Object arg = arguments[i];

            Annotation[] annotations = parameterAnnotations[i];

            ParameterKey parameterKey = getParameterKeyAnnotation(annotations);

            sb.append(getArgumentKey(arg, parameterKey));
        }
        return sb.toString();
    }

    private ParameterKey getParameterKeyAnnotation(Annotation[] annotations) {
        List<ParameterKey> returnList = new ArrayList<ParameterKey>();

        for (Annotation annotation : annotations) {
            if (annotation instanceof ParameterKey) {
                returnList.add((ParameterKey) annotation);
            }
        }
        if (returnList.size() > 1) {
            throw new RuntimeException("Only one ParameterKey annotation allowed per Parameter.");
        }

        if (returnList.size() == 1) {
            return returnList.get(0);
        } else {
            return null;
        }
    }

    protected String getArgumentKey(Object argument, ParameterKey key) {
        if (key != null) {
            StringBuffer sb = new StringBuffer();
            String[] fields = key.field();
            for (String fieldName : fields) {
                Field field = ReflectionUtils.findField(argument.getClass(), fieldName);
                field.setAccessible(true);

                Object fieldArg = ReflectionUtils.getField(field, argument);
                sb.append(getArgumentKey(fieldArg));
            }
            return sb.toString();
        } else {
            return getArgumentKey(argument);
        }
    }

    protected String getArgumentKey(Object argument) {
        StringBuffer sb = new StringBuffer();
        if (argument == null) {
            sb.append(NULL_VALUE_KEY);
        } else {
            sb.append(argument.hashCode());
        }
        return sb.toString();
    }

    protected Map<String, CacheWrapper<String, Object>> getCaches() {
        return this.cacheRegistry.getCaches();
    }

    /**
     * Gets the logger.
     * 
     * @return the logger
     */
    public LgLoggerIF getLogger() {
        return logger;
    }

    /**
     * Sets the logger.
     * 
     * @param logger the new logger
     */
    public void setLogger(LgLoggerIF logger) {
        this.logger = logger;
    }

    public SystemVariables getSystemVariables() {
        return systemVariables;
    }

    public void setSystemVariables(SystemVariables systemVariables) {
        this.systemVariables = systemVariables;
    }

    public void setCacheRegistry(CacheRegistry cacheRegistry) {
        this.cacheRegistry = cacheRegistry;
    }

    public CacheRegistry getCacheRegistry() {
        return cacheRegistry;
    }
}