diff --git a/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd b/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd new file mode 100644 index 00000000000..076fdbc91ce --- /dev/null +++ b/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java index 0851d702656..9aab40515bf 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java @@ -34,7 +34,7 @@ import org.springframework.util.Assert; * Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource} * interface for working with caching metadata in JDK 1.5+ annotation format. * - *

This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CacheUpdate} and {@link CacheEvict} + *

This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CachePut} and {@link CacheEvict} * annotations and exposes corresponding caching operation definition to Spring's cache infrastructure. * This class may also serve as base class for a custom CacheOperationSource. * diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java index 44117112c71..d5a54abe9d9 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java @@ -26,7 +26,7 @@ import org.springframework.cache.interceptor.CacheOperation; * Strategy interface for parsing known caching annotation types. * {@link AnnotationCacheDefinitionSource} delegates to such * parsers for supporting specific annotation types such as Spring's own - * {@link Cacheable}, {@link CacheUpdate} or {@link CacheEvict}. + * {@link Cacheable}, {@link CachePut} or {@link CacheEvict}. * * @author Costin Leau * @since 3.1 diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinitions.java similarity index 80% rename from org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java rename to org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinitions.java index 7151a3470d1..0aa4947f83b 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinitions.java @@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Group annotation for multiple cacheable annotations (of different or the same type). + * Group annotation for multiple cache annotations (of different or the same type). * * @author Costin Leau * @since 3.1 @@ -33,11 +33,11 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented -public @interface CacheDefinition { +public @interface CacheDefinitions { - Cacheable[] cacheables(); + Cacheable[] cacheable() default {}; - CacheUpdate[] updates(); + CachePut[] put() default {}; - CacheEvict[] evicts(); + CacheEvict[] evict() default {}; } diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachePut.java similarity index 85% rename from org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java rename to org.springframework.context/src/main/java/org/springframework/cache/annotation/CachePut.java index fee6fb19c7f..dbeb6aeef6f 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CachePut.java @@ -23,11 +23,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.cache.Cache; + /** * * Annotation indicating that a method (or all methods on a class) trigger(s) - * a cache update operation. As opposed to {@link Cacheable} annotation, this annotation - * does not cause the target method to be skipped in case of a cache hit - rather it + * a {@link Cache#put(Object, Object)} operation. As opposed to {@link Cacheable} annotation, + * this annotation does not cause the target method to be skipped - rather it * always causes the method to be invoked and its result to be placed into the cache. * * @author Costin Leau @@ -37,7 +39,7 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented -public @interface CacheUpdate { +public @interface CachePut { /** * Name of the caches in which the update takes place. diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java index 0ec6c4b5c49..4cffa9f6b28 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java @@ -23,13 +23,13 @@ import java.util.Collection; import org.springframework.cache.interceptor.CacheEvictOperation; import org.springframework.cache.interceptor.CacheOperation; -import org.springframework.cache.interceptor.CacheUpdateOperation; +import org.springframework.cache.interceptor.CachePutOperation; import org.springframework.cache.interceptor.CacheableOperation; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ObjectUtils; /** - * Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CacheUpdate} annotations. + * Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CachePut} annotations. * * @author Costin Leau * @author Juergen Hoeller @@ -51,12 +51,12 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria ops = lazyInit(ops); ops.add(parseEvictAnnotation(ae, evict)); } - CacheUpdate update = AnnotationUtils.getAnnotation(ae, CacheUpdate.class); + CachePut update = AnnotationUtils.getAnnotation(ae, CachePut.class); if (update != null) { ops = lazyInit(ops); ops.add(parseUpdateAnnotation(ae, update)); } - CacheDefinition definition = AnnotationUtils.getAnnotation(ae, CacheDefinition.class); + CacheDefinitions definition = AnnotationUtils.getAnnotation(ae, CacheDefinitions.class); if (definition != null) { ops = lazyInit(ops); ops.addAll(parseDefinitionAnnotation(ae, definition)); @@ -87,8 +87,8 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria return ceo; } - CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CacheUpdate ann) { - CacheUpdateOperation cuo = new CacheUpdateOperation(); + CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut ann) { + CachePutOperation cuo = new CachePutOperation(); cuo.setCacheNames(ann.value()); cuo.setCondition(ann.condition()); cuo.setKey(ann.key()); @@ -96,27 +96,27 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria return cuo; } - Collection parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinition ann) { + Collection parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinitions ann) { Collection ops = null; - Cacheable[] cacheables = ann.cacheables(); + Cacheable[] cacheables = ann.cacheable(); if (!ObjectUtils.isEmpty(cacheables)) { ops = lazyInit(ops); for (Cacheable cacheable : cacheables) { ops.add(parseCacheableAnnotation(ae, cacheable)); } } - CacheEvict[] evicts = ann.evicts(); + CacheEvict[] evicts = ann.evict(); if (!ObjectUtils.isEmpty(evicts)) { ops = lazyInit(ops); for (CacheEvict evict : evicts) { ops.add(parseEvictAnnotation(ae, evict)); } } - CacheUpdate[] updates = ann.updates(); + CachePut[] updates = ann.put(); if (!ObjectUtils.isEmpty(updates)) { ops = lazyInit(ops); - for (CacheUpdate update : updates) { + for (CachePut update : updates) { ops.add(parseUpdateAnnotation(ae, update)); } } diff --git a/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java index 473442ceb67..5150169cc19 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java @@ -16,6 +16,8 @@ package org.springframework.cache.config; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.springframework.beans.factory.config.TypedStringValue; @@ -30,6 +32,7 @@ import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.interceptor.CacheEvictOperation; import org.springframework.cache.interceptor.CacheInterceptor; import org.springframework.cache.interceptor.CacheOperation; +import org.springframework.cache.interceptor.CachePutOperation; import org.springframework.cache.interceptor.CacheableOperation; import org.springframework.cache.interceptor.NameMatchCacheOperationSource; import org.springframework.util.StringUtils; @@ -52,13 +55,14 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { */ private static class Props { - private String key, condition; + private String key, condition, method; private String[] caches = null; Props(Element root) { String defaultCache = root.getAttribute("cache"); key = root.getAttribute("key"); condition = root.getAttribute("condition"); + method = root.getAttribute(METHOD_ATTRIBUTE); if (StringUtils.hasText(defaultCache)) { caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim()); @@ -95,10 +99,24 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { return op; } + + String merge(Element element, ReaderContext readerCtx) { + String m = element.getAttribute(METHOD_ATTRIBUTE); + + if (StringUtils.hasText(m)) { + return m.trim(); + } + if (StringUtils.hasText(method)) { + return method; + } + readerCtx.error("No method specified for " + element.getNodeName(), element); + return null; + } } private static final String CACHEABLE_ELEMENT = "cacheable"; private static final String CACHE_EVICT_ELEMENT = "cache-evict"; + private static final String CACHE_PUT_ELEMENT = "cache-put"; private static final String METHOD_ATTRIBUTE = "method"; private static final String DEFS_ELEMENT = "definitions"; @@ -139,34 +157,60 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser { Props prop = new Props(definition); // add cacheable first - ManagedMap cacheOpeMap = new ManagedMap(); - cacheOpeMap.setSource(parserContext.extractSource(definition)); + ManagedMap> cacheOpMap = new ManagedMap>(); + cacheOpMap.setSource(parserContext.extractSource(definition)); - List updateCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT); + List cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT); - for (Element opElement : updateCacheMethods) { - String name = opElement.getAttribute(METHOD_ATTRIBUTE); + for (Element opElement : cacheableCacheMethods) { + String name = prop.merge(opElement, parserContext.getReaderContext()); TypedStringValue nameHolder = new TypedStringValue(name); nameHolder.setSource(parserContext.extractSource(opElement)); CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation()); - cacheOpeMap.put(nameHolder, op); + Collection col = cacheOpMap.get(nameHolder); + if (col == null) { + col = new ArrayList(2); + cacheOpMap.put(nameHolder, col); + } + col.add(op); } List evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT); for (Element opElement : evictCacheMethods) { - String name = opElement.getAttribute(METHOD_ATTRIBUTE); + String name = prop.merge(opElement, parserContext.getReaderContext()); TypedStringValue nameHolder = new TypedStringValue(name); nameHolder.setSource(parserContext.extractSource(opElement)); CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation()); - cacheOpeMap.put(nameHolder, op); + Collection col = cacheOpMap.get(nameHolder); + if (col == null) { + col = new ArrayList(2); + cacheOpMap.put(nameHolder, col); + } + col.add(op); + } + + List putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT); + + for (Element opElement : putCacheMethods) { + String name = prop.merge(opElement, parserContext.getReaderContext()); + TypedStringValue nameHolder = new TypedStringValue(name); + nameHolder.setSource(parserContext.extractSource(opElement)); + CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation()); + + Collection col = cacheOpMap.get(nameHolder); + if (col == null) { + col = new ArrayList(2); + cacheOpMap.put(nameHolder, col); + } + col.add(op); } RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class); attributeSourceDefinition.setSource(parserContext.extractSource(definition)); - attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpeMap); + attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap); return attributeSourceDefinition; } } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 6c9f88fff1f..921c8528761 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -403,7 +403,7 @@ public abstract class CacheAspectSupport implements InitializingBean { evicts.add(opContext); } - if (cacheOperation instanceof CacheUpdateOperation) { + if (cacheOperation instanceof CachePutOperation) { updates.add(opContext); } } diff --git a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheOperationEditor.java b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheOperationEditor.java deleted file mode 100644 index 97da30b2ad0..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheOperationEditor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 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.beans.PropertyEditorSupport; - -import org.springframework.util.StringUtils; - -/** - * PropertyEditor for {@link CacheOperation} objects. Accepts a String of form - *

action,cache,key,condition - *

where only action and cache are required. Available definitions for action are - * cacheable and evict. - * When specifying multiple caches, use ; as a separator - * - * A typical example would be: - *

cacheable, orders;books, #p0 - * - *

The tokens need to be specified in the order above. - * - * @author Costin Leau - * - * @see org.springframework.transaction.TransactionAttributeEditor - * @see org.springframework.core.Constants - */ -public class CacheOperationEditor extends PropertyEditorSupport { - - /** - * Format is action, cache, key, condition. - * Null or the empty string means that the method is non cacheable. - * @see java.beans.PropertyEditor#setAsText(java.lang.String) - */ - @Override - public void setAsText(String text) throws IllegalArgumentException { - if (StringUtils.hasLength(text)) { - // tokenize it with "," - String[] tokens = StringUtils.commaDelimitedListToStringArray(text); - if (tokens.length < 2) { - throw new IllegalArgumentException( - "too little arguments found, at least the cache action and cache name are required"); - } - - CacheOperation op; - - if ("cacheable".contains(tokens[0])) { - op = new CacheableOperation(); - } - - else if ("evict".contains(tokens[0])) { - op = new CacheEvictOperation(); - } else { - throw new IllegalArgumentException("Invalid cache action specified " + tokens[0]); - } - - op.setCacheNames(StringUtils.delimitedListToStringArray(tokens[1], ";")); - - if (tokens.length > 2) { - op.setKey(tokens[2]); - } - - if (tokens.length > 3) { - op.setCondition(tokens[3]); - } - - setValue(op); - } else { - setValue(null); - } - } -} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheUpdateOperation.java b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java similarity index 84% rename from org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheUpdateOperation.java rename to org.springframework.context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java index a2cb6c93ea9..5a54b72b680 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheUpdateOperation.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java @@ -17,11 +17,11 @@ package org.springframework.cache.interceptor; /** - * Class describing a cache 'update' operation. + * Class describing a cache 'put' operation. * * @author Costin Leau * @since 3.1 */ -public class CacheUpdateOperation extends CacheOperation { +public class CachePutOperation extends CacheOperation { } diff --git a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java index aae17502ec6..f56c04fa4a0 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/NameMatchCacheOperationSource.java @@ -19,11 +19,8 @@ package org.springframework.cache.interceptor; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -54,63 +51,44 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri * @see CacheOperation * @see CacheOperationEditor */ - public void setNameMap(Map nameMap) { - for (Map.Entry entry : nameMap.entrySet()) { + public void setNameMap(Map> nameMap) { + for (Map.Entry> entry : nameMap.entrySet()) { addCacheMethod(entry.getKey(), entry.getValue()); } } - /** - * Parses the given properties into a name/attribute map. - * Expects method names as keys and String attributes definitions as values, - * parsable into CacheOperation instances via CacheOperationEditor. - * @see #setNameMap - * @see CacheOperationEditor - */ - public void setProperties(Properties cacheOperations) { - CacheOperationEditor tae = new CacheOperationEditor(); - Enumeration propNames = cacheOperations.propertyNames(); - while (propNames.hasMoreElements()) { - String methodName = (String) propNames.nextElement(); - String value = cacheOperations.getProperty(methodName); - tae.setAsText(value); - CacheOperation op = (CacheOperation) tae.getValue(); - addCacheMethod(methodName, op); - } - } - /** * Add an attribute for a cacheable method. *

Method names can be exact matches, or of the pattern "xxx*", * "*xxx" or "*xxx*" for matching multiple methods. * @param methodName the name of the method - * @param operation operation associated with the method + * @param ops operation associated with the method */ - public void addCacheMethod(String methodName, CacheOperation operation) { + public void addCacheMethod(String methodName, Collection ops) { if (logger.isDebugEnabled()) { - logger.debug("Adding method [" + methodName + "] with cache operation [" + operation + "]"); + logger.debug("Adding method [" + methodName + "] with cache operations [" + ops + "]"); } - this.nameMap.put(methodName, Collections.singleton(operation)); + this.nameMap.put(methodName, ops); } public Collection getCacheOperations(Method method, Class targetClass) { // look for direct name match String methodName = method.getName(); - Collection attr = this.nameMap.get(methodName); + Collection ops = this.nameMap.get(methodName); - if (attr == null) { + if (ops == null) { // Look for most specific name match. String bestNameMatch = null; for (String mappedName : this.nameMap.keySet()) { if (isMatch(methodName, mappedName) && (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { - attr = this.nameMap.get(mappedName); + ops = this.nameMap.get(mappedName); bestNameMatch = mappedName; } } } - return attr; + return ops; } /** diff --git a/org.springframework.context/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd b/org.springframework.context/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd index 2ffee69a392..d633418686a 100644 --- a/org.springframework.context/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd +++ b/org.springframework.context/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd @@ -182,35 +182,29 @@ The SpEL expression used for conditioning the method caching.]]> - - - - - - - - + + - - - - + example, 'get*', 'handle*', '*Order', 'on*Event', etc.]]> + + + - - + + + - + cn = cm.getCacheNames(); + assertTrue(cn.contains("default")); + assertTrue(cn.contains("secondary")); + assertTrue(cn.contains("primary")); } public void testCacheable(CacheableService service) throws Exception { @@ -201,6 +206,124 @@ public abstract class AbstractAnnotationTests { assertEquals(three, Integer.valueOf(cache.get(three).get().toString())); } + public void testMultiCache(CacheableService service) { + Object o1 = new Object(); + Object o2 = new Object(); + + Cache primary = cm.getCache("primary"); + Cache secondary = cm.getCache("secondary"); + + assertNull(primary.get(o1)); + assertNull(secondary.get(o1)); + Object r1 = service.multiCache(o1); + assertSame(r1, primary.get(o1).get()); + assertSame(r1, secondary.get(o1).get()); + + Object r2 = service.multiCache(o1); + Object r3 = service.multiCache(o1); + + assertSame(r1, r2); + assertSame(r1, r3); + + assertNull(primary.get(o2)); + assertNull(secondary.get(o2)); + Object r4 = service.multiCache(o2); + assertSame(r4, primary.get(o2).get()); + assertSame(r4, secondary.get(o2).get()); + } + + public void testMultiEvict(CacheableService service) { + Object o1 = new Object(); + + Object r1 = service.multiCache(o1); + Object r2 = service.multiCache(o1); + + Cache primary = cm.getCache("primary"); + Cache secondary = cm.getCache("secondary"); + + assertSame(r1, r2); + assertSame(r1, primary.get(o1).get()); + assertSame(r1, secondary.get(o1).get()); + + service.multiEvict(o1); + assertNull(primary.get(o1)); + assertNull(secondary.get(o1)); + + Object r3 = service.multiCache(o1); + Object r4 = service.multiCache(o1); + assertNotSame(r1, r3); + assertSame(r3, r4); + + assertSame(r3, primary.get(o1).get()); + assertSame(r4, secondary.get(o1).get()); + } + + public void testMultiPut(CacheableService service) { + Object o = Integer.valueOf(1); + + Cache primary = cm.getCache("primary"); + Cache secondary = cm.getCache("secondary"); + + assertNull(primary.get(o)); + assertNull(secondary.get(o)); + Object r1 = service.multiUpdate(o); + assertSame(r1, primary.get(o).get()); + assertSame(r1, secondary.get(o).get()); + + o = Integer.valueOf(2); + assertNull(primary.get(o)); + assertNull(secondary.get(o)); + Object r2 = service.multiUpdate(o); + assertSame(r2, primary.get(o).get()); + assertSame(r2, secondary.get(o).get()); + } + + public void testMultiCacheAndEvict(CacheableService service) { + String methodName = "multiCacheAndEvict"; + + Cache primary = cm.getCache("primary"); + Cache secondary = cm.getCache("secondary"); + Object key = Integer.valueOf(1); + + secondary.put(key, key); + + assertNull(secondary.get(methodName)); + assertSame(key, secondary.get(key).get()); + + Object r1 = service.multiCacheAndEvict(key); + assertSame(r1, service.multiCacheAndEvict(key)); + + // assert the method name is used + assertSame(r1, primary.get(methodName).get()); + assertNull(secondary.get(methodName)); + assertNull(secondary.get(key)); + } + + public void testMultiConditionalCacheAndEvict(CacheableService service) { + Cache primary = cm.getCache("primary"); + Cache secondary = cm.getCache("secondary"); + Object key = Integer.valueOf(1); + + secondary.put(key, key); + + assertNull(primary.get(key)); + assertSame(key, secondary.get(key).get()); + + Object r1 = service.multiConditionalCacheAndEvict(key); + Object r3 = service.multiConditionalCacheAndEvict(key); + + assertTrue(!r1.equals(r3)); + assertNull(primary.get(key)); + + Object key2 = Integer.valueOf(3); + Object r2 = service.multiConditionalCacheAndEvict(key2); + assertSame(r2, service.multiConditionalCacheAndEvict(key2)); + + // assert the method name is used + assertSame(r2, primary.get(key2).get()); + assertNull(secondary.get(key2)); + } + @Test public void testCacheable() throws Exception { testCacheable(cs); @@ -329,4 +452,54 @@ public abstract class AbstractAnnotationTests { public void testClassConditionalUpdate() { testConditionalCacheUpdate(ccs); } + + @Test + public void testMultiCache() { + testMultiCache(cs); + } + + @Test + public void testClassMultiCache() { + testMultiCache(ccs); + } + + @Test + public void testMultiEvict() { + testMultiEvict(cs); + } + + @Test + public void testClassMultiEvict() { + testMultiEvict(ccs); + } + + @Test + public void testMultiPut() { + testMultiPut(cs); + } + + @Test + public void testClassMultiPut() { + testMultiPut(ccs); + } + + @Test + public void testMultiCacheAndEvict() { + testMultiCacheAndEvict(cs); + } + + @Test + public void testClassMultiCacheAndEvict() { + testMultiCacheAndEvict(ccs); + } + + @Test + public void testMultiConditionalCacheAndEvict() { + testMultiConditionalCacheAndEvict(cs); + } + + @Test + public void testClassMultiConditionalCacheAndEvict() { + testMultiConditionalCacheAndEvict(ccs); + } } \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/org.springframework.context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java index 503b9a52ec8..58cb3983c0d 100644 --- a/org.springframework.context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java +++ b/org.springframework.context/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java @@ -18,8 +18,9 @@ package org.springframework.cache.config; import java.util.concurrent.atomic.AtomicLong; +import org.springframework.cache.annotation.CacheDefinitions; import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CacheUpdate; +import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; /** @@ -62,12 +63,12 @@ public class AnnotatedClassCacheableService implements CacheableService { return counter.getAndIncrement(); } - @CacheUpdate("default") + @CachePut("default") public Object update(Object arg1) { return counter.getAndIncrement(); } - @CacheUpdate(value = "default", condition = "#arg.equals(3)") + @CachePut(value = "default", condition = "#arg.equals(3)") public Object conditionalUpdate(Object arg) { return arg; } @@ -88,4 +89,31 @@ public class AnnotatedClassCacheableService implements CacheableService { public Long throwUnchecked(Object arg1) { throw new UnsupportedOperationException(); } -} + + // multi annotations + + @CacheDefinitions(cacheable = { @Cacheable("primary"), @Cacheable("secondary") }) + public Object multiCache(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") }) + public Object multiEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(cacheable = { @Cacheable(value = "primary", key = "#root.methodName") }, evict = { @CacheEvict("secondary") }) + public Object multiCacheAndEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(cacheable = { @Cacheable(value = "primary", condition = "#p0 == 3") }, evict = { @CacheEvict("secondary") }) + public Object multiConditionalCacheAndEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(put = { @CachePut("primary"), @CachePut("secondary") }) + public Object multiUpdate(Object arg1) { + return arg1; + } +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/cache/config/AbstractCacheAdviceNamespaceTests.java b/org.springframework.context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java similarity index 89% rename from org.springframework.context/src/test/java/org/springframework/cache/config/AbstractCacheAdviceNamespaceTests.java rename to org.springframework.context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java index c8a31d79f33..6fa45351d38 100644 --- a/org.springframework.context/src/test/java/org/springframework/cache/config/AbstractCacheAdviceNamespaceTests.java +++ b/org.springframework.context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java @@ -24,7 +24,7 @@ import org.springframework.cache.interceptor.CacheInterceptor; /** * @author Costin Leau */ -public abstract class AbstractCacheAdviceNamespaceTests extends AbstractAnnotationTests { +public class CacheAdviceNamespaceTests extends AbstractAnnotationTests { @Override diff --git a/org.springframework.context/src/test/java/org/springframework/cache/config/CacheableService.java b/org.springframework.context/src/test/java/org/springframework/cache/config/CacheableService.java index e145ea3e613..88e87cada87 100644 --- a/org.springframework.context/src/test/java/org/springframework/cache/config/CacheableService.java +++ b/org.springframework.context/src/test/java/org/springframework/cache/config/CacheableService.java @@ -50,4 +50,14 @@ public interface CacheableService { T throwUnchecked(Object arg1); + // multi annotations + T multiCache(Object arg1); + + T multiEvict(Object arg1); + + T multiCacheAndEvict(Object arg1); + + T multiConditionalCacheAndEvict(Object arg1); + + T multiUpdate(Object arg1); } \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/org.springframework.context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java index 1d796204b1f..5376c56f3b8 100644 --- a/org.springframework.context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java +++ b/org.springframework.context/src/test/java/org/springframework/cache/config/DefaultCacheableService.java @@ -18,8 +18,9 @@ package org.springframework.cache.config; import java.util.concurrent.atomic.AtomicLong; +import org.springframework.cache.annotation.CacheDefinitions; import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CacheUpdate; +import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; /** @@ -65,12 +66,12 @@ public class DefaultCacheableService implements CacheableService { return counter.getAndIncrement(); } - @CacheUpdate("default") + @CachePut("default") public Long update(Object arg1) { return counter.getAndIncrement(); } - @CacheUpdate(value = "default", condition = "#arg.equals(3)") + @CachePut(value = "default", condition = "#arg.equals(3)") public Long conditionalUpdate(Object arg) { return Long.valueOf(arg.toString()); } @@ -94,4 +95,31 @@ public class DefaultCacheableService implements CacheableService { public Long throwUnchecked(Object arg1) { throw new UnsupportedOperationException(arg1.toString()); } + + // multi annotations + + @CacheDefinitions(cacheable = { @Cacheable("primary"), @Cacheable("secondary") }) + public Long multiCache(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") }) + public Long multiEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(cacheable = { @Cacheable(value = "primary", key = "#root.methodName") }, evict = { @CacheEvict("secondary") }) + public Long multiCacheAndEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(cacheable = { @Cacheable(value = "primary", condition = "#p0 == 3") }, evict = { @CacheEvict("secondary") }) + public Long multiConditionalCacheAndEvict(Object arg1) { + return counter.getAndIncrement(); + } + + @CacheDefinitions(put = { @CachePut("primary"), @CachePut("secondary") }) + public Long multiUpdate(Object arg1) { + return Long.valueOf(arg1.toString()); + } } \ No newline at end of file diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml index c029a56ce44..f0e83281eec 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml @@ -30,6 +30,8 @@ + + diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml index 5179346d7f3..30127b43529 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml @@ -17,6 +17,8 @@ + + diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml index cc69de1d09a..6af65ab73d6 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml @@ -20,6 +20,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -35,6 +55,22 @@ + + + + + + + + + + + + + + + + @@ -47,6 +83,8 @@ + +