org.onebusaway.container.cache.CacheableMethodKeyFactoryManager.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.container.cache.CacheableMethodKeyFactoryManager.java

Source

/**
 * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
 *
 * 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.onebusaway.container.cache;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.ehcache.Cache;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.onebusaway.collections.PropertyPathExpression;
import org.springframework.stereotype.Component;

/**
 * Support class that determines the caching policy for a particular method by
 * producing a {@link CacheableMethodKeyFactory} for that method. We make use of
 * {@link Cacheable}, {@link CacheableArgument}, and {@link CacheableKey}
 * annotations to allow customization of the default key factory behavior, as
 * well as directly setting behavior using methods such as
 * {@link #setCacheKeyFactories(Map)} and
 * {@link #putCacheRefreshIndicatorArgumentIndexForMethod(Method, int)}.
 * 
 * @author bdferris
 * @see CacheableMethodKeyFactory
 * @see Cacheable
 * @see CacheableArgument
 * @see CacheableKey
 */
@Component
public class CacheableMethodKeyFactoryManager {

    private Map<Class<?>, CacheableObjectKeyFactory> _keyFactories = new HashMap<Class<?>, CacheableObjectKeyFactory>();

    private Map<Method, Integer> _cacheRefreshIndicatorArgumentIndexByMethod = new HashMap<Method, Integer>();

    public void addCacheableObjectKeyFactory(Class<?> className, CacheableObjectKeyFactory keyFactory) {
        _keyFactories.put(className, keyFactory);
    }

    public void setCacheKeyFactories(Map<Object, Object> keyFactories) {
        for (Map.Entry<Object, Object> entry : keyFactories.entrySet()) {
            Class<?> className = getObjectAsClass(entry.getKey());
            CacheableObjectKeyFactory keyFactory = getObjectAsObjectKeyFactory(entry.getValue());
            addCacheableObjectKeyFactory(className, keyFactory);
        }
    }

    public void putCacheRefreshIndicatorArgumentIndexForMethodSignature(String methodName, int argumentIndex) {
        Method method = getMethodForSignature(methodName);
        _cacheRefreshIndicatorArgumentIndexByMethod.put(method, argumentIndex);
    }

    public void putCacheRefreshIndicatorArgumentIndexForMethod(Method method, int argumentIndex) {
        _cacheRefreshIndicatorArgumentIndexByMethod.put(method, argumentIndex);
    }

    public CacheableMethodKeyFactory getCacheableMethodKeyFactoryForJoinPoint(ProceedingJoinPoint pjp,
            Method method) {
        return getCacheableMethodKeyFactoryForMethod(method);
    }

    public CacheableMethodKeyFactory getCacheableMethodKeyFactoryForMethod(Method m) {

        /**
         * Try looking for a @Cacheable annotation first and using that first for
         * the CacheableMethodKeyFactory if specified
         */
        Cacheable cacheableAnnotation = m.getAnnotation(Cacheable.class);

        if (cacheableAnnotation != null) {

            Class<? extends CacheableMethodKeyFactory> keyFactoryType = cacheableAnnotation.keyFactory();

            if (!keyFactoryType.equals(CacheableMethodKeyFactory.class))
                try {
                    return keyFactoryType.newInstance();
                } catch (Exception ex) {
                    throw new IllegalStateException(
                            "error instantiating CacheableKeyFactory: " + keyFactoryType.getName(), ex);
                }
        }

        /**
         * Revert to default cacheable method key factory behavior
         */
        Class<?>[] parameters = m.getParameterTypes();
        Annotation[][] annotations = m.getParameterAnnotations();

        int cacheRefreshParameterIndex = -1;
        if (_cacheRefreshIndicatorArgumentIndexByMethod.containsKey(m))
            cacheRefreshParameterIndex = _cacheRefreshIndicatorArgumentIndexByMethod.get(m);

        CacheableObjectKeyFactory[] keyFactories = new CacheableObjectKeyFactory[parameters.length];
        for (int i = 0; i < parameters.length; i++) {

            boolean cacheRefreshIndicator = (i == cacheRefreshParameterIndex);
            CacheableArgument cacheableArgumentAnnotation = getCacheableArgumentAnnotation(annotations[i]);

            if (cacheableArgumentAnnotation != null) {
                keyFactories[i] = getKeyFactoryForCacheableArgumentAnnotation(parameters[i],
                        cacheableArgumentAnnotation, cacheRefreshIndicator);
            } else {
                keyFactories[i] = getKeyFactoryForParameterType(parameters[i], cacheRefreshIndicator);
            }
        }

        return new DefaultCacheableKeyFactory(keyFactories);
    }

    public Method getMatchingMethodForJoinPoint(ProceedingJoinPoint pjp) {
        List<Method> methods = getMatchingMethodsForJoinPoint(pjp);
        if (methods.size() == 1) {
            return methods.get(0);
        } else if (methods.size() == 0) {
            throw new IllegalArgumentException("method not found: pjp=" + pjp.getSignature());
        } else {
            throw new IllegalArgumentException("multiple methods found: pjp=" + pjp.getSignature());
        }
    }

    public List<Method> getMatchingMethodsForJoinPoint(ProceedingJoinPoint pjp) {

        Signature sig = pjp.getSignature();

        Object target = pjp.getTarget();
        Class<?> type = target.getClass();

        List<Method> matches = new ArrayList<Method>();

        for (Method m : type.getDeclaredMethods()) {
            if (!m.getName().equals(sig.getName()))
                continue;

            // if (m.getModifiers() != sig.getModifiers())
            // continue;
            Object[] args = pjp.getArgs();
            Class<?>[] types = m.getParameterTypes();
            if (args.length != types.length)
                continue;
            boolean miss = false;
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                Class<?> argType = types[i];
                if (argType.isPrimitive()) {
                    if (argType.equals(Double.TYPE) && !arg.getClass().equals(Double.class))
                        miss = true;
                } else {
                    if (arg != null && !argType.isInstance(arg))
                        miss = true;
                }
            }
            if (miss)
                continue;
            matches.add(m);
        }

        return matches;
    }

    /***************************************************************************
     * Private Methods
     **************************************************************************/

    protected CacheableArgument getCacheableArgumentAnnotation(Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if (annotation instanceof CacheableArgument)
                return (CacheableArgument) annotation;
        }
        return null;
    }

    protected CacheableObjectKeyFactory getKeyFactoryForCacheableArgumentAnnotation(Class<?> type,
            CacheableArgument cacheableArgumentAnnotation, boolean cacheRefreshIndicator) {

        String keyProperty = cacheableArgumentAnnotation.keyProperty();

        cacheRefreshIndicator |= cacheableArgumentAnnotation.cacheRefreshIndicator();

        if (!(keyProperty == null || keyProperty.equals(""))) {
            PropertyPathExpression expression = new PropertyPathExpression(keyProperty);
            type = expression.initialize(type);
            CacheableObjectKeyFactory factory = getKeyFactoryForParameterType(type, cacheRefreshIndicator);
            return new PropertyPathExpressionCacheableObjectKeyFactory(expression, factory);
        }

        // Nothing interesting defined in the annotation? Apply the default behavior
        return getKeyFactoryForParameterType(type, cacheRefreshIndicator);
    }

    protected CacheableObjectKeyFactory getKeyFactoryForParameterType(Class<?> type,
            boolean cacheRefreshIndicator) {

        if (_keyFactories.containsKey(type))
            return _keyFactories.get(type);

        for (Map.Entry<Class<?>, CacheableObjectKeyFactory> entry : _keyFactories.entrySet()) {
            Class<?> argumentType = entry.getKey();
            if (argumentType.isAssignableFrom(type))
                return entry.getValue();
        }

        Class<?> checkType = type;

        while (checkType != null && !checkType.equals(Object.class)) {
            CacheableKey annotation = checkType.getAnnotation(CacheableKey.class);

            if (annotation != null) {
                Class<? extends CacheableObjectKeyFactory> keyFactoryType = annotation.keyFactory();
                try {
                    return keyFactoryType.newInstance();
                } catch (Exception ex) {
                    throw new IllegalStateException("error instantiating CacheableObjectKeyFactory [type="
                            + keyFactoryType.getName() + "] from CacheableKey [type=" + checkType.getName() + "]",
                            ex);
                }
            }
            checkType = type.getSuperclass();
        }

        DefaultCacheableObjectKeyFactory factory = new DefaultCacheableObjectKeyFactory();
        factory.setCacheRefreshCheck(cacheRefreshIndicator);
        return factory;
    }

    protected Cache createCache(ProceedingJoinPoint pjp, String name) {
        return null;
    }

    /****
     * Private Methods
     ****/

    private Class<?> getObjectAsClass(Object object) {
        if (object instanceof Class<?>)
            return (Class<?>) object;
        if (object instanceof String) {
            try {
                return Class.forName((String) object);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException(e);
            }
        }
        throw new IllegalArgumentException("unable to convert object to class: " + object);
    }

    private CacheableObjectKeyFactory getObjectAsObjectKeyFactory(Object value) {
        if (value instanceof CacheableObjectKeyFactory)
            return (CacheableObjectKeyFactory) value;
        Class<?> classType = getObjectAsClass(value);
        if (!CacheableObjectKeyFactory.class.isAssignableFrom(classType))
            throw new IllegalArgumentException(
                    classType + " is not assignable to " + CacheableObjectKeyFactory.class);
        try {
            return (CacheableObjectKeyFactory) classType.newInstance();
        } catch (Exception ex) {
            throw new IllegalStateException("error instantiating " + classType, ex);
        }
    }

    private Method getMethodForSignature(String methodSignature) {

        int index = methodSignature.lastIndexOf('.');
        if (index == -1)
            throw new IllegalArgumentException(
                    "invalid method signature: expected=package.ClassName.methodName actual=" + methodSignature);

        String className = methodSignature.substring(0, index);
        String methodName = methodSignature.substring(index + 1);
        Class<?> clazz = null;

        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            throw new IllegalStateException(ex);
        }

        List<Method> methods = new ArrayList<Method>();
        for (Method method : clazz.getMethods()) {
            if (method.getName().equals(methodName))
                methods.add(method);
        }

        if (methods.size() == 1)
            return methods.get(0);
        else if (methods.size() == 0)
            throw new IllegalArgumentException("no method found for signature: " + methodSignature);
        else
            throw new IllegalArgumentException("multiple methods found for signature: " + methodSignature);
    }

}