Browse Source

Provide common cache config

Prior to this commit, common cache operation settings had to be
repeated for every operation: cache name(s), custom cache manager
and custom key manager.

This commit introduces the @CacheConfig annotation to bet set at
class-level (either directly or as a meta-annotation). As the cache
name(s) can be rationalized there, the "value" of the various
annotations are no longer mandatory.

CacheAnnotationParser has an API breakage to be able to retrieve
information at class-level.

Issue: SPR-11316
pull/442/merge
Stephane Nicoll 12 years ago
parent
commit
3c28301ded
  1. 46
      spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java
  2. 28
      spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
  3. 62
      spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java
  4. 3
      spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
  5. 3
      spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
  6. 3
      spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
  7. 124
      spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
  8. 6
      spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
  9. 144
      spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java

46
spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java vendored

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
package org.springframework.cache.annotation;
import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
@ -40,6 +39,7 @@ import org.springframework.util.Assert; @@ -40,6 +39,7 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
*/
@SuppressWarnings("serial")
@ -106,29 +106,40 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati @@ -106,29 +106,40 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
@Override
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
return determineCacheOperations(clazz);
protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(clazz);
}
});
}
@Override
protected Collection<CacheOperation> findCacheOperations(Method method) {
return determineCacheOperations(method);
protected Collection<CacheOperation> findCacheOperations(final Method method) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(method);
}
});
}
/**
* Determine the cache operation(s) for the given method or class.
* Determine the cache operation(s) for the given {@link CacheOperationProvider}.
* <p>This implementation delegates to configured
* {@link CacheAnnotationParser}s for parsing known annotations into
* Spring's metadata attribute class.
* <p>Can be overridden to support custom annotations that carry
* caching metadata.
* @param ae the annotated method or class
* @param provider the cache operation provider to use
* @return the configured caching operations, or {@code null} if none found
*/
protected Collection<CacheOperation> determineCacheOperations(AnnotatedElement ae) {
protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
Collection<CacheOperation> ops = null;
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
Collection<CacheOperation> annOps = annotationParser.parseCacheAnnotations(ae);
Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);
if (annOps != null) {
if (ops == null) {
ops = new ArrayList<CacheOperation>();
@ -166,4 +177,19 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati @@ -166,4 +177,19 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
return this.annotationParsers.hashCode();
}
/**
* Callback interface providing {@link CacheOperation} instance(s) based on
* a given {@link CacheAnnotationParser}.
*/
protected interface CacheOperationProvider {
/**
* Returns the {@link CacheOperation} instance(s) provided by the specified parser.
*
* @param parser the parser to use
* @return the cache operations or {@code null} if none is found
*/
Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser);
}
}

28
spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java vendored

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
package org.springframework.cache.annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collection;
import org.springframework.cache.interceptor.CacheOperation;
@ -28,20 +28,34 @@ import org.springframework.cache.interceptor.CacheOperation; @@ -28,20 +28,34 @@ import org.springframework.cache.interceptor.CacheOperation;
* {@link Cacheable}, {@link CachePut} or {@link CacheEvict}.
*
* @author Costin Leau
* @author Stephane Nicoll
* @since 3.1
*/
public interface CacheAnnotationParser {
/**
* Parses the cache definition for the given method or class,
* Parses the cache definition for the given class,
* based on a known annotation type.
* <p>This essentially parses a known cache annotation into Spring's
* metadata attribute class. Returns {@code null} if the method/class
* metadata attribute class. Returns {@code null} if the class
* is not cacheable.
* @param ae the annotated method or class
* @param type the annotated class
* @return CacheOperation the configured caching operation,
* or {@code null} if none was found
* @see AnnotationCacheOperationSource#determineCacheOperations(AnnotatedElement)
* @see AnnotationCacheOperationSource#findCacheOperations(Class)
*/
Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae);
Collection<CacheOperation> parseCacheAnnotations(Class<?> type);
/**
* Parses the cache definition for the given method,
* based on a known annotation type.
* <p>This essentially parses a known cache annotation into Spring's
* metadata attribute class. Returns {@code null} if the method
* is not cacheable.
* @param method the annotated method
* @return CacheOperation the configured caching operation,
* or {@code null} if none was found
* @see AnnotationCacheOperationSource#findCacheOperations(Method)
*/
Collection<CacheOperation> parseCacheAnnotations(Method method);
}

62
spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java vendored

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* Copyright 2002-2014 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Provide a way to share common cache-related settings at class-level.
* <p>When this annotation is present on a given class, it provides a set
* of default settings for any cache operation defined on that class.
*
* @author Stephane Nicoll
* @since 4.1
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
/**
* Name of the default caches to consider for a caching operation defined in the class.
* <p>If none is set at the operation level, these ones are used instead of the default.
* <p>May be used to determine the target cache (or caches), matching the
* qualifier value (or the bean name(s)) of (a) specific bean definition.
*/
String[] cacheNames() default {};
/**
* The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to
* use for the class.
* <p>If none is set at the operation level, this one is used instead of the default.
* <p>The key generator is mutually exclusive with the use of a custom key. When such key is
* defined for the operation, the value of this key generator is ignored.
*/
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use.
* <p>If none is set at the operation level, this one is used instead of the default.
*/
String cacheManager() default "";
}

3
spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java vendored

@ -30,6 +30,7 @@ import java.lang.annotation.Target; @@ -30,6 +30,7 @@ import java.lang.annotation.Target;
* @author Costin Leau
* @author Stephane Nicoll
* @since 3.1
* @see CacheConfig
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ -42,7 +43,7 @@ public @interface CacheEvict { @@ -42,7 +43,7 @@ public @interface CacheEvict {
* <p>May be used to determine the target cache (or caches), matching the qualifier
* value (or the bean name(s)) of (a) specific bean definition.
*/
String[] value();
String[] value() default {};
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.

3
spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java vendored

@ -35,6 +35,7 @@ import org.springframework.cache.Cache; @@ -35,6 +35,7 @@ import org.springframework.cache.Cache;
* @author Phillip Webb
* @author Stephane Nicoll
* @since 3.1
* @see CacheConfig
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ -47,7 +48,7 @@ public @interface CachePut { @@ -47,7 +48,7 @@ public @interface CachePut {
* <p>May be used to determine the target cache (or caches), matching the
* qualifier value (or the bean name(s)) of (a) specific bean definition.
*/
String[] value();
String[] value() default {};
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.

3
spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java vendored

@ -33,6 +33,7 @@ import java.lang.annotation.Target; @@ -33,6 +33,7 @@ import java.lang.annotation.Target;
* @author Phillip Webb
* @author Stephane Nicoll
* @since 3.1
* @see CacheConfig
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ -45,7 +46,7 @@ public @interface Cacheable { @@ -45,7 +46,7 @@ public @interface Cacheable {
* <p>May be used to determine the target cache (or caches), matching the
* qualifier value (or the bean name(s)) of (a) specific bean definition.
*/
String[] value();
String[] value() default {};
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.

124
spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java vendored

@ -19,6 +19,7 @@ package org.springframework.cache.annotation; @@ -19,6 +19,7 @@ package org.springframework.cache.annotation;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
@ -26,6 +27,7 @@ import org.springframework.cache.interceptor.CacheEvictOperation; @@ -26,6 +27,7 @@ import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -44,35 +46,47 @@ import org.springframework.util.StringUtils; @@ -44,35 +46,47 @@ import org.springframework.util.StringUtils;
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
@Override
public Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae) {
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
@Override
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig,
AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
Collection<Cacheable> cacheables = getAnnotations(ae, Cacheable.class);
if (cacheables != null) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cacheable));
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
Collection<CacheEvict> evicts = getAnnotations(ae, CacheEvict.class);
if (evicts != null) {
ops = lazyInit(ops);
for (CacheEvict e : evicts) {
ops.add(parseEvictAnnotation(ae, e));
ops.add(parseEvictAnnotation(ae, cachingConfig, e));
}
}
Collection<CachePut> updates = getAnnotations(ae, CachePut.class);
if (updates != null) {
ops = lazyInit(ops);
for (CachePut p : updates) {
ops.add(parseUpdateAnnotation(ae, p));
ops.add(parseUpdateAnnotation(ae, cachingConfig, p));
}
}
Collection<Caching> caching = getAnnotations(ae, Caching.class);
if (caching != null) {
ops = lazyInit(ops);
for (Caching c : caching) {
ops.addAll(parseCachingAnnotation(ae, c));
ops.addAll(parseCachingAnnotation(ae, cachingConfig, c));
}
}
return ops;
@ -82,7 +96,8 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -82,7 +96,8 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return (ops != null ? ops : new ArrayList<CacheOperation>(1));
}
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable caching) {
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae,
DefaultCacheConfig defaultConfig, Cacheable caching) {
CacheableOperation cuo = new CacheableOperation();
cuo.setCacheNames(caching.value());
cuo.setCondition(caching.condition());
@ -92,11 +107,14 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -92,11 +107,14 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
cuo.setCacheManager(caching.cacheManager());
cuo.setName(ae.toString());
checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
defaultConfig.applyDefault(cuo);
validateCacheOperation(ae, cuo);
return cuo;
}
CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, CacheEvict caching) {
CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae,
DefaultCacheConfig defaultConfig, CacheEvict caching) {
CacheEvictOperation ceo = new CacheEvictOperation();
ceo.setCacheNames(caching.value());
ceo.setCondition(caching.condition());
@ -107,11 +125,14 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -107,11 +125,14 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
ceo.setBeforeInvocation(caching.beforeInvocation());
ceo.setName(ae.toString());
checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
defaultConfig.applyDefault(ceo);
validateCacheOperation(ae, ceo);
return ceo;
}
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut caching) {
CacheOperation parseUpdateAnnotation(AnnotatedElement ae,
DefaultCacheConfig defaultConfig, CachePut caching) {
CachePutOperation cuo = new CachePutOperation();
cuo.setCacheNames(caching.value());
cuo.setCondition(caching.condition());
@ -121,38 +142,56 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -121,38 +142,56 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
cuo.setCacheManager(caching.cacheManager());
cuo.setName(ae.toString());
checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
defaultConfig.applyDefault(cuo);
validateCacheOperation(ae, cuo);
return cuo;
}
Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, Caching caching) {
Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae,
DefaultCacheConfig defaultConfig, Caching caching) {
Collection<CacheOperation> ops = null;
Cacheable[] cacheables = caching.cacheable();
if (!ObjectUtils.isEmpty(cacheables)) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cacheable));
ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
}
}
CacheEvict[] evicts = caching.evict();
if (!ObjectUtils.isEmpty(evicts)) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, evict));
ops.add(parseEvictAnnotation(ae, defaultConfig, evict));
}
}
CachePut[] updates = caching.put();
if (!ObjectUtils.isEmpty(updates)) {
ops = lazyInit(ops);
for (CachePut update : updates) {
ops.add(parseUpdateAnnotation(ae, update));
ops.add(parseUpdateAnnotation(ae, defaultConfig, update));
}
}
return ops;
}
/**
* Provides the {@link DefaultCacheConfig} instance for the specified {@link Class}.
*
* @param target the class-level to handle
* @return the default config (never {@code null})
*/
DefaultCacheConfig getDefaultCacheConfig(Class<?> target) {
final CacheConfig annotation = AnnotationUtils.getAnnotation(target, CacheConfig.class);
if (annotation != null) {
return new DefaultCacheConfig(annotation.cacheManager(),
annotation.keyGenerator(), annotation.cacheNames());
}
return new DefaultCacheConfig();
}
private <T extends Annotation> Collection<T> getAnnotations(AnnotatedElement ae, Class<T> annotationType) {
Collection<T> anns = new ArrayList<T>(2);
@ -173,13 +212,27 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -173,13 +212,27 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return (anns.isEmpty() ? null : anns);
}
private void checkKeySourceConsistency(AnnotatedElement ae, String key, String keyGenerator) {
if (StringUtils.hasText(key) && StringUtils.hasText(keyGenerator)) {
/**
* Validates the specified {@link CacheOperation}.
* <p>Throws an {@link IllegalStateException} if the state of the operation is
* invalid. As there might be multiple sources for default values, this ensure
* that the operation is in a proper state before being returned.
*
* @param ae the annotated element of the cache operation
* @param operation the {@link CacheOperation} to validate
*/
private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache annotation configuration on '"
+ ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
if (operation.getCacheNames().isEmpty()) {
throw new IllegalStateException("No cache names could be detected on '"
+ ae.toString()+ "'. Make sure to set the value parameter on the annotation or" +
"declare a @CacheConfig at the class-level with the default cache name(s) to use.");
}
}
@Override
@ -192,4 +245,41 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria @@ -192,4 +245,41 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return SpringCacheAnnotationParser.class.hashCode();
}
/**
* Provides default settings for a given set of cache operations.
*/
static class DefaultCacheConfig {
private final String cacheManager;
private final String keyGenerator;
private final String[] cacheNames;
private DefaultCacheConfig(String cacheManager, String keyGenerator, String[] cacheNames) {
this.cacheManager = cacheManager;
this.keyGenerator = keyGenerator;
this.cacheNames = cacheNames;
}
public DefaultCacheConfig() {
this(null, null, null);
}
/**
* Apply the defaults to the specified {@link CacheOperation}.
*
* @param operation the operation to update
*/
public void applyDefault(CacheOperation operation) {
if (!StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(cacheManager)) {
operation.setCacheManager(cacheManager);
}
if (!StringUtils.hasText(operation.getKey()) && !StringUtils.hasText(operation.getKeyGenerator())
&& StringUtils.hasText(keyGenerator)) {
operation.setKeyGenerator(keyGenerator);
}
if (operation.getCacheNames().isEmpty() && cacheNames != null) {
operation.setCacheNames(cacheNames);
}
}
}
}

6
spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java vendored

@ -73,10 +73,10 @@ public abstract class CacheOperation { @@ -73,10 +73,10 @@ public abstract class CacheOperation {
}
public void setCacheNames(String[] cacheNames) {
Assert.notEmpty(cacheNames);
this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
for (String string : cacheNames) {
this.cacheNames.add(string);
for (String cacheName : cacheNames) {
Assert.hasText(cacheName, "Cache name must be set if specified.");
this.cacheNames.add(cacheName);
}
}

144
spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java vendored

@ -26,7 +26,9 @@ import java.lang.reflect.Method; @@ -26,7 +26,9 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CacheableOperation;
@ -38,24 +40,33 @@ import org.springframework.util.ReflectionUtils; @@ -38,24 +40,33 @@ import org.springframework.util.ReflectionUtils;
*/
public class AnnotationCacheOperationSourceTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
private Collection<CacheOperation> getOps(String name) {
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name);
return source.getCacheOperations(method, AnnotatedClass.class);
private Collection<CacheOperation> getOps(Class<?> target, String name,
int expectedNumberOfOperations) {
Collection<CacheOperation> result = getOps(target, name);
assertEquals("Wrong number of operation(s) for '"+name+"'",
expectedNumberOfOperations, result.size());
return result;
}
private Collection<CacheOperation> getOps(Class<?> target, String name) {
Method method = ReflectionUtils.findMethod(target, name);
return source.getCacheOperations(method, target);
}
@Test
public void testSingularAnnotation() throws Exception {
Collection<CacheOperation> ops = getOps("singular");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singular", 1);
assertTrue(ops.iterator().next() instanceof CacheableOperation);
}
@Test
public void testMultipleAnnotation() throws Exception {
Collection<CacheOperation> ops = getOps("multiple");
assertEquals(2, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multiple", 2);
Iterator<CacheOperation> it = ops.iterator();
assertTrue(it.next() instanceof CacheableOperation);
assertTrue(it.next() instanceof CacheEvictOperation);
@ -63,8 +74,7 @@ public class AnnotationCacheOperationSourceTests { @@ -63,8 +74,7 @@ public class AnnotationCacheOperationSourceTests {
@Test
public void testCaching() throws Exception {
Collection<CacheOperation> ops = getOps("caching");
assertEquals(2, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "caching", 2);
Iterator<CacheOperation> it = ops.iterator();
assertTrue(it.next() instanceof CacheableOperation);
assertTrue(it.next() instanceof CacheEvictOperation);
@ -72,15 +82,13 @@ public class AnnotationCacheOperationSourceTests { @@ -72,15 +82,13 @@ public class AnnotationCacheOperationSourceTests {
@Test
public void testSingularStereotype() throws Exception {
Collection<CacheOperation> ops = getOps("singleStereotype");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleStereotype", 1);
assertTrue(ops.iterator().next() instanceof CacheEvictOperation);
}
@Test
public void testMultipleStereotypes() throws Exception {
Collection<CacheOperation> ops = getOps("multipleStereotype");
assertEquals(3, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleStereotype", 3);
Iterator<CacheOperation> it = ops.iterator();
assertTrue(it.next() instanceof CacheableOperation);
CacheOperation next = it.next();
@ -93,16 +101,14 @@ public class AnnotationCacheOperationSourceTests { @@ -93,16 +101,14 @@ public class AnnotationCacheOperationSourceTests {
@Test
public void testCustomKeyGenerator() {
Collection<CacheOperation> ops = getOps("customKeyGenerator");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGenerator", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
}
@Test
public void testCustomKeyGeneratorInherited() {
Collection<CacheOperation> ops = getOps("customKeyGeneratorInherited");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGeneratorInherited", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
}
@ -110,7 +116,7 @@ public class AnnotationCacheOperationSourceTests { @@ -110,7 +116,7 @@ public class AnnotationCacheOperationSourceTests {
@Test
public void testKeyAndKeyGeneratorCannotBeSetTogether() {
try {
getOps("invalidKeyAndKeyGeneratorSet");
getOps(AnnotatedClass.class, "invalidKeyAndKeyGeneratorSet");
fail("Should have failed to parse @Cacheable annotation");
} catch (IllegalStateException e) {
// expected
@ -119,20 +125,70 @@ public class AnnotationCacheOperationSourceTests { @@ -119,20 +125,70 @@ public class AnnotationCacheOperationSourceTests {
@Test
public void testCustomCacheManager() {
Collection<CacheOperation> ops = getOps("customCacheManager");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheManager", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertEquals("Custom cache manager not set", "custom", cacheOperation.getCacheManager());
}
@Test
public void testCustomCacheManagerInherited() {
Collection<CacheOperation> ops = getOps("customCacheManagerInherited");
assertEquals(1, ops.size());
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheManagerInherited", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertEquals("Custom cache manager not set", "custom", cacheOperation.getCacheManager());
}
@Test
public void fullClassLevelWithCustomKeyManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "custom", "classCacheName");
}
@Test
public void fullClassLevelWithCustomCacheManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "custom", "classKeyGenerator", "classCacheName");
}
@Test
public void fullClassLevelWithCustomCacheName() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheName", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "classKeyGenerator", "custom");
}
@Test
public void validateAtLeastOneCacheNameMustBeSet() {
thrown.expect(IllegalStateException.class);
getOps(AnnotatedClass.class, "noCacheNameSpecified");
}
@Test
public void customClassLevelWithCustomCacheName() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithCustomDefault.class, "methodLevelCacheName", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "classKeyGenerator", "custom");
}
@Test
public void severalCacheConfigUseClosest() {
Collection<CacheOperation> ops = getOps(MultipleCacheConfig.class, "multipleCacheConfig");
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "", "", "myCache");
}
private void assertSharedConfig(CacheOperation actual, String cacheManager,
String keyGenerator, String... cacheNames) {
assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager());
assertEquals("Wrong key manager", keyGenerator, actual.getKeyGenerator());
for (String cacheName : cacheNames) {
assertTrue("Cache '"+cacheName+"' not found (got "+actual.getCacheNames(),
actual.getCacheNames().contains(cacheName));
}
assertEquals("Wrong number of cache name(s)", cacheNames.length, actual.getCacheNames().size());
}
private static class AnnotatedClass {
@Cacheable("test")
public void singular() {
@ -180,6 +236,44 @@ public class AnnotationCacheOperationSourceTests { @@ -180,6 +236,44 @@ public class AnnotationCacheOperationSourceTests {
@CacheableFooCustomCacheManager
public void customCacheManagerInherited() {
}
@Cacheable // cache name can be inherited from CacheConfig. There's none here
public void noCacheNameSpecified() {
}
}
@CacheConfig(cacheNames = "classCacheName",
cacheManager = "classCacheManager", keyGenerator = "classKeyGenerator")
private static class AnnotatedClassWithFullDefault {
@Cacheable(keyGenerator = "custom")
public void methodLevelKeyGenerator() {
}
@Cacheable(cacheManager = "custom")
public void methodLevelCacheManager() {
}
@Cacheable("custom")
public void methodLevelCacheName() {
}
}
@CacheConfigFoo
private static class AnnotatedClassWithCustomDefault {
@Cacheable("custom")
public void methodLevelCacheName() {
}
}
@CacheConfigFoo
@CacheConfig(cacheNames = "myCache") // multiple sources
private static class MultipleCacheConfig {
@Cacheable
public void multipleCacheConfig() {
}
}
@Retention(RetentionPolicy.RUNTIME)
@ -212,4 +306,10 @@ public class AnnotationCacheOperationSourceTests { @@ -212,4 +306,10 @@ public class AnnotationCacheOperationSourceTests {
@CacheEvict(value = "bar")
public @interface EvictBar {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@CacheConfig(cacheManager = "classCacheManager", keyGenerator = "classKeyGenerator")
public @interface CacheConfigFoo {
}
}
Loading…
Cancel
Save