com.googlecode.ehcache.annotations.impl.CacheAttributeSourceImpl Maven / Gradle / Ivy
Show all versions of ehcache-spring-annotations Show documentation
/**
* Copyright 2010 Nicholas Blair, Eric Dalquist
*
* 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 com.googlecode.ehcache.annotations.impl;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import net.sf.ehcache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.googlecode.ehcache.annotations.AdviceType;
import com.googlecode.ehcache.annotations.CacheAttributeSource;
import com.googlecode.ehcache.annotations.Cacheable;
import com.googlecode.ehcache.annotations.CacheableAttribute;
import com.googlecode.ehcache.annotations.KeyGenerator;
import com.googlecode.ehcache.annotations.MethodAttribute;
import com.googlecode.ehcache.annotations.ParameterMask;
import com.googlecode.ehcache.annotations.PartialCacheKey;
import com.googlecode.ehcache.annotations.Property;
import com.googlecode.ehcache.annotations.ResolverFactory;
import com.googlecode.ehcache.annotations.SelfPopulatingCacheScope;
import com.googlecode.ehcache.annotations.TriggersRemove;
import com.googlecode.ehcache.annotations.TriggersRemoveAttribute;
import com.googlecode.ehcache.annotations.key.CacheKeyGenerator;
import com.googlecode.ehcache.annotations.key.ReflectionHelper;
import com.googlecode.ehcache.annotations.key.ReflectionHelperAware;
import com.googlecode.ehcache.annotations.resolver.CacheResolverFactory;
import com.googlecode.ehcache.annotations.resolver.CacheableCacheResolver;
import com.googlecode.ehcache.annotations.resolver.DefaultCacheResolverFactory;
import com.googlecode.ehcache.annotations.resolver.TriggersRemoveCacheResolver;
/**
* Provides logic for determining if a class + method are advised and to then setup the
* data needed for the advice.
*
* @author Eric Dalquist
* @version $Revision: 625 $
*/
public class CacheAttributeSourceImpl implements CacheAttributeSource, BeanFactoryAware, InitializingBean {
/**
* Logger available to subclasses.
*/
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* Caches for advice data
*/
private final MultiKeyConcurrentMap, Method, Boolean> ingoredMethods = new MultiKeyConcurrentMap, Method, Boolean>();
private final MultiKeyConcurrentMap, Method, MethodAttribute> attributesCache = new MultiKeyConcurrentMap, Method, MethodAttribute>();
private DefaultListableBeanFactory childBeanFactory;
private CacheManager cacheManager;
private BeanFactory beanFactory;
private SelfPopulatingCacheScope selfPopulatingCacheScope = SelfPopulatingCacheScope.SHARED;
private boolean createCaches = false;
private CacheKeyGenerator extends Serializable> defaultCacheKeyGenerator;
private ReflectionHelper reflectionHelper;
private DefaultCacheResolverFactory cacheResolverFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
this.childBeanFactory = new DefaultListableBeanFactory(this.beanFactory);
}
public void setCreateCaches(boolean createCaches) {
this.createCaches = createCaches;
if (this.cacheResolverFactory != null) {
this.cacheResolverFactory.setCreateCaches(this.createCaches);
}
}
public void setDefaultCacheKeyGenerator(CacheKeyGenerator extends Serializable> defaultCacheKeyGenerator) {
this.defaultCacheKeyGenerator = defaultCacheKeyGenerator;
}
public void setSelfPopulatingCacheScope(SelfPopulatingCacheScope selfPopulatingCacheScope) {
this.selfPopulatingCacheScope = selfPopulatingCacheScope;
if (this.cacheResolverFactory != null) {
this.cacheResolverFactory.setSelfPopulatingCacheScope(this.selfPopulatingCacheScope);
}
}
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public void setReflectionHelper(ReflectionHelper reflectionHelper) {
this.reflectionHelper = reflectionHelper;
}
public void afterPropertiesSet() throws Exception {
this.cacheResolverFactory = new DefaultCacheResolverFactory(this.getCacheManager());
this.cacheResolverFactory.setCreateCaches(this.createCaches);
this.cacheResolverFactory.setSelfPopulatingCacheScope(this.selfPopulatingCacheScope);
}
/* (non-Javadoc)
* @see com.googlecode.ehcache.annotations.CacheAttributeSource#getAdviceType(java.lang.reflect.Method, java.lang.Class)
*/
public AdviceType getAdviceType(Method method, Class> targetClass) {
final MethodAttribute methodAttribute = this.getMethodAttribute(method, targetClass);
if (methodAttribute != null) {
return methodAttribute.getAdviceType();
}
return AdviceType.NONE;
}
/* (non-Javadoc)
* @see com.googlecode.ehcache.annotations.CacheAttributeSource#getCacheableAttribute(java.lang.reflect.Method, java.lang.Class)
*/
public CacheableAttribute getCacheableAttribute(Method method, Class> targetClass) {
final MethodAttribute methodAttribute = this.getMethodAttribute(method, targetClass);
if (methodAttribute != null && AdviceType.CACHE == methodAttribute.getAdviceType()) {
return (CacheableAttribute)methodAttribute;
}
return null;
}
/* (non-Javadoc)
* @see com.googlecode.ehcache.annotations.CacheAttributeSource#getTriggersRemoveAttribute(java.lang.reflect.Method, java.lang.Class)
*/
public TriggersRemoveAttribute getTriggersRemoveAttribute(Method method, Class> targetClass) {
final MethodAttribute methodAttribute = this.getMethodAttribute(method, targetClass);
if (methodAttribute != null && AdviceType.REMOVE == methodAttribute.getAdviceType()) {
return (TriggersRemoveAttribute)methodAttribute;
}
return null;
}
private MethodAttribute getMethodAttribute(final Method method, final Class> targetClass) {
//Check if the method has already been inspected and should be ignored
if (this.ingoredMethods.containsKey(targetClass, method)) {
return null;
}
//Check the cache if the method has already had its advise attributes created
final MethodAttribute attributes = this.attributesCache.get(targetClass, method);
if (attributes != null) {
return attributes;
}
// We need to work it out.
final MethodAttribute att = this.computeMethodAttribute(method, targetClass);
// Put it in the cache.
if (att == null) {
this.ingoredMethods.put(targetClass, method, Boolean.TRUE);
}
else {
this.logger.debug("Adding {} advised method '{}' with attribute: {}", new Object[] { att.getAdviceType(), method.getName(), att });
final MethodAttribute existing = this.attributesCache.putIfAbsent(targetClass, method, att);
if (existing != null) {
return existing;
}
}
return att;
}
/**
* Should only public methods be allowed to be advised?
* The default implementation returns false
.
*/
protected boolean allowPublicMethodsOnly() {
return false;
}
/**
* @return The injected CacheManager.
*/
protected CacheManager getCacheManager() {
return this.cacheManager;
}
private MethodAttribute computeMethodAttribute(Method method, Class> targetClass) {
// Don't allow no-public methods as required.
if (this.allowPublicMethodsOnly() && !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.
MethodAttribute att = this.findMethodAttribute(specificMethod);
if (att != null) {
return att;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
att = this.findMethodAttribute(method);
if (att != null) {
return att;
}
}
return null;
}
/**
* Determine if the specified {@link AnnotatedElement} is annotated with either {@link Cacheable} or {@link TriggersRemove}
*
* @param method The element to inspect
* @return The advice attributes about the element, null if the element is not advised
*/
private MethodAttribute findMethodAttribute(Method method) {
Cacheable cacheableAnnotation = method.getAnnotation(Cacheable.class);
if (cacheableAnnotation != null) {
final ParameterMask parameterMask = this.parsePartialCacheKeyAnnotations(method);
return this.parseCacheableAnnotation(cacheableAnnotation, method, parameterMask);
}
TriggersRemove triggersRemove = method.getAnnotation(TriggersRemove.class);
if (triggersRemove != null) {
final ParameterMask parameterMask = this.parsePartialCacheKeyAnnotations(method);
return this.parseTriggersRemoveAnnotation(triggersRemove, method, parameterMask);
}
for (final Annotation metaAnn : method.getAnnotations()) {
final Class extends Annotation> annotationType = metaAnn.annotationType();
cacheableAnnotation = annotationType.getAnnotation(Cacheable.class);
if (cacheableAnnotation != null) {
final ParameterMask parameterMask = this.parsePartialCacheKeyAnnotations(method);
return this.parseCacheableAnnotation(cacheableAnnotation, method, parameterMask);
}
triggersRemove = annotationType.getAnnotation(TriggersRemove.class);
if (triggersRemove != null) {
final ParameterMask parameterMask = this.parsePartialCacheKeyAnnotations(method);
return this.parseTriggersRemoveAnnotation(triggersRemove, method, parameterMask);
}
}
return null;
}
/**
* Parse the parameters annotated with {@link PartialCacheKey}.
*
* @param method The method who's parameters should be checked.
* @return The parameter mask for the annotated parameters.
*/
protected ParameterMask parsePartialCacheKeyAnnotations(Method method) {
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
final boolean[] mask = new boolean[parameterAnnotations.length];
for (int i = 0; i < parameterAnnotations.length; i++) {
final Annotation[] annotations = parameterAnnotations[i];
for (final Annotation annotation : annotations) {
final Class extends Annotation> annotationType = annotation.annotationType();
if (PartialCacheKey.class.equals(annotationType)) {
mask[i] = true;
break;
}
}
}
return ParameterMask.create(mask);
}
/**
* Construct a {@link CacheableAttribute} from a {@link Cacheable} annotation.
*
* @param ann The annotation to build the attributes from
* @return The constructed cacheable advise attributes
*/
protected CacheableAttribute parseCacheableAnnotation(Cacheable ann, Method method, ParameterMask parameterMask) {
final String resolverFactoryName = ann.resolverFactoryName();
final ResolverFactory resolverFactory = ann.resolverFactory();
final CacheResolverFactory cacheResolverFactory = this.getCacheResolverFactory(resolverFactoryName, resolverFactory);
final CacheableCacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(ann, method);
final String keyGeneratorName = ann.keyGeneratorName();
final KeyGenerator keyGenerator = ann.keyGenerator();
final CacheKeyGenerator extends Serializable> cacheKeyGenerator = this.getCacheKeyGenerator(keyGeneratorName, keyGenerator);
return new CacheableAttributeImpl(cacheResolver, cacheKeyGenerator, parameterMask);
}
/**
* Construct a {@link TriggersRemoveAttribute} from a {@link TriggersRemove} annotation.
*
* @param ann The annotation to build the attributes from
* @return The constructed triggers remove advise attributes
*/
protected TriggersRemoveAttribute parseTriggersRemoveAnnotation(TriggersRemove ann, Method method, ParameterMask parameterMask) {
final String resolverFactoryName = ann.resolverFactoryName();
final ResolverFactory resolverFactory = ann.resolverFactory();
final CacheResolverFactory cacheResolverFactory = this.getCacheResolverFactory(resolverFactoryName, resolverFactory);
final TriggersRemoveCacheResolver cacheResolver = cacheResolverFactory.getCacheResolver(ann, method);
final String keyGeneratorName = ann.keyGeneratorName();
final KeyGenerator keyGenerator = ann.keyGenerator();
final CacheKeyGenerator extends Serializable> cacheKeyGenerator = this.getCacheKeyGenerator(keyGeneratorName, keyGenerator);
return new TriggersRemoveAttributeImpl(cacheResolver, cacheKeyGenerator, parameterMask, ann.removeAll(), ann.when());
}
/**
* Get the {@link CacheResolverFactory} by name. Returning a default resolver factory if the name is empty or null
*
* @param resolverFactoryName Name of the resolver factory to retrieve
* @return The named generator or the default generator if the name was empty or null
*/
protected final CacheResolverFactory getCacheResolverFactory(String resolverFactoryName, ResolverFactory resolverFactory) {
String cacheResolverClassName = resolverFactory.name();
if (cacheResolverClassName.length() > 0) {
return this.getOrCreateCacheResolverFactory(resolverFactory);
}
if (StringUtils.hasLength(resolverFactoryName)) {
return this.beanFactory.getBean(resolverFactoryName, CacheResolverFactory.class);
}
return this.cacheResolverFactory;
}
/**
* Get the {@link CacheKeyGenerator} by name. Returning a default generator if the name is empty or null
*
* @param keyGeneratorName Name of the generator to retrieve
* @return The named generator or the default generator if the name was empty or null
*/
@SuppressWarnings("unchecked")
protected final CacheKeyGenerator extends Serializable> getCacheKeyGenerator(String keyGeneratorName, KeyGenerator keyGenerator) {
String keyGeneratorClassName = keyGenerator.name();
if (keyGeneratorClassName.length() > 0) {
return this.getOrCreateCacheKeyGenerator(keyGenerator);
}
final CacheKeyGenerator extends Serializable> cacheKeyGenerator;
if (StringUtils.hasLength(keyGeneratorName)) {
cacheKeyGenerator = this.beanFactory.getBean(keyGeneratorName, CacheKeyGenerator.class);
} else {
cacheKeyGenerator = this.defaultCacheKeyGenerator;
}
return cacheKeyGenerator;
}
@SuppressWarnings("unchecked")
protected final CacheKeyGenerator extends Serializable> getOrCreateCacheKeyGenerator(KeyGenerator keyGenerator) {
String keyGeneratorClassName = keyGenerator.name();
if (!keyGeneratorClassName.contains(".")) {
keyGeneratorClassName = "com.googlecode.ehcache.annotations.key." + keyGeneratorClassName;
}
return this.getOrCreateChildBean(CacheKeyGenerator.class, keyGeneratorClassName, keyGenerator.properties());
}
protected final CacheResolverFactory getOrCreateCacheResolverFactory(ResolverFactory resolverFactory) {
String resolverFactoryClassName = resolverFactory.name();
if (!resolverFactoryClassName.contains(".")) {
resolverFactoryClassName = "com.googlecode.ehcache.annotations.resolver." + resolverFactoryClassName;
}
return this.getOrCreateChildBean(CacheResolverFactory.class, resolverFactoryClassName, resolverFactory.properties());
}
protected final T getOrCreateChildBean(Class beanType, String beanClass, Property[] properties) {
final StringBuilder beanNameBuilder = new StringBuilder();
beanNameBuilder.append(beanClass);
final MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
//Sort the properties array first so bean name generation is always consistent
Arrays.sort(properties, PropertyComparator.INSTANCE);
for (Property property : properties) {
final String name = property.name();
final String value = property.value();
final String ref = property.ref();
beanNameBuilder.append("[").append(name).append(",").append(value).append(",").append(ref).append("]");
if (value.length() > 0) {
if (ref.length() > 0) {
throw new IllegalArgumentException("Only one of value or ref must be specified no both on Property with name: " + name);
}
mutablePropertyValues.addPropertyValue(name, value);
}
else if (ref.length() > 0) {
mutablePropertyValues.addPropertyValue(name, new RuntimeBeanReference(ref));
}
else {
throw new IllegalArgumentException("Either value or ref must be specified on Property with name: " + name);
}
}
final String beanName = beanNameBuilder.toString();
//See if the bean is already registered using the compiled bean name, if so just use that instance
if (this.childBeanFactory.containsBean(beanName)) {
return this.childBeanFactory.getBean(beanName, beanType);
}
//Create and register the bean if it didn't already exist
final AbstractBeanDefinition beanDefinition;
try {
beanDefinition = BeanDefinitionReaderUtils.createBeanDefinition(null, beanClass, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException e) {
throw new BeanCreationException("Could not find class '" + beanClass + "' to create " + beanType + " from", e);
}
if (ReflectionHelperAware.class.isAssignableFrom(beanDefinition.getBeanClass())) {
mutablePropertyValues.addPropertyValue("reflectionHelper", this.reflectionHelper);
}
beanDefinition.setPropertyValues(mutablePropertyValues);
this.childBeanFactory.registerBeanDefinition(beanName, beanDefinition);
return this.childBeanFactory.getBean(beanName, beanType);
}
}