diff --git a/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj
new file mode 100644
index 00000000000..61f2551931f
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 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.aspectj;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import org.aspectj.lang.annotation.SuppressAjWarnings;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.cache.interceptor.CacheAspectSupport;
+import org.springframework.cache.interceptor.CacheDefinitionSource;
+
+/**
+ * Abstract superaspect for AspectJ cache aspects. Concrete
+ * subaspects will implement the cacheMethodExecution()
+ * pointcut using a strategy such as Java 5 annotations.
+ *
+ *
Suitable for use inside or outside the Spring IoC container.
+ * Set the "cacheManager" property appropriately, allowing
+ * use of any cache implementation supported by Spring.
+ *
+ *
NB: If a method implements an interface that is itself
+ * cache annotated, the relevant Spring cache definition
+ * will not be resolved.
+
+ * @author Costin Leau
+ */
+public abstract aspect AbstractCacheAspect extends CacheAspectSupport {
+
+ protected AbstractCacheAspect() {
+ }
+
+ /**
+ * Construct object using the given caching metadata retrieval strategy.
+ * @param cds {@link CacheDefinitionSource} implementation, retrieving Spring
+ * cache metadata for each joinpoint.
+ */
+ protected AbstractCacheAspect(CacheDefinitionSource... cds) {
+ setCacheDefinitionSources(cds);
+ }
+
+ @SuppressAjWarnings("adviceDidNotMatch")
+ Object around(final Object cachedObject) : cacheMethodExecution(cachedObject){
+ MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
+ Method method = methodSignature.getMethod();
+
+ Callable ajInvocation = new Callable() {
+
+ public Object call() {
+ return proceed(cachedObject);
+ }
+ };
+
+ try {
+ return execute(ajInvocation, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
+ } catch (Exception ex) {
+ throw new UnsupportedOperationException("Should not throw exception", ex);
+ }
+ }
+
+ /**
+ * Concrete subaspects must implement this pointcut, to identify
+ * cached methods. For each selected joinpoint, {@link CacheOperationDefinition}
+ * will be retrieved using Spring's {@link CacheDefinitionSource} interface.
+ */
+ protected abstract pointcut cacheMethodExecution(Object cachedObject);
+}
\ No newline at end of file
diff --git a/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj
new file mode 100644
index 00000000000..3ef92e640a1
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 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.aspectj;
+
+import org.springframework.cache.annotation.AnnotationCacheDefinitionSource;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+/**
+ * Concrete AspectJ cache aspect using Spring {@link Cacheable} annotation
+ * for JDK 1.5+.
+ *
+ * When using this aspect, you must annotate the implementation class
+ * (and/or methods within that class), not the interface (if any) that
+ * the class implements. AspectJ follows Java's rule that annotations on
+ * interfaces are not inherited.
+ *
+ *
A {@link Cacheable} annotation on a class specifies the default caching
+ * semantics for the execution of any public operation in the class.
+ *
+ *
A {@link Cacheable} annotation on a method within the class overrides the
+ * default caching semantics given by the class annotation (if present).
+ * Any method may be annotated (regardless of visibility).
+ * Annotating non-public methods directly is the only way
+ * to get caching demarcation for the execution of such operations.
+ *
+ * @author Costin Leau
+ */
+public aspect AnnotationCacheAspect extends AbstractCacheAspect {
+
+ public AnnotationCacheAspect() {
+ super(new AnnotationCacheDefinitionSource(false));
+ }
+
+ /**
+ * Matches the execution of any public method in a type with the
+ * {@link Cacheable} annotation, or any subtype of a type with the
+ * {@link Cacheable} annotation.
+ */
+ private pointcut executionOfAnyPublicMethodInAtCacheableType() :
+ execution(public * ((@Cacheable *)+).*(..)) && @this(Cacheable);
+
+ /**
+ * Matches the execution of any public method in a type with the
+ * {@link CacheEvict} annotation, or any subtype of a type with the
+ * {@link CacheEvict} annotation.
+ */
+ private pointcut executionOfAnyPublicMethodInAtCacheEvictType() :
+ execution(public * ((@CacheEvict *)+).*(..)) && @this(CacheEvict);
+
+ /**
+ * Matches the execution of any method with the
+ * Cacheable annotation.
+ */
+ private pointcut executionOfCacheableMethod() :
+ execution(* *(..)) && @annotation(Cacheable);
+
+ /**
+ * Matches the execution of any method with the {@link CacheEvict} annotation.
+ */
+ private pointcut executionOfCacheEvictMethod() :
+ execution(* *(..)) && @annotation(CacheEvict);
+
+ /**
+ * Definition of pointcut from super aspect - matched join points
+ * will have Spring cache management applied.
+ */
+ protected pointcut cacheMethodExecution(Object cachedObject) :
+ (executionOfAnyPublicMethodInAtCacheableType() || executionOfAnyPublicMethodInAtCacheEvictType()
+ || executionOfCacheableMethod() || executionOfCacheEvictMethod())
+ && this(cachedObject);
+}
\ No newline at end of file
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AbstractAnnotationTest.java b/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AbstractAnnotationTest.java
new file mode 100644
index 00000000000..934a97fff52
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AbstractAnnotationTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2010 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.aspectj;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.cache.config.CacheableService;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+/**
+ * Abstract annotation test (containing several reusable methods).
+ * @author Costin Leau
+ */
+public abstract class AbstractAnnotationTest {
+
+ protected ApplicationContext ctx;
+
+ protected CacheableService cs;
+
+ protected CacheableService ccs;
+
+ protected abstract String getConfig();
+
+ @Before
+ public void setup() {
+ ctx = new ClassPathXmlApplicationContext(getConfig());
+ cs = ctx.getBean("service", CacheableService.class);
+ ccs = ctx.getBean("classService", CacheableService.class);
+ }
+
+ public void testCacheable(CacheableService service) throws Exception {
+ Object o1 = new Object();
+ Object o2 = new Object();
+
+ Object r1 = service.cache(o1);
+ Object r2 = service.cache(o1);
+ Object r3 = service.cache(o1);
+
+ assertSame(r1, r2);
+ assertSame(r1, r3);
+ }
+
+ public void testInvalidate(CacheableService service) throws Exception {
+ Object o1 = new Object();
+ Object o2 = new Object();
+
+ Object r1 = service.cache(o1);
+ Object r2 = service.cache(o1);
+
+ assertSame(r1, r2);
+ service.invalidate(o1);
+ Object r3 = service.cache(o1);
+ Object r4 = service.cache(o1);
+ assertNotSame(r1, r3);
+ assertSame(r3, r4);
+ }
+
+ public void testConditionalExpression(CacheableService service) throws Exception {
+ Object r1 = service.conditional(4);
+ Object r2 = service.conditional(4);
+
+ assertNotSame(r1, r2);
+
+ Object r3 = service.conditional(3);
+ Object r4 = service.conditional(3);
+
+ assertSame(r3, r4);
+ }
+
+ public void testKeyExpression(CacheableService service) throws Exception {
+ Object r1 = service.key(5, 1);
+ Object r2 = service.key(5, 2);
+
+ assertSame(r1, r2);
+
+ Object r3 = service.key(1, 5);
+ Object r4 = service.key(2, 5);
+
+ assertNotSame(r3, r4);
+ }
+
+ @Test
+ public void testCacheable() throws Exception {
+ testCacheable(cs);
+ }
+
+ @Test
+ public void testInvalidate() throws Exception {
+ testInvalidate(cs);
+ }
+
+ @Test
+ public void testConditionalExpression() throws Exception {
+ testConditionalExpression(cs);
+ }
+
+ @Test
+ public void testKeyExpression() throws Exception {
+ testKeyExpression(cs);
+ }
+
+ @Test
+ public void testClassCacheCacheable() throws Exception {
+ testCacheable(ccs);
+ }
+
+ @Test
+ public void testClassCacheInvalidate() throws Exception {
+ testInvalidate(ccs);
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AspectJAnnotationTest.java b/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AspectJAnnotationTest.java
new file mode 100644
index 00000000000..23fb0ab2db5
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/aspectj/AspectJAnnotationTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.aspectj;
+
+
+/**
+ * @author Costin Leau
+ */
+public class AspectJAnnotationTest extends AbstractAnnotationTest {
+
+ @Override
+ protected String getConfig() {
+ return "/org/springframework/cache/config/annotation-cache-aspectj.xml";
+ }
+}
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/org.springframework.aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
new file mode 100644
index 00000000000..46db113e9f1
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 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.config;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+/**
+ * @author Costin Leau
+ */
+@Cacheable
+public class AnnotatedClassCacheableService implements CacheableService {
+
+ private AtomicLong counter = new AtomicLong();
+
+ public Object cache(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ public Object conditional(int field) {
+ return null;
+ }
+
+ @CacheEvict
+ public void invalidate(Object arg1) {
+ }
+
+ @Cacheable(key = "#p0")
+ public Object key(Object arg1, Object arg2) {
+ return counter.getAndIncrement();
+ }
+}
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/config/CacheableService.java b/org.springframework.aspects/src/test/java/org/springframework/cache/config/CacheableService.java
new file mode 100644
index 00000000000..5ec9b0c155b
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/config/CacheableService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.config;
+
+/**
+ * Basic service interface.
+ *
+ * @author Costin Leau
+ */
+public interface CacheableService {
+
+ T cache(Object arg1);
+
+ void invalidate(Object arg1);
+
+ T conditional(int field);
+
+ T key(Object arg1, Object arg2);
+
+}
\ No newline at end of file
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/org.springframework.aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
new file mode 100644
index 00000000000..f5baac7780e
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.config;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+
+/**
+ * Simple cacheable service
+ *
+ * @author Costin Leau
+ */
+public class DefaultCacheableService implements CacheableService {
+
+ private AtomicLong counter = new AtomicLong();
+
+ @Cacheable
+ public Long cache(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @CacheEvict
+ public void invalidate(Object arg1) {
+ }
+
+ @Cacheable(condition = "#classField == 3")
+ public Long conditional(int classField) {
+ return counter.getAndIncrement();
+ }
+
+ @Cacheable(key = "#p0")
+ public Long key(Object arg1, Object arg2) {
+ return counter.getAndIncrement();
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml b/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml
new file mode 100644
index 00000000000..d19682f41f6
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/Cache.java b/org.springframework.context.support/src/main/java/org/springframework/cache/Cache.java
new file mode 100644
index 00000000000..ba9ce6b4a0a
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/Cache.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2002-2009 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;
+
+
+/**
+ * Interface that defines the common cache operations.
+ *
+ * @author Costin Leau
+ */
+public interface Cache {
+
+ /**
+ * Returns the cache name.
+ *
+ * @return the cache name.
+ */
+ String getName();
+
+ /**
+ * Returns the the native, underlying cache provider.
+ *
+ * @return
+ */
+ Object getNativeCache();
+
+ /**
+ * Returns true if this cache contains a mapping for the specified
+ * key. More formally, returns true if and only if
+ * this cache contains a mapping for a key k such that
+ * (key==null ? k==null : key.equals(k)) . (There can be
+ * at most one such mapping.)
+ *
+ * @param key key whose presence in this cache is to be tested.
+ * @return true if this cache contains a mapping for the specified
+ * key.
+ */
+ boolean containsKey(Object key);
+
+ /**
+ * Returns the value to which this cache maps the specified key. Returns
+ * null if the cache contains no mapping for this key. A return
+ * value of null does not necessarily indicate that the
+ * cache contains no mapping for the key; it's also possible that the cache
+ * explicitly maps the key to null . The containsKey
+ * operation may be used to distinguish these two cases.
+ *
+ * More formally, if this cache contains a mapping from a key
+ * k to a value v such that (key==null ? k==null :
+ * key.equals(k)) , then this method returns v ; otherwise
+ * it returns null . (There can be at most one such mapping.)
+ *
+ * @param key key whose associated value is to be returned.
+ * @return the value to which this cache maps the specified key, or
+ * null if the cache contains no mapping for this key.
+ *
+ * @see #containsKey(Object)
+ */
+ V get(Object key);
+
+
+ /**
+ * Associates the specified value with the specified key in this cache
+ * (optional operation). If the cache previously contained a mapping for
+ * this key, the old value is replaced by the specified value. (A cache
+ * m is said to contain a mapping for a key k if and only
+ * if {@link #containsKey(Object) m.containsKey(k)} would return
+ * true .))
+ *
+ * @param key key with which the specified value is to be associated.
+ * @param value value to be associated with the specified key.
+ * @return previous value associated with specified key, or null
+ * if there was no mapping for key. A null return can
+ * also indicate that the cache previously associated null
+ * with the specified key, if the implementation supports
+ * null values.
+ */
+ V put(K key, V value);
+
+
+ /**
+ * If the specified key is not already associated with a value, associate it with the given value.
+ *
+ * This is equivalent to:
+ *
+ * if (!cache.containsKey(key))
+ * return cache.put(key, value);
+ * else
+ * return cache.get(key);
+ *
+ *
+ * @param key key with which the specified value is to be associated.
+ * @param value value to be associated with the specified key.
+ * @return previous value associated with specified key, or null
+ * if there was no mapping for key. A null return can
+ * also indicate that the cache previously associated null
+ * with the specified key, if the implementation supports
+ * null values.
+ */
+ V putIfAbsent(K key, V value);
+
+
+ /**
+ * Removes the mapping for this key from this cache if it is present
+ * (optional operation). More formally, if this cache contains a mapping
+ * from key k to value v such that
+ * (key==null ? k==null : key.equals(k)), that mapping
+ * is removed. (The cache can contain at most one such mapping.)
+ *
+ * Returns the value to which the cache previously associated the key, or
+ * null if the cache contained no mapping for this key. (A
+ * null return can also indicate that the cache previously
+ * associated null with the specified key if the implementation
+ * supports null values.) The cache will not contain a mapping for
+ * the specified key once the call returns.
+ *
+ * @param key key whose mapping is to be removed from the cache.
+ * @return previous value associated with specified key, or null
+ * if there was no mapping for key.
+ */
+ V remove(Object key);
+
+
+ /**
+ * Remove entry for key only if currently mapped to given value.
+ *
+ * Similar to:
+ *
+ * if ((cache.containsKey(key) && cache.get(key).equals(value)) {
+ * cache.remove(key);
+ * return true;
+ * }
+ * else
+ * return false;
+ *
+ *
+ * @param key key with which the specified value is associated.
+ * @param value value associated with the specified key.
+ * @return true if the value was removed, false otherwise
+ */
+ boolean remove(Object key, Object value);
+
+
+ /**
+ * Replace entry for key only if currently mapped to given value.
+ *
+ * Similar to:
+ *
+ * if ((cache.containsKey(key) && cache.get(key).equals(oldValue)) {
+ * cache.put(key, newValue);
+ * return true;
+ * } else return false;
+ *
+
+ * @param key key with which the specified value is associated.
+ * @param oldValue value expected to be associated with the specified key.
+ * @param newValue value to be associated with the specified key.
+ * @return true if the value was replaced
+ */
+ boolean replace(K key, V oldValue, V newValue);
+
+ /**
+ * Replace entry for key only if currently mapped to some value.
+ * Acts as
+ *
+ * if ((cache.containsKey(key)) {
+ * return cache.put(key, value);
+ * } else return null;
+ *
+ * except that the action is performed atomically.
+ * @param key key with which the specified value is associated.
+ * @param value value to be associated with the specified key.
+ * @return previous value associated with specified key, or null
+ * if there was no mapping for key. A null return can
+ * also indicate that the cache previously associated null
+ * with the specified key, if the implementation supports
+ * null values.
+ */
+ V replace(K key, V value);
+
+
+ /**
+ * Removes all mappings from the cache.
+ */
+ void clear();
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/CacheManager.java b/org.springframework.context.support/src/main/java/org/springframework/cache/CacheManager.java
new file mode 100644
index 00000000000..d8307d7e47a
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/CacheManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2009 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;
+
+import java.util.Collection;
+
+
+/**
+ * Entity managing {@link Cache}s.
+ *
+ * @author Costin Leau
+ */
+public interface CacheManager {
+
+ /**
+ * Returns the cache associated with the given name.
+ *
+ * @param name cache identifier - cannot be null
+ * @return associated cache or null if none is found
+ */
+ Cache getCache(String name);
+
+ /**
+ * Returns a collection of the caches known by this cache manager.
+ *
+ * @return names of caches known by the cache manager.
+ */
+ Collection getCacheNames();
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/KeyGenerator.java b/org.springframework.context.support/src/main/java/org/springframework/cache/KeyGenerator.java
new file mode 100644
index 00000000000..256e253cb16
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/KeyGenerator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2009 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;
+
+/**
+ * Cache 'key' extractor. Used for creating a key based on the given
+ * parameters.
+ *
+ * @author Costin Leau
+ */
+// CL: could be renamed to KeyFactory
+public interface KeyGenerator {
+
+ K extract(Object... params);
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/AnnotationCacheDefinitionSource.java b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/AnnotationCacheDefinitionSource.java
new file mode 100644
index 00000000000..40681fa080b
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/AnnotationCacheDefinitionSource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.cache.interceptor.AbstractFallbackCacheDefinitionSource;
+import org.springframework.cache.interceptor.CacheDefinition;
+import org.springframework.util.Assert;
+
+/**
+ *
+ * Implementation of the
+ * {@link org.springframework.cache.interceptor.CacheDefinitionSource}
+ * interface for working with caching metadata in JDK 1.5+ annotation format.
+ *
+ * This class reads Spring's JDK 1.5+ {@link Cacheable} 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 CacheDefinitionSource.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+public class AnnotationCacheDefinitionSource extends AbstractFallbackCacheDefinitionSource implements
+ Serializable {
+
+ private final boolean publicMethodsOnly;
+
+ private final Set annotationParsers;
+
+ /**
+ * Create a default AnnotationCacheOperationDefinitionSource, supporting
+ * public methods that carry the Cacheable and CacheInvalidate
+ * annotations.
+ */
+ public AnnotationCacheDefinitionSource() {
+ this(true);
+ }
+
+ /**
+ * Create a custom AnnotationCacheOperationDefinitionSource, supporting
+ * public methods that carry the Cacheable and
+ * CacheInvalidate annotations.
+ *
+ * @param publicMethodsOnly whether to support only annotated public methods
+ * typically for use with proxy-based AOP), or protected/private methods as well
+ * (typically used with AspectJ class weaving)
+ */
+ public AnnotationCacheDefinitionSource(boolean publicMethodsOnly) {
+ this.publicMethodsOnly = publicMethodsOnly;
+ this.annotationParsers = new LinkedHashSet(1);
+ this.annotationParsers.add(new SpringCachingAnnotationParser());
+ }
+
+ /**
+ * Create a custom AnnotationCacheOperationDefinitionSource.
+ * @param annotationParsers the CacheAnnotationParser to use
+ */
+ public AnnotationCacheDefinitionSource(CacheAnnotationParser... annotationParsers) {
+ this.publicMethodsOnly = true;
+ Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
+ Set parsers = new LinkedHashSet(annotationParsers.length);
+ Collections.addAll(parsers, annotationParsers);
+ this.annotationParsers = parsers;
+ }
+
+ @Override
+ protected CacheDefinition findCacheDefinition(Class> clazz) {
+ return determineCacheDefinition(clazz);
+ }
+
+ @Override
+ protected CacheDefinition findCacheOperation(Method method) {
+ return determineCacheDefinition(method);
+ }
+
+ /**
+ * Determine the cache operation definition for the given method or class.
+ * This implementation delegates to configured
+ * {@link CacheAnnotationParser CacheAnnotationParsers}
+ * for parsing known annotations into Spring's metadata attribute class.
+ * Returns null if it's not cacheable.
+ *
Can be overridden to support custom annotations that carry caching metadata.
+ * @param ae the annotated method or class
+ * @return CacheOperationDefinition the configured caching operation,
+ * or null if none was found
+ */
+ protected CacheDefinition determineCacheDefinition(AnnotatedElement ae) {
+ for (CacheAnnotationParser annotationParser : this.annotationParsers) {
+ CacheDefinition attr = annotationParser.parseTransactionAnnotation(ae);
+ if (attr != null) {
+ return attr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * By default, only public methods can be made cacheable.
+ */
+ @Override
+ protected boolean allowPublicMethodsOnly() {
+ return this.publicMethodsOnly;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
new file mode 100644
index 00000000000..13fb7f18791
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 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.reflect.AnnotatedElement;
+
+import org.springframework.cache.interceptor.CacheDefinition;
+
+
+/**
+ * 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} or {@link CacheEvict}.
+ *
+ * @author Costin Leau
+ */
+public interface CacheAnnotationParser {
+
+ /**
+ * Parses the cache definition for the given method or class,
+ * based on a known annotation type.
+ *
This essentially parses a known cache annotation into Spring's
+ * metadata attribute class. Returns null if the method/class
+ * is not cacheable.
+ * @param ae the annotated method or class
+ * @return CacheOperationDefinition the configured caching operation,
+ * or null if none was found
+ * @see AnnotationCacheDefinitionSource#determineCacheOperationDefinition
+ */
+ CacheDefinition parseTransactionAnnotation(AnnotatedElement ae);
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheEvict.java
new file mode 100644
index 00000000000..cf5c54ea436
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/CacheEvict.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2009 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.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation indicating that a method (or all methods on a class) trigger(s)
+ * a cache invalidate operation.
+ *
+ * @author Costin Leau
+ */
+@Target( { ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface CacheEvict {
+
+ /**
+ * Qualifier value for the specified cached operation.
+ *
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() default "";
+
+ /**
+ * Spring Expression Language (SpEL) attribute for computing the key dynamically.
+ *
+ * Default is "" meaning all method parameters are considered as a key.
+ */
+ String key() default "";
+
+ /**
+ * Spring Expression Language (SpEL) attribute used for conditioning the method caching.
+ *
+ * Default is "" meaning the method is always cached.
+ */
+ String condition() default "";
+
+ /**
+ * Whether or not all the entries inside the cache are removed or not.
+ * By default, only the value under the associated key is removed.
+ *
+ * Note that specifying setting this parameter to true and specifying a
+ * {@link CacheKey key} is not allowed.
+ */
+ boolean allEntries() default false;
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/Cacheable.java b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/Cacheable.java
new file mode 100644
index 00000000000..1e641f69af6
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2009 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.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation indicating that a method (or all the methods on a class) can be cached.
+ * The method arguments and signature are used for computing the key while the return instance
+ * as the cache value.
+ *
+ * @author Costin Leau
+ */
+@Target( { ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Cacheable {
+
+ /**
+ * Name of the cache in which the update takes place.
+ * 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() default "";
+
+ /**
+ * Spring Expression Language (SpEL) attribute for computing the key dynamically.
+ *
+ * Default is "" meaning all method parameters are considered as a key.
+ */
+ String key() default "";
+
+ /**
+ * Spring Expression Language (SpEL) attribute used for conditioning the method caching.
+ *
+ * Default is "" meaning the method is always cached.
+ */
+ String condition() default "";
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/SpringCachingAnnotationParser.java b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/SpringCachingAnnotationParser.java
new file mode 100644
index 00000000000..7dcf9c98582
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/annotation/SpringCachingAnnotationParser.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+
+import org.springframework.cache.interceptor.CacheInvalidateDefinition;
+import org.springframework.cache.interceptor.CacheDefinition;
+import org.springframework.cache.interceptor.CacheUpdateDefinition;
+import org.springframework.cache.interceptor.DefaultCacheInvalidateDefinition;
+import org.springframework.cache.interceptor.DefaultCacheUpdateDefinition;
+
+/**
+ * Strategy implementation for parsing Spring's {@link Cacheable} and {@link CacheEvict} annotations.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+public class SpringCachingAnnotationParser implements CacheAnnotationParser, Serializable {
+
+ public CacheDefinition parseTransactionAnnotation(AnnotatedElement ae) {
+ Cacheable update = findAnnotation(ae, Cacheable.class);
+
+ if (update != null) {
+ return parseCacheableAnnotation(update);
+ }
+
+ CacheEvict invalidate = findAnnotation(ae, CacheEvict.class);
+
+ if (invalidate != null) {
+ return parseInvalidateAnnotation(invalidate);
+ }
+
+ return null;
+ }
+
+ private T findAnnotation(AnnotatedElement ae, Class annotationType) {
+ T ann = ae.getAnnotation(annotationType);
+ if (ann == null) {
+ for (Annotation metaAnn : ae.getAnnotations()) {
+ ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ break;
+ }
+ }
+ }
+ return ann;
+ }
+
+ public CacheUpdateDefinition parseCacheableAnnotation(Cacheable ann) {
+ DefaultCacheUpdateDefinition dcud = new DefaultCacheUpdateDefinition();
+ dcud.setCacheName(ann.value());
+ dcud.setCondition(ann.condition());
+ dcud.setKey(ann.key());
+
+ return dcud;
+ }
+
+ public CacheInvalidateDefinition parseInvalidateAnnotation(CacheEvict ann) {
+ DefaultCacheInvalidateDefinition dcid = new DefaultCacheInvalidateDefinition();
+ dcid.setCacheName(ann.value());
+ dcid.setCondition(ann.condition());
+ dcid.setKey(ann.key());
+ dcid.setCacheWide(ann.allEntries());
+
+ return dcid;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCache.java b/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCache.java
new file mode 100644
index 00000000000..c853aba5b5b
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCache.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 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.concurrent;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.AbstractDelegatingCache;
+
+/**
+ * Simple {@link Cache} implementation based on the JDK 1.5+ java.util.concurrent package.
+ * Useful for testing or simple caching scenarios.
+ *
+ * @author Costin Leau
+ */
+public class ConcurrentCache extends AbstractDelegatingCache {
+
+ private final ConcurrentMap store;
+ private final String name;
+
+ public ConcurrentCache() {
+ this("");
+ }
+
+ public ConcurrentCache(String name) {
+ this(new ConcurrentHashMap(), name);
+ }
+
+ public ConcurrentCache(ConcurrentMap delegate, String name) {
+ super(delegate);
+ this.store = delegate;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ConcurrentMap getNativeCache() {
+ return store;
+ }
+
+ public V putIfAbsent(K key, V value) {
+ return store.putIfAbsent(key, value);
+ }
+
+ public boolean remove(Object key, Object value) {
+ return store.remove(key, value);
+ }
+
+ public boolean replace(K key, V oldValue, V newValue) {
+ return store.replace(key, oldValue, newValue);
+ }
+
+ public V replace(K key, V value) {
+ return store.replace(key, value);
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCacheFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCacheFactoryBean.java
new file mode 100644
index 00000000000..66a97d026ad
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/concurrent/ConcurrentCacheFactoryBean.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010 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.concurrent;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Factory bean for easy configuration of {@link ConcurrentCache} through Spring.
+ *
+ * @author Costin Leau
+ */
+public class ConcurrentCacheFactoryBean implements FactoryBean>, BeanNameAware,
+ InitializingBean {
+
+ private String name = "";
+ private ConcurrentCache cache;
+
+ private ConcurrentMap store;
+
+ public void afterPropertiesSet() {
+ cache = (store == null ? new ConcurrentCache(name) : new ConcurrentCache(store, name));
+ }
+
+ public ConcurrentCache getObject() throws Exception {
+ return cache;
+ }
+
+ public Class> getObjectType() {
+ return (cache != null ? cache.getClass() : ConcurrentCache.class);
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ public void setBeanName(String beanName) {
+ setName(beanName);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setStore(ConcurrentMap store) {
+ this.store = store;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/org.springframework.context.support/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
new file mode 100644
index 00000000000..e91a187239a
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2010 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.config;
+
+import org.springframework.aop.config.AopNamespaceUtils;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.cache.annotation.AnnotationCacheDefinitionSource;
+import org.springframework.cache.interceptor.BeanFactoryCacheDefinitionSourceAdvisor;
+import org.springframework.cache.interceptor.CacheInterceptor;
+import org.w3c.dom.Element;
+
+/**
+ * {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
+ * implementation that allows users to easily configure all the infrastructure
+ * beans required to enable annotation-driven cache demarcation.
+ *
+ * By default, all proxies are created as JDK proxies. This may cause some
+ * problems if you are injecting objects as concrete classes rather than
+ * interfaces. To overcome this restriction you can set the
+ * 'proxy-target-class' attribute to 'true', which
+ * will result in class-based proxies being created.
+ *
+ * @author Costin Leau
+ */
+class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
+
+ private static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
+
+
+ /**
+ * The bean name of the internally managed cache advisor (mode="proxy").
+ */
+ public static final String CACHE_ADVISOR_BEAN_NAME = "org.springframework.cache.config.internalCacheAdvisor";
+
+ /**
+ * The bean name of the internally managed cache aspect (mode="aspectj").
+ */
+ public static final String CACHE_ASPECT_BEAN_NAME = "org.springframework.cache.config.internalCacheAspect";
+
+ private static final String CACHE_ASPECT_CLASS_NAME = "org.springframework.cache.aspectj.AnnotationCacheAspect";
+
+ /**
+ * Parses the '<cache:annotation-driven/>' tag. Will
+ * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
+ * with the container as necessary.
+ */
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ String mode = element.getAttribute("mode");
+ if ("aspectj".equals(mode)) {
+ // mode="aspectj"
+ registerCacheAspect(element, parserContext);
+ }
+ else {
+ // mode="proxy"
+ AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
+ }
+ return null;
+ }
+
+ private static String extractCacheManager(Element element) {
+ return (element.hasAttribute(CACHE_MANAGER_ATTRIBUTE) ? element.getAttribute(CACHE_MANAGER_ATTRIBUTE)
+ : DEFAULT_CACHE_MANAGER_BEAN_NAME);
+ }
+
+ private static void registerCacheManagerProperty(Element element, BeanDefinition def) {
+ def.getPropertyValues().add("cacheManager", new RuntimeBeanReference(extractCacheManager(element)));
+ }
+
+ /**
+ * Registers a
+ *
+ *
+ *
+ *
+ *
+ *
+ * @param element
+ * @param parserContext
+ */
+ private void registerCacheAspect(Element element, ParserContext parserContext) {
+ if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ASPECT_BEAN_NAME)) {
+ RootBeanDefinition def = new RootBeanDefinition();
+ def.setBeanClassName(CACHE_ASPECT_CLASS_NAME);
+ def.setFactoryMethodName("aspectOf");
+ registerCacheManagerProperty(element, def);
+ parserContext.registerBeanComponent(new BeanComponentDefinition(def, CACHE_ASPECT_BEAN_NAME));
+ }
+ }
+
+
+ /**
+ * Inner class to just introduce an AOP framework dependency when actually in proxy mode.
+ */
+ private static class AopAutoProxyConfigurer {
+
+ public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
+ AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
+
+ if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ADVISOR_BEAN_NAME)) {
+ Object eleSource = parserContext.extractSource(element);
+
+ // Create the CacheDefinitionSource definition.
+ RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationCacheDefinitionSource.class);
+ sourceDef.setSource(eleSource);
+ sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
+
+ // Create the CacheInterceptor definition.
+ RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);
+ interceptorDef.setSource(eleSource);
+ interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registerCacheManagerProperty(element, interceptorDef);
+ interceptorDef.getPropertyValues().add("cacheDefinitionSources", new RuntimeBeanReference(sourceName));
+ String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
+
+ // Create the CacheAdvisor definition.
+ RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheDefinitionSourceAdvisor.class);
+ advisorDef.setSource(eleSource);
+ advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ advisorDef.getPropertyValues().add("cacheDefinitionSource", new RuntimeBeanReference(sourceName));
+ advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
+ if (element.hasAttribute("order")) {
+ advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
+ }
+ parserContext.getRegistry().registerBeanDefinition(CACHE_ADVISOR_BEAN_NAME, advisorDef);
+
+ CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
+ eleSource);
+ compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
+ compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
+ compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, CACHE_ADVISOR_BEAN_NAME));
+ parserContext.registerComponent(compositeDef);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java b/org.springframework.context.support/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java
new file mode 100644
index 00000000000..e1ff0ea21e4
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/config/CacheNamespaceHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.config;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+/**
+ * NamespaceHandler allowing for the configuration of
+ * declarative cache management using either XML or using annotations.
+ *
+ * This namespace handler is the central piece of functionality in the
+ * Spring cache management facilities.
+ *
+ * @author Costin Leau
+ */
+public class CacheNamespaceHandler extends NamespaceHandlerSupport {
+
+ public void init() {
+ registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java
new file mode 100644
index 00000000000..6d93ee2fb2d
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2010 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.ehcache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.Status;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.SimpleMapEntry;
+import org.springframework.util.Assert;
+
+/**
+ * {@link Cache} implementation on top of an {@link Ehcache} instance.
+ *
+ * @author Costin Leau
+ */
+public class EhCacheCache implements Cache {
+
+ private final Ehcache cache;
+
+ /**
+ * Creates a {@link EhCacheCache} instance.
+ *
+ * @param ehcache backing Ehcache instance
+ */
+ public EhCacheCache(Ehcache ehcache) {
+ Assert.notNull(ehcache, "non null ehcache required");
+ Status status = ehcache.getStatus();
+ Assert.isTrue(Status.STATUS_ALIVE.equals(status), "an 'alive' ehcache is required - current cache is "
+ + status.toString());
+ this.cache = ehcache;
+ }
+
+ public String getName() {
+ return cache.getName();
+ }
+
+ public Ehcache getNativeCache() {
+ return cache;
+ }
+
+ public void clear() {
+ cache.removeAll();
+ }
+
+ public boolean containsKey(Object key) {
+ return cache.isKeyInCache(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return cache.isValueInCache(value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set> entrySet() {
+ List keys = cache.getKeys();
+ Set> entries = new LinkedHashSet>(keys.size());
+ for (Object key : keys) {
+ Element element = cache.get(key);
+ if (element != null) {
+ entries.add(new SimpleMapEntry(key, element.getObjectValue()));
+ }
+ }
+
+ return Collections.unmodifiableSet(entries);
+ }
+
+ public Object get(Object key) {
+ Element element = cache.get(key);
+ return (element != null ? element.getObjectValue() : null);
+ }
+
+ public boolean isEmpty() {
+ return cache.getSize() == 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set keySet() {
+ List keys = cache.getKeys();
+ Set keySet = new LinkedHashSet(keys.size());
+ for (Object key : keys) {
+ keySet.add(key);
+ }
+ return Collections.unmodifiableSet(keySet);
+ }
+
+ public Object put(Object key, Object value) {
+ Element previous = cache.getQuiet(key);
+ cache.put(new Element(key, value));
+ return (previous != null ? previous.getValue() : null);
+ }
+
+ public void putAll(Map extends Object, ? extends Object> m) {
+ for (Map.Entry extends Object, ? extends Object> entry : m.entrySet()) {
+ cache.put(new Element(entry.getKey(), entry.getValue()));
+ }
+ }
+
+ public Object remove(Object key) {
+ Object value = null;
+ if (cache.isKeyInCache(key)) {
+ Element element = cache.getQuiet(key);
+ value = (element != null ? element.getObjectValue() : null);
+ }
+ cache.remove(key);
+ return value;
+ }
+
+ public int size() {
+ return cache.getSize();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection values() {
+ List keys = cache.getKeys();
+ List values = new ArrayList(keys.size());
+ for (Object key : keys) {
+ Element element = cache.get(key);
+ if (element != null) {
+ values.add(element.getObjectValue());
+ }
+ }
+ return Collections.unmodifiableCollection(values);
+ }
+
+ public Object putIfAbsent(Object key, Object value) {
+ // putIfAbsent supported only from Ehcache 2.1
+ // return cache.putIfAbsent(new Element(key, value));
+ Element existing = cache.getQuiet(key);
+ if (existing == null) {
+ cache.put(new Element(key, value));
+ return null;
+ }
+ return existing.getObjectValue();
+ }
+
+ public boolean remove(Object key, Object value) {
+ // remove(Element) supported only from Ehcache 2.1
+ // return cache.removeElement(new Element(key, value));
+ Element existing = cache.getQuiet(key);
+
+ if (existing != null && existing.getObjectValue().equals(value)) {
+ cache.remove(key);
+ return true;
+ }
+
+ return false;
+ }
+
+ public Object replace(Object key, Object value) {
+ // replace(Object, Object) supported only from Ehcache 2.1
+ // return cache.replace(new Element(key, value));
+ Element existing = cache.getQuiet(key);
+
+ if (existing != null) {
+ cache.put(new Element(key, value));
+ return existing.getObjectValue();
+ }
+
+ return null;
+ }
+
+ public boolean replace(Object key, Object oldValue, Object newValue) {
+ // replace(Object, Object, Object) supported only from Ehcache 2.1
+ // return cache.replace(new Element(key, oldValue), new Element(key,
+ // newValue));
+ Element existing = cache.getQuiet(key);
+
+ if (existing != null && existing.getObjectValue().equals(oldValue)) {
+ cache.put(new Element(key, newValue));
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhcacheCacheManager.java b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhcacheCacheManager.java
new file mode 100644
index 00000000000..6abf06d1b52
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhcacheCacheManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010 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.ehcache;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Status;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.AbstractCacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * CacheManager backed by an Ehcache {@link net.sf.ehcache.CacheManager}.
+ *
+ * @author Costin Leau
+ */
+public class EhcacheCacheManager extends AbstractCacheManager {
+
+ private net.sf.ehcache.CacheManager cacheManager;
+
+ @Override
+ protected Collection> loadCaches() {
+ Assert.notNull(cacheManager, "a backing Ehcache cache manager is required");
+ Status status = cacheManager.getStatus();
+
+ Assert.isTrue(Status.STATUS_ALIVE.equals(status),
+ "an 'alive' Ehcache cache manager is required - current cache is " + status.toString());
+
+ String[] names = cacheManager.getCacheNames();
+ Collection> caches = new LinkedHashSet>(names.length);
+
+ for (String name : names) {
+ caches.add(new EhCacheCache(cacheManager.getEhcache(name)));
+ }
+
+ return caches;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Cache getCache(String name) {
+ Cache cache = super.getCache(name);
+ if (cache == null) {
+ // check the Ehcache cache again
+ // in case the cache was added at runtime
+
+ Ehcache ehcache = cacheManager.getEhcache(name);
+ if (ehcache != null) {
+ // reinitialize cache map
+ afterPropertiesSet();
+ cache = super.getCache(name);
+ }
+ }
+
+ return cache;
+ }
+
+ /**
+ * Sets the backing Ehcache {@link net.sf.ehcache.CacheManager}.
+ *
+ * @param cacheManager backing Ehcache {@link net.sf.ehcache.CacheManager}
+ */
+ public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractCacheDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractCacheDefinition.java
new file mode 100644
index 00000000000..45a77b05798
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractCacheDefinition.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 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;
+
+
+/**
+ * Base class implementing {@link CacheDefinition}.
+ *
+ * @author Costin Leau
+ */
+abstract class AbstractCacheDefinition implements CacheDefinition {
+
+ private String cacheName = "";
+ private String condition = "";
+ private String key = "";
+ private String name = "";
+
+
+ public String getCacheName() {
+ return cacheName;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setCacheName(String cacheName) {
+ this.cacheName = cacheName;
+ }
+
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * This implementation compares the toString() results.
+ * @see #toString()
+ */
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof CacheDefinition && toString().equals(other.toString()));
+ }
+
+ /**
+ * This implementation returns toString()'s hash code.
+ * @see #toString()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Return an identifying description for this cache operation definition.
+ * Has to be overridden in subclasses for correct equals
+ * and hashCode behavior. Alternatively, {@link #equals}
+ * and {@link #hashCode} can be overridden themselves.
+ */
+ @Override
+ public String toString() {
+ return getDefinitionDescription().toString();
+ }
+
+ /**
+ * Return an identifying description for this caching definition.
+ *
Available to subclasses, for inclusion in their toString() result.
+ */
+ protected StringBuilder getDefinitionDescription() {
+ StringBuilder result = new StringBuilder();
+ result.append(cacheName);
+ result.append(',');
+ result.append(condition);
+ result.append(",");
+ result.append(key);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheDefinitionSource.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheDefinitionSource.java
new file mode 100644
index 00000000000..61f18fe5ed5
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheDefinitionSource.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2010 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.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Abstract implementation of {@link CacheDefinition} that caches
+ * attributes for methods and implements a fallback policy: 1. specific target
+ * method; 2. target class; 3. declaring method; 4. declaring class/interface.
+ *
+ *
Defaults to using the target class's caching attribute if none is
+ * associated with the target method. Any caching attribute associated with
+ * the target method completely overrides a class caching attribute.
+ * If none found on the target class, the interface that the invoked method
+ * has been called through (in case of a JDK proxy) will be checked.
+ *
+ *
This implementation caches attributes by method after they are first used.
+ * If it is ever desirable to allow dynamic changing of cacheable attributes
+ * (which is very unlikely), caching could be made configurable.
+
+ * @author Costin Leau
+ * @see org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource
+ */
+public abstract class AbstractFallbackCacheDefinitionSource implements CacheDefinitionSource {
+
+ /**
+ * Canonical value held in cache to indicate no caching attribute was
+ * found for this method, and we don't need to look again.
+ */
+ private final static CacheDefinition NULL_CACHING_ATTRIBUTE = new DefaultCacheUpdateDefinition();
+
+ /**
+ * Logger available to subclasses.
+ *
As this base class is not marked Serializable, the logger will be recreated
+ * after serialization - provided that the concrete subclass is Serializable.
+ */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * Cache of CacheOperationDefinitions, keyed by DefaultCacheKey (Method + target Class).
+ *
As this base class is not marked Serializable, the cache will be recreated
+ * after serialization - provided that the concrete subclass is Serializable.
+ */
+ final Map attributeCache = new ConcurrentHashMap();
+
+ /**
+ * Determine the caching attribute for this method invocation.
+ * Defaults to the class's caching attribute if no method attribute is found.
+ * @param method the method for the current invocation (never null)
+ * @param targetClass the target class for this invocation (may be null)
+ * @return {@link CacheDefinition} for this method, or null if the method
+ * is not cacheable
+ */
+ public CacheDefinition getCacheDefinition(Method method, Class> targetClass) {
+ // First, see if we have a cached value.
+ Object cacheKey = getCacheKey(method, targetClass);
+ CacheDefinition cached = this.attributeCache.get(cacheKey);
+ if (cached != null) {
+ if (cached == NULL_CACHING_ATTRIBUTE) {
+ return null;
+ }
+ // Value will either be canonical value indicating there is no caching attribute,
+ // or an actual caching attribute.
+ return cached;
+ }
+ else {
+ // We need to work it out.
+ CacheDefinition cacheDef = computeCacheOperationDefinition(method, targetClass);
+ // Put it in the cache.
+ if (cacheDef == null) {
+ this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheDef);
+ }
+ this.attributeCache.put(cacheKey, cacheDef);
+ }
+ return cacheDef;
+ }
+ }
+
+ /**
+ * Determine a cache key for the given method and target class.
+ *
Must not produce same key for overloaded methods.
+ * Must produce same key for different instances of the same method.
+ * @param method the method (never null)
+ * @param targetClass the target class (may be null)
+ * @return the cache key (never null)
+ */
+ protected Object getCacheKey(Method method, Class> targetClass) {
+ return new DefaultCacheKey(method, targetClass);
+ }
+
+ /**
+ * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
+ * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
+ * @see #getTransactionAttribute
+ */
+ private CacheDefinition computeCacheOperationDefinition(Method method, Class> targetClass) {
+ // Don't allow no-public methods as required.
+ if (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.
+ CacheDefinition opDef = findCacheOperation(specificMethod);
+ if (opDef != null) {
+ return opDef;
+ }
+
+ // Second try is the caching operation on the target class.
+ opDef = findCacheDefinition(specificMethod.getDeclaringClass());
+ if (opDef != null) {
+ return opDef;
+ }
+
+ if (specificMethod != method) {
+ // Fall back is to look at the original method.
+ opDef = findCacheOperation(method);
+ if (opDef != null) {
+ return opDef;
+ }
+ // Last fall back is the class of the original method.
+ return findCacheDefinition(method.getDeclaringClass());
+ }
+ return null;
+ }
+
+ /**
+ * Subclasses need to implement this to return the caching attribute
+ * for the given method, if any.
+ * @param method the method to retrieve the attribute for
+ * @return all caching attribute associated with this method
+ * (or null if none)
+ */
+ protected abstract CacheDefinition findCacheOperation(Method method);
+
+ /**
+ * Subclasses need to implement this to return the caching attribute
+ * for the given class, if any.
+ * @param clazz the class to retrieve the attribute for
+ * @return all caching attribute associated with this class
+ * (or null if none)
+ */
+ protected abstract CacheDefinition findCacheDefinition(Class> clazz);
+
+ /**
+ * Should only public methods be allowed to have caching semantics?
+ *
The default implementation returns false.
+ */
+ protected boolean allowPublicMethodsOnly() {
+ return false;
+ }
+
+ /**
+ * Default cache key for the CacheOperationDefinition cache.
+ */
+ private static class DefaultCacheKey {
+
+ private final Method method;
+
+ private final Class> targetClass;
+
+ public DefaultCacheKey(Method method, Class> targetClass) {
+ this.method = method;
+ this.targetClass = targetClass;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof DefaultCacheKey)) {
+ return false;
+ }
+ DefaultCacheKey otherKey = (DefaultCacheKey) other;
+ return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass,
+ otherKey.targetClass));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheDefinitionSourceAdvisor.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheDefinitionSourceAdvisor.java
new file mode 100644
index 00000000000..5bbd01b2e02
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/BeanFactoryCacheDefinitionSourceAdvisor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 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 org.springframework.aop.ClassFilter;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor;
+
+/**
+ * Advisor driven by a {@link CacheDefinitionSource}, used to include a
+ * transaction advice bean for methods that are transactional.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+public class BeanFactoryCacheDefinitionSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
+
+ private CacheDefinitionSource cacheDefinitionSource;
+
+ private final CacheDefinitionSourcePointcut pointcut = new CacheDefinitionSourcePointcut() {
+ @Override
+ protected CacheDefinitionSource getCacheDefinitionSource() {
+ return cacheDefinitionSource;
+ }
+ };
+
+ /**
+ * Set the cache operation attribute source which is used to find cache
+ * attributes. This should usually be identical to the source reference
+ * set on the cache interceptor itself.
+ * @see CacheInterceptor#setCacheAttributeSource
+ */
+ public void setCacheDefinitionSource(CacheDefinitionSource cacheDefinitionSource) {
+ this.cacheDefinitionSource = cacheDefinitionSource;
+ }
+
+ /**
+ * Set the {@link ClassFilter} to use for this pointcut.
+ * Default is {@link ClassFilter#TRUE}.
+ */
+ public void setClassFilter(ClassFilter classFilter) {
+ this.pointcut.setClassFilter(classFilter);
+ }
+
+ public Pointcut getPointcut() {
+ return this.pointcut;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
new file mode 100644
index 00000000000..a82bf237f64
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.KeyGenerator;
+import org.springframework.cache.support.DefaultKeyGenerator;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Base class for caching aspects, such as the {@link CacheInterceptor}
+ * or an AspectJ aspect.
+ *
+ *
This enables the underlying Spring caching infrastructure to be used easily
+ * to implement an aspect for any aspect system.
+ *
+ *
Subclasses are responsible for calling methods in this class in the correct order.
+ *
+ *
If no caching name has been specified in the CacheOperationDefinition,
+ * the exposed name will be the fully-qualified class name + "." + method name
+ * (by default).
+ *
+ *
Uses the Strategy design pattern. A CacheManager
+ * implementation will perform the actual transaction management, and a
+ * CacheDefinitionSource is used for determining caching operation definitions.
+ *
+ *
A cache aspect is serializable if its CacheManager
+ * and CacheDefinitionSource are serializable.
+ *
+ * @author Costin Leau
+ */
+public abstract class CacheAspectSupport implements InitializingBean {
+
+ private static class EmptyHolder implements Serializable {
+ }
+
+ private static final Object NULL_RETURN = new EmptyHolder();
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private CacheManager cacheManager;
+
+ private CacheDefinitionSource cacheDefinitionSource;
+
+ private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
+
+ public void afterPropertiesSet() {
+ if (this.cacheManager == null) {
+ throw new IllegalStateException("Setting the property 'cacheManager' is required");
+ }
+ if (this.cacheDefinitionSource == null) {
+ throw new IllegalStateException("Either 'cacheDefinitionSource' or 'cacheDefinitionSources' is required: "
+ + "If there are no cacheable methods, then don't use a cache aspect.");
+ }
+ }
+
+ /**
+ * Convenience method to return a String representation of this Method
+ * for use in logging. Can be overridden in subclasses to provide a
+ * different identifier for the given method.
+ * @param method the method we're interested in
+ * @param targetClass class the method is on
+ * @return log message identifying this method
+ * @see org.springframework.util.ClassUtils#getQualifiedMethodName
+ */
+ protected String methodIdentification(Method method, Class> targetClass) {
+ Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
+ return ClassUtils.getQualifiedMethodName(specificMethod);
+ }
+
+ public CacheManager getCacheManager() {
+ return cacheManager;
+ }
+
+ public void setCacheManager(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ public CacheDefinitionSource getCacheDefinitionSource() {
+ return cacheDefinitionSource;
+ }
+
+ /**
+ * Set multiple cache definition sources which are used to find the cache
+ * attributes. Will build a CompositeCachingDefinitionSource for the given sources.
+ */
+ public void setCacheDefinitionSources(CacheDefinitionSource... cacheDefinitionSources) {
+ Assert.notEmpty(cacheDefinitionSources);
+ this.cacheDefinitionSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheDefinitionSource(
+ cacheDefinitionSources) : cacheDefinitionSources[0]);
+ }
+
+ protected Cache getCache(CacheDefinition definition) {
+ // TODO: add support for multiple caches
+ // TODO: add behaviour for the default cache
+ String name = definition.getCacheName();
+ if (!StringUtils.hasText(name)) {
+ name = cacheManager.getCacheNames().iterator().next();
+ }
+ return cacheManager.getCache(name);
+ }
+
+ protected CacheOperationContext getOperationContext(CacheDefinition definition, Method method, Object[] args, Class> targetClass) {
+ return new CacheOperationContext(definition, method, args, targetClass);
+ }
+
+ protected Object execute(Callable invocation, Object target, Method method, Object[] args) throws Exception {
+ // get backing class
+ Class> targetClass = AopProxyUtils.ultimateTargetClass(target);
+
+ if (targetClass == null && target != null) {
+ targetClass = target.getClass();
+ }
+
+ final CacheDefinition cacheDef = getCacheDefinitionSource().getCacheDefinition(method,
+ targetClass);
+
+ Object retVal = null;
+
+ // analyze caching information
+ if (cacheDef != null) {
+ CacheOperationContext context = getOperationContext(cacheDef, method, args, targetClass);
+ Cache cache = (Cache) context.getCache();
+
+ if (context.hasConditionPassed()) {
+ // check operation
+ if (cacheDef instanceof CacheUpdateDefinition) {
+ // check cache first
+ Object key = context.generateKey();
+ retVal = cache.get(key);
+ if (retVal == null) {
+ retVal = invocation.call();
+ cache.put(key, (retVal == null ? NULL_RETURN : retVal));
+ }
+ }
+
+ if (cacheDef instanceof CacheInvalidateDefinition) {
+ CacheInvalidateDefinition invalidateDef = (CacheInvalidateDefinition) cacheDef;
+ retVal = invocation.call();
+
+ // flush the cache (ignore arguments)
+ if (invalidateDef.isCacheWide()) {
+ cache.clear();
+ }
+ else {
+ // check key
+ Object key = context.generateKey();
+ cache.remove(key);
+ }
+ }
+
+ return retVal;
+ }
+ }
+
+ return invocation.call();
+ }
+
+ protected class CacheOperationContext {
+
+ private CacheDefinition definition;
+ private final Cache, ?> cache;
+ private final Method method;
+ private final Object[] args;
+
+ // context passed around to avoid multiple creations
+ private final EvaluationContext evalContext;
+
+ private final KeyGenerator keyGenerator = new DefaultKeyGenerator();
+
+ public CacheOperationContext(CacheDefinition operationDefinition, Method method, Object[] args,
+ Class> targetClass) {
+ this.definition = operationDefinition;
+ this.cache = CacheAspectSupport.this.getCache(definition);
+ this.method = method;
+ this.args = args;
+
+ this.evalContext = evaluator.createEvaluationContext(cache, method, args, targetClass);
+ }
+
+ /**
+ * Evaluates the definition condition.
+ *
+ * @param definition
+ * @return
+ */
+ protected boolean hasConditionPassed() {
+ if (StringUtils.hasText(definition.getCondition())) {
+ return evaluator.condition(definition.getCondition(), method, evalContext);
+ }
+ return true;
+ }
+
+ /**
+ * Computes the key for the given caching definition.
+ *
+ * @param definition
+ * @param method method being invoked
+ * @param objects arguments passed during the method invocation
+ * @return generated key (null if none can be generated)
+ */
+ protected Object generateKey() {
+ if (StringUtils.hasText(definition.getKey())) {
+ return evaluator.key(definition.getKey(), method, evalContext);
+ }
+
+ return keyGenerator.extract(args);
+ }
+
+ protected Cache, ?> getCache() {
+ return cache;
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinition.java
new file mode 100644
index 00000000000..565d358b2f3
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinition.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * Interface describing Spring-compliant caching operation.
+ *
+ * @author Costin Leau
+ */
+public interface CacheDefinition {
+
+ /**
+ * Returns the name of this operation. Can be null .
+ * In case of Spring's declarative caching, the exposed name will be:
+ * fully qualified class name.method name .
+ *
+ * @return the operation name
+ */
+ String getName();
+
+ /**
+ * Returns the name of the cache against which this operation is performed.
+ *
+ * @return name of the cache on which the operation is performed.
+ */
+ String getCacheName();
+
+ /**
+ * Returns the SpEL expression conditioning the operation.
+ *
+ * @return operation condition (as SpEL expression).
+ */
+ String getCondition();
+
+ /**
+ * Returns the SpEL expression identifying the cache key.
+ *
+ * @return
+ */
+ String getKey();
+
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSource.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSource.java
new file mode 100644
index 00000000000..c719861c004
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 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.lang.reflect.Method;
+
+
+/**
+ * Interface used by CacheInterceptor. Implementations know
+ * how to source cache operation attributes, whether from configuration,
+ * metadata attributes at source level, or anywhere else.
+ *
+ * @author Costin Leau
+ */
+public interface CacheDefinitionSource {
+
+ /**
+ * Return the cache operation definition for this method.
+ * Return null if the method is not cacheable.
+ * @param method method
+ * @param targetClass target class. May be null, in which
+ * case the declaring class of the method must be used.
+ * @return {@link CacheDefinition} the matching cache operation definition,
+ * or null if none found
+ */
+ CacheDefinition getCacheDefinition(Method method, Class> targetClass);
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSourcePointcut.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSourcePointcut.java
new file mode 100644
index 00000000000..8ed9ae1f4ec
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheDefinitionSourcePointcut.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.reflect.Method;
+
+import org.springframework.aop.support.StaticMethodMatcherPointcut;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Inner class that implements a Pointcut that matches if the underlying
+ * {@link CacheDefinitionSource} has an attribute for a given method.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+abstract class CacheDefinitionSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
+
+ public boolean matches(Method method, Class> targetClass) {
+ CacheDefinitionSource cas = getCacheDefinitionSource();
+ return (cas == null || cas.getCacheDefinition(method, targetClass) != null);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof CacheDefinitionSourcePointcut)) {
+ return false;
+ }
+ CacheDefinitionSourcePointcut otherPc = (CacheDefinitionSourcePointcut) other;
+ return ObjectUtils.nullSafeEquals(getCacheDefinitionSource(),
+ otherPc.getCacheDefinitionSource());
+ }
+
+ @Override
+ public int hashCode() {
+ return CacheDefinitionSourcePointcut.class.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + ": " + getCacheDefinitionSource();
+ }
+
+
+ /**
+ * Obtain the underlying CacheOperationDefinitionSource (may be null).
+ * To be implemented by subclasses.
+ */
+ protected abstract CacheDefinitionSource getCacheDefinitionSource();
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheExpressionRootObject.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheExpressionRootObject.java
new file mode 100644
index 00000000000..502368ee6b7
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheExpressionRootObject.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 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 org.springframework.cache.Cache;
+
+/**
+ * Interface describing the root object used during the expression evaluation.
+ *
+ * @author Costin Leau
+ */
+interface CacheExpressionRootObject {
+
+ /**
+ * Returns the name of the method being cached.
+ *
+ * @return name of the cached method.
+ */
+ String getMethodName();
+
+ /**
+ * Returns the cache against which the method is executed.
+ *
+ * @return current cache
+ */
+ Cache getCache();
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java
new file mode 100644
index 00000000000..7791cdedee0
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * AOP Alliance MethodInterceptor for declarative cache
+ * management using the common Spring caching infrastructure
+ * ({@link org.springframework.cache.Cache}).
+ *
+ * Derives from the {@link CacheAspectSupport} class which
+ * contains the integration with Spring's underlying caching API.
+ * CacheInterceptor simply calls the relevant superclass methods
+ * in the correct order.
+ *
+ *
CacheInterceptors are thread-safe.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
+
+ @SuppressWarnings("unchecked")
+ public Object invoke(final MethodInvocation invocation) throws Throwable {
+ Method method = invocation.getMethod();
+
+ Callable aopAllianceInvocation = new Callable() {
+
+ public Object call() throws Exception {
+ try {
+ return invocation.proceed();
+ } catch (Throwable th) {
+ if (th instanceof Exception) {
+ throw (Exception) th;
+ }
+ throw (Error) th;
+ }
+ }
+ };
+
+ return execute(aopAllianceInvocation, invocation.getThis(), method, invocation.getArguments());
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInvalidateDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInvalidateDefinition.java
new file mode 100644
index 00000000000..872ab00c752
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheInvalidateDefinition.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 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;
+
+
+/**
+ * Interface describing a Spring cache invalidation.
+ *
+ * @author Costin Leau
+ */
+public interface CacheInvalidateDefinition extends CacheDefinition {
+
+ /**
+ * Returns whether the operation affects the entire cache or not.
+ *
+ * @return whether the operation is cache wide or not.
+ */
+ boolean isCacheWide();
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java
new file mode 100644
index 00000000000..3604308a60b
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheProxyFactoryBean.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010 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 org.springframework.aop.Pointcut;
+import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean;
+
+/**
+ * Proxy factory bean for simplified declarative caching handling.
+ * This is a convenient alternative to a standard AOP
+ * {@link org.springframework.aop.framework.ProxyFactoryBean}
+ * with a separate {@link CachingInterceptor} definition.
+ *
+ * This class is intended to cover the typical case of declarative
+ * transaction demarcation: namely, wrapping a singleton target object with a
+ * caching proxy, proxying all the interfaces that the target implements.
+ *
+ * @author Costin Leau
+ * @see org.springframework.aop.framework.ProxyFactoryBean
+ * @see CachingInterceptor
+ */
+public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
+
+ private final CacheInterceptor cachingInterceptor = new CacheInterceptor();
+ private Pointcut pointcut;
+
+ @Override
+ protected Object createMainInterceptor() {
+ return null;
+ }
+
+ /**
+ * Set the caching attribute source which is used to find the cache operation
+ * definition.
+ *
+ * @param cacheDefinitionSources cache definition sources
+ */
+ public void setCacheDefinitionSources(CacheDefinitionSource... cacheDefinitionSources) {
+ this.cachingInterceptor.setCacheDefinitionSources(cacheDefinitionSources);
+ }
+
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheUpdateDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheUpdateDefinition.java
new file mode 100644
index 00000000000..e5e9caf8ac2
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CacheUpdateDefinition.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * Interface describing a Spring cache update.
+ *
+ * @author Costin Leau
+ */
+public interface CacheUpdateDefinition extends CacheDefinition {
+
+ /**
+ * Returns the SpEL expression identifying the cache key.
+ *
+ * @return
+ */
+ String getKey();
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CompositeCacheDefinitionSource.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CompositeCacheDefinitionSource.java
new file mode 100644
index 00000000000..b6b5319c69b
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/CompositeCacheDefinitionSource.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 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.io.Serializable;
+import java.lang.reflect.Method;
+
+import org.springframework.util.Assert;
+
+/**
+ * Composite {@link CacheDefinitionSource} implementation that iterates
+ * over a given array of {@link CacheDefinitionSource} instances.
+ *
+ * @author Costin Leau
+ */
+@SuppressWarnings("serial")
+public class CompositeCacheDefinitionSource implements CacheDefinitionSource, Serializable {
+
+ private final CacheDefinitionSource[] cacheDefinitionSources;
+
+ /**
+ * Create a new CompositeCachingDefinitionSource for the given sources.
+ * @param cacheDefinitionSourcess the CacheDefinitionSource instances to combine
+ */
+ public CompositeCacheDefinitionSource(CacheDefinitionSource[] cacheDefinitionSources) {
+ Assert.notNull(cacheDefinitionSources, "cacheDefinitionSource array must not be null");
+ this.cacheDefinitionSources = cacheDefinitionSources;
+ }
+
+ /**
+ * Return the CacheDefinitionSource instances that this
+ * CompositeCachingDefinitionSource combines.
+ */
+ public final CacheDefinitionSource[] getCacheDefinitionSources() {
+ return this.cacheDefinitionSources;
+ }
+
+
+ public CacheDefinition getCacheDefinition(Method method, Class> targetClass) {
+ for (CacheDefinitionSource source : cacheDefinitionSources) {
+ CacheDefinition definition = source.getCacheDefinition(method, targetClass);
+ if (definition != null) {
+ return definition;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheExpressionRootObject.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheExpressionRootObject.java
new file mode 100644
index 00000000000..5a2a15989ca
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheExpressionRootObject.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 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 org.springframework.cache.Cache;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of expression root object.
+ *
+ * @author Costin Leau
+ */
+public class DefaultCacheExpressionRootObject implements CacheExpressionRootObject {
+
+ private final String methodName;
+ private final Cache cache;
+
+ public DefaultCacheExpressionRootObject(Cache cache, String methodName) {
+ Assert.hasText(methodName, "method name is required");
+ this.methodName = methodName;
+ this.cache = cache;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public Cache getCache() {
+ return cache;
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheInvalidateDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheInvalidateDefinition.java
new file mode 100644
index 00000000000..5c1badd6f8b
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheInvalidateDefinition.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * Default implementation of the {@link CacheInvalidateDefinition} interface.
+ *
+ * @author Costin Leau
+ */
+public class DefaultCacheInvalidateDefinition extends AbstractCacheDefinition implements
+ CacheInvalidateDefinition {
+
+ private boolean cacheWide = false;
+
+ public boolean isCacheWide() {
+ return cacheWide;
+ }
+
+ public void setCacheWide(boolean cacheWide) {
+ this.cacheWide = cacheWide;
+ }
+
+ @Override
+ protected StringBuilder getDefinitionDescription() {
+ StringBuilder sb = super.getDefinitionDescription();
+ sb.append(",");
+ sb.append(cacheWide);
+ return sb;
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheUpdateDefinition.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheUpdateDefinition.java
new file mode 100644
index 00000000000..8040ad5fcda
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/DefaultCacheUpdateDefinition.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * Default implementation of the {@link CacheUpdateDefinition} interface.
+ *
+ * @author Costin Leau
+ */
+public class DefaultCacheUpdateDefinition extends AbstractCacheDefinition implements CacheUpdateDefinition {
+
+
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java
new file mode 100644
index 00000000000..01203f8c39f
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 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.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.cache.Cache;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+/**
+ * Utility class handling the SpEL expression parsing.
+ * Meant to be used as a reusable, thread-safe component.
+ *
+ * Performs internal caching for performance reasons.
+ *
+ * @author Costin Leau
+ */
+class ExpressionEvaluator {
+
+ private SpelExpressionParser parser = new SpelExpressionParser();
+ // shared param discoverer since it caches data internally
+ private ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
+
+ private Map conditionCache = new ConcurrentHashMap();
+ private Map keyCache = new ConcurrentHashMap();
+ private Map targetMethodCache = new ConcurrentHashMap();
+
+ EvaluationContext createEvaluationContext(Cache, ?> cache, Method method, Object[] args, Class> targetClass) {
+ DefaultCacheExpressionRootObject rootObject = new DefaultCacheExpressionRootObject(cache, method.getName());
+ StandardEvaluationContext evaluationContext = new LazyParamAwareEvaluationContext(rootObject,
+ paramNameDiscoverer, method, args, targetClass, targetMethodCache);
+
+ return evaluationContext;
+ }
+
+ boolean condition(String conditionExpression, Method method, EvaluationContext evalContext) {
+ Expression condExp = conditionCache.get(conditionExpression);
+ if (condExp == null) {
+ condExp = parser.parseExpression(conditionExpression);
+ conditionCache.put(method, condExp);
+ }
+ return condExp.getValue(evalContext, boolean.class);
+ }
+
+ Object key(String keyExpression, Method method, EvaluationContext evalContext) {
+ Expression keyExp = keyCache.get(keyExpression);
+ if (keyExp == null) {
+ keyExp = parser.parseExpression(keyExpression);
+ keyCache.put(method, keyExp);
+ }
+ return keyExp.getValue(evalContext);
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java
new file mode 100644
index 00000000000..8cae8fb2e1e
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2010 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.lang.reflect.Method;
+import java.util.Map;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Evaluation context class that adds a method parameters as SpEL variables,
+ * in a lazy manner. The lazy nature eliminates unneeded parsing of classes
+ * byte code for parameter discovery.
+ *
+ * To limit the creation of objects, an ugly constructor is used (rather then a
+ * dedicated 'closure'-like class for deferred execution).
+ *
+ * @author Costin Leau
+ */
+class LazyParamAwareEvaluationContext extends StandardEvaluationContext {
+
+ private final ParameterNameDiscoverer paramDiscoverer;
+ private final Method method;
+ private final Object[] args;
+ private Class> targetClass;
+ private Map methodCache;
+
+ private boolean paramLoaded = false;
+
+ LazyParamAwareEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
+ Object[] args, Class> targetClass, Map methodCache) {
+ this.paramDiscoverer = paramDiscoverer;
+ this.method = method;
+ this.args = args;
+ this.targetClass = targetClass;
+ this.methodCache = methodCache;
+ }
+
+ /**
+ * Load the param information only when needed.
+ */
+ @Override
+ public Object lookupVariable(String name) {
+ Object variable = super.lookupVariable(name);
+ if (variable != null) {
+ return variable;
+ }
+
+ if (!paramLoaded) {
+ paramLoaded = true;
+ loadArgsAsVariables();
+ variable = super.lookupVariable(name);
+ }
+
+ return variable;
+ }
+
+ private void loadArgsAsVariables() {
+ // shortcut if no args need to be loaded
+ if (ObjectUtils.isEmpty(args)) {
+ return;
+ }
+
+ Method targetMethod = methodCache.get(method);
+ if (targetMethod == null) {
+ targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ if (targetMethod == null) {
+ targetMethod = method;
+ }
+ methodCache.put(method, targetMethod);
+ }
+
+ // save arguments as indexed variables
+ for (int i = 0; i < args.length; i++) {
+ super.setVariable("p" + i, args[i]);
+ }
+
+ String[] parameterNames = paramDiscoverer.getParameterNames(targetMethod);
+ // save parameter names (if discovered)
+ if (parameterNames != null) {
+ for (int i = 0; i < parameterNames.length; i++) {
+ super.setVariable(parameterNames[i], args[i]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
new file mode 100644
index 00000000000..e0a805edb91
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractCacheManager.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 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.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * Abstract base class implementing the common CacheManager methods. Useful for 'static' environments where the
+ * backing caches do not change.
+ *
+ * @author Costin Leau
+ */
+public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
+
+ // fast lookup by name map
+ private final ConcurrentMap> caches = new ConcurrentHashMap>();
+ private Collection names;
+
+ public void afterPropertiesSet() {
+ Collection> cacheSet = loadCaches();
+
+ Assert.notEmpty(cacheSet);
+
+ caches.clear();
+
+ // preserve the initial order of the cache names
+ Set cacheNames = new LinkedHashSet(cacheSet.size());
+
+ for (Cache, ?> cache : cacheSet) {
+ caches.put(cache.getName(), cache);
+ cacheNames.add(cache.getName());
+ }
+
+ names = Collections.unmodifiableSet(cacheNames);
+ }
+
+ /**
+ * Loads the caches into the cache manager. Occurs at startup.
+ * The returned collection should not be null.
+ *
+ * @param caches the collection of caches handled by the manager
+ */
+ protected abstract Collection> loadCaches();
+
+ /**
+ * Returns the internal cache map.
+ *
+ * @return internal cache map
+ */
+ protected final ConcurrentMap> getCacheMap() {
+ return caches;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Cache getCache(String name) {
+ return (Cache) caches.get(name);
+ }
+
+ public Collection getCacheNames() {
+ return names;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractDelegatingCache.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractDelegatingCache.java
new file mode 100644
index 00000000000..354798ea769
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/AbstractDelegatingCache.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 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.support;
+
+import java.util.Map;
+
+import org.springframework.cache.Cache;
+import org.springframework.util.Assert;
+
+/**
+ * Abstract base class delegating most of the {@link Map}-like methods
+ * to the underlying cache.
+ *
+ * @author Costin Leau
+ */
+public abstract class AbstractDelegatingCache implements Cache {
+
+ private final Map delegate;
+
+ public > AbstractDelegatingCache(D delegate) {
+ Assert.notNull(delegate);
+ this.delegate = delegate;
+ }
+
+ public void clear() {
+ delegate.clear();
+ }
+
+ public boolean containsKey(Object key) {
+ return delegate.containsKey(key);
+ }
+
+ public V get(Object key) {
+ return delegate.get(key);
+ }
+
+ public V put(K key, V value) {
+ return delegate.put(key, value);
+ }
+
+ public V remove(Object key) {
+ return delegate.remove(key);
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/CompositeCacheManager.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/CompositeCacheManager.java
new file mode 100644
index 00000000000..6d44cb84250
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/CompositeCacheManager.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 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.support;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * Composite {@link CacheManager} implementation that iterates
+ * over a given collection of {@link CacheManager} instances.
+ *
+ * @author Costin Leau
+ */
+public class CompositeCacheManager implements CacheManager {
+
+ private CacheManager[] cacheManagers;
+
+ public Cache getCache(String name) {
+ Cache cache = null;
+ for (CacheManager cacheManager : cacheManagers) {
+ cache = cacheManager.getCache(name);
+ if (cache != null) {
+ return cache;
+ }
+ }
+
+ return cache;
+ }
+
+ public Collection getCacheNames() {
+ List names = new ArrayList();
+ for (CacheManager manager : cacheManagers) {
+ names.addAll(manager.getCacheNames());
+ }
+ return Collections.unmodifiableCollection(names);
+ }
+
+ public void setCacheManagers(CacheManager[] cacheManagers) {
+ Assert.notEmpty(cacheManagers, "non-null/empty array required");
+ this.cacheManagers = cacheManagers.clone();
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/DefaultKeyGenerator.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/DefaultKeyGenerator.java
new file mode 100644
index 00000000000..a1d640634db
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/DefaultKeyGenerator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.support;
+
+import org.springframework.cache.KeyGenerator;
+
+/**
+ * @author Costin Leau
+ */
+public class DefaultKeyGenerator implements KeyGenerator {
+
+ public Object extract(Object... params) {
+ int hashCode = 17;
+
+ for (Object object : params) {
+ hashCode = 31 * hashCode + object.hashCode();
+ }
+
+ return Integer.valueOf(hashCode);
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/MapCacheManager.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/MapCacheManager.java
new file mode 100644
index 00000000000..7fe17d0d97d
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/MapCacheManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 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.support;
+
+import java.util.Collection;
+
+import org.springframework.cache.Cache;
+
+/**
+ * Simple cache manager working against a given collection of caches. Useful for testing or simple
+ * caching declarations.
+ *
+ * @author Costin Leau
+ */
+public class MapCacheManager extends AbstractCacheManager {
+
+ private Collection> caches;
+
+ @Override
+ protected Collection> loadCaches() {
+ return caches;
+ }
+
+ public void setCaches(Collection> caches) {
+ this.caches = caches;
+ }
+}
diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/support/SimpleMapEntry.java b/org.springframework.context.support/src/main/java/org/springframework/cache/support/SimpleMapEntry.java
new file mode 100644
index 00000000000..8179f96e9d1
--- /dev/null
+++ b/org.springframework.context.support/src/main/java/org/springframework/cache/support/SimpleMapEntry.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 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.support;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Basic {@link Entry} implementation.
+ *
+ * @author Costin Leau
+ */
+public class SimpleMapEntry implements Entry {
+ private K key;
+ private V value;
+
+ public SimpleMapEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public SimpleMapEntry(Map.Entry e) {
+ this.key = e.getKey();
+ this.value = e.getValue();
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry e = (Map.Entry) o;
+ return eq(key, e.getKey()) && eq(value, e.getValue());
+ }
+
+ public int hashCode() {
+ return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
+ }
+
+ public String toString() {
+ return key + "=" + value;
+ }
+
+ static boolean eq(Object o1, Object o2) {
+ return (o1 == null ? o2 == null : o1.equals(o2));
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/resources/META-INF/spring.handlers b/org.springframework.context.support/src/main/resources/META-INF/spring.handlers
new file mode 100644
index 00000000000..56f14af426e
--- /dev/null
+++ b/org.springframework.context.support/src/main/resources/META-INF/spring.handlers
@@ -0,0 +1 @@
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
diff --git a/org.springframework.context.support/src/main/resources/META-INF/spring.schemas b/org.springframework.context.support/src/main/resources/META-INF/spring.schemas
new file mode 100644
index 00000000000..bb49cc50198
--- /dev/null
+++ b/org.springframework.context.support/src/main/resources/META-INF/spring.schemas
@@ -0,0 +1,2 @@
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
diff --git a/org.springframework.context.support/src/main/resources/META-INF/spring.tooling b/org.springframework.context.support/src/main/resources/META-INF/spring.tooling
new file mode 100644
index 00000000000..2db6d54b1f9
--- /dev/null
+++ b/org.springframework.context.support/src/main/resources/META-INF/spring.tooling
@@ -0,0 +1,4 @@
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
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..5fade140cbd
--- /dev/null
+++ b/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache-3.1.xsd
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache.gif b/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache.gif
new file mode 100644
index 00000000000..d9929998ab9
Binary files /dev/null and b/org.springframework.context.support/src/main/resources/org/springframework/cache/config/spring-cache.gif differ
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTest.java
new file mode 100644
index 00000000000..ede51f05cbd
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/concurrent/ConcurrentCacheTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 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.concurrent;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.vendor.AbstractNativeCacheTest;
+
+/**
+ * @author Costin Leau
+ */
+public class ConcurrentCacheTest extends AbstractNativeCacheTest> {
+
+ @Override
+ protected Cache createCache(ConcurrentMap nativeCache) {
+ return new ConcurrentCache(nativeCache, CACHE_NAME);
+ }
+
+ @Override
+ protected ConcurrentMap createNativeCache() throws Exception {
+ return new ConcurrentHashMap();
+ }
+}
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/AbstractAnnotationTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AbstractAnnotationTest.java
new file mode 100644
index 00000000000..f9a833e4a8e
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AbstractAnnotationTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 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.config;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+/**
+ * Abstract annotation test (containing several reusable methods).
+ * @author Costin Leau
+ */
+public abstract class AbstractAnnotationTest {
+
+ protected ApplicationContext ctx;
+
+ protected CacheableService cs;
+
+ protected CacheableService ccs;
+
+ protected abstract String getConfig();
+
+ @Before
+ public void setup() {
+ ctx = new ClassPathXmlApplicationContext(getConfig());
+ cs = ctx.getBean("service", CacheableService.class);
+ ccs = ctx.getBean("classService", CacheableService.class);
+ }
+
+ public void testCacheable(CacheableService service) throws Exception {
+ Object o1 = new Object();
+ Object o2 = new Object();
+
+ Object r1 = service.cache(o1);
+ Object r2 = service.cache(o1);
+ Object r3 = service.cache(o1);
+
+ assertSame(r1, r2);
+ assertSame(r1, r3);
+ }
+
+ public void testInvalidate(CacheableService service) throws Exception {
+ Object o1 = new Object();
+ Object o2 = new Object();
+
+ Object r1 = service.cache(o1);
+ Object r2 = service.cache(o1);
+
+ assertSame(r1, r2);
+ service.invalidate(o1);
+ Object r3 = service.cache(o1);
+ Object r4 = service.cache(o1);
+ assertNotSame(r1, r3);
+ assertSame(r3, r4);
+ }
+
+ public void testConditionalExpression(CacheableService service) throws Exception {
+ Object r1 = service.conditional(4);
+ Object r2 = service.conditional(4);
+
+ assertNotSame(r1, r2);
+
+ Object r3 = service.conditional(3);
+ Object r4 = service.conditional(3);
+
+ assertSame(r3, r4);
+ }
+
+ public void testKeyExpression(CacheableService service) throws Exception {
+ Object r1 = service.key(5, 1);
+ Object r2 = service.key(5, 2);
+
+ assertSame(r1, r2);
+
+ Object r3 = service.key(1, 5);
+ Object r4 = service.key(2, 5);
+
+ assertNotSame(r3, r4);
+ }
+
+ @Test
+ public void testCacheable() throws Exception {
+ testCacheable(cs);
+ }
+
+ @Test
+ public void testInvalidate() throws Exception {
+ testInvalidate(cs);
+ }
+
+ @Test
+ public void testConditionalExpression() throws Exception {
+ testConditionalExpression(cs);
+ }
+
+ @Test
+ public void testKeyExpression() throws Exception {
+ testKeyExpression(cs);
+ }
+
+ @Test
+ public void testClassCacheCacheable() throws Exception {
+ testCacheable(ccs);
+ }
+
+ @Test
+ public void testClassCacheInvalidate() throws Exception {
+ testInvalidate(ccs);
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
new file mode 100644
index 00000000000..46db113e9f1
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 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.config;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+/**
+ * @author Costin Leau
+ */
+@Cacheable
+public class AnnotatedClassCacheableService implements CacheableService {
+
+ private AtomicLong counter = new AtomicLong();
+
+ public Object cache(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ public Object conditional(int field) {
+ return null;
+ }
+
+ @CacheEvict
+ public void invalidate(Object arg1) {
+ }
+
+ @Cacheable(key = "#p0")
+ public Object key(Object arg1, Object arg2) {
+ return counter.getAndIncrement();
+ }
+}
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTest.java
new file mode 100644
index 00000000000..57f68bf45fb
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 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.config;
+
+
+/**
+ * @author Costin Leau
+ */
+public class AnnotationNamespaceDrivenTest extends AbstractAnnotationTest {
+
+ @Override
+ protected String getConfig() {
+ return "/org/springframework/cache/config/annotationDrivenCacheNamespace.xml";
+ }
+
+}
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationTest.java
new file mode 100644
index 00000000000..50d6cf6fa42
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/AnnotationTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.config;
+
+
+/**
+ * @author Costin Leau
+ */
+public class AnnotationTest extends AbstractAnnotationTest {
+
+ @Override
+ protected String getConfig() {
+ return "/org/springframework/cache/config/annotationDrivenCacheConfig.xml";
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/CacheableService.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/CacheableService.java
new file mode 100644
index 00000000000..5ec9b0c155b
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/CacheableService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.config;
+
+/**
+ * Basic service interface.
+ *
+ * @author Costin Leau
+ */
+public interface CacheableService {
+
+ T cache(Object arg1);
+
+ void invalidate(Object arg1);
+
+ T conditional(int field);
+
+ T key(Object arg1, Object arg2);
+
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/config/DefaultCacheableService.java b/org.springframework.context.support/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
new file mode 100644
index 00000000000..f5baac7780e
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/config/DefaultCacheableService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.config;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+
+/**
+ * Simple cacheable service
+ *
+ * @author Costin Leau
+ */
+public class DefaultCacheableService implements CacheableService {
+
+ private AtomicLong counter = new AtomicLong();
+
+ @Cacheable
+ public Long cache(Object arg1) {
+ return counter.getAndIncrement();
+ }
+
+ @CacheEvict
+ public void invalidate(Object arg1) {
+ }
+
+ @Cacheable(condition = "#classField == 3")
+ public Long conditional(int classField) {
+ return counter.getAndIncrement();
+ }
+
+ @Cacheable(key = "#p0")
+ public Long key(Object arg1, Object arg2) {
+ return counter.getAndIncrement();
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTest.java
new file mode 100644
index 00000000000..2d5b70b7e36
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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.ehcache;
+
+import net.sf.ehcache.Ehcache;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.vendor.AbstractNativeCacheTest;
+
+/**
+ * Integration test for EhCache cache.
+ *
+ * @author Costin Leau
+ */
+public class EhCacheCacheTest extends AbstractNativeCacheTest {
+
+ @Override
+ protected Ehcache createNativeCache() throws Exception {
+ EhCacheFactoryBean fb = new EhCacheFactoryBean();
+ fb.setBeanName(CACHE_NAME);
+ fb.setCacheName(CACHE_NAME);
+ fb.afterPropertiesSet();
+ return fb.getObject();
+ }
+
+ @Override
+ protected Cache createCache(Ehcache nativeCache) {
+ return new EhCacheCache(nativeCache);
+ }
+}
diff --git a/org.springframework.context.support/src/test/java/org/springframework/cache/vendor/AbstractNativeCacheTest.java b/org.springframework.context.support/src/test/java/org/springframework/cache/vendor/AbstractNativeCacheTest.java
new file mode 100644
index 00000000000..5fd03f6a57b
--- /dev/null
+++ b/org.springframework.context.support/src/test/java/org/springframework/cache/vendor/AbstractNativeCacheTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2010 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.vendor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.cache.Cache;
+
+/**
+ * Test for native cache implementations.
+ *
+ * @author Costin Leau
+ */
+public abstract class AbstractNativeCacheTest {
+
+ private T nativeCache;
+ private Cache cache;
+ protected final static String CACHE_NAME = "testCache";
+
+ @Before
+ public void setUp() throws Exception {
+ nativeCache = createNativeCache();
+ cache = createCache(nativeCache);
+ cache.clear();
+ }
+
+
+ protected abstract T createNativeCache() throws Exception;
+
+ protected abstract Cache createCache(T nativeCache);
+
+
+ @Test
+ public void testCacheName() throws Exception {
+ assertEquals(CACHE_NAME, cache.getName());
+ }
+
+ @Test
+ public void testNativeCache() throws Exception {
+ assertSame(nativeCache, cache.getNativeCache());
+ }
+
+ @Test
+ public void testCachePut() throws Exception {
+
+ Object key = "enescu";
+ Object value = "george";
+
+ assertNull(cache.get(key));
+ cache.put(key, value);
+ assertEquals(value, cache.get(key));
+ }
+
+ @Test
+ public void testCacheRemove() throws Exception {
+ Object key = "enescu";
+ Object value = "george";
+
+ assertNull(cache.get(key));
+ cache.put(key, value);
+ assertEquals(value, cache.remove(key));
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void testCacheClear() throws Exception {
+ assertNull(cache.get("enescu"));
+ cache.put("enescu", "george");
+ assertNull(cache.get("vlaicu"));
+ cache.put("vlaicu", "aurel");
+ cache.clear();
+ assertNull(cache.get("vlaicu"));
+ assertNull(cache.get("enescu"));
+ }
+
+ // concurrent map tests
+ @Test
+ public void testPutIfAbsent() throws Exception {
+ Object key = "enescu";
+ Object value1 = "george";
+ Object value2 = "geo";
+
+ assertNull(cache.get("enescu"));
+ cache.put(key, value1);
+ cache.putIfAbsent(key, value2);
+ assertEquals(value1, cache.get(key));
+ }
+
+ @Test
+ public void testConcurrentRemove() throws Exception {
+ Object key = "enescu";
+ Object value1 = "george";
+ Object value2 = "geo";
+
+ assertNull(cache.get("enescu"));
+ cache.put(key, value1);
+ // no remove
+ cache.remove(key, value2);
+ assertEquals(value1, cache.get(key));
+ // one remove
+ cache.remove(key, value1);
+ assertNull(cache.get("enescu"));
+ }
+
+ @Test
+ public void testConcurrentReplace() throws Exception {
+ Object key = "enescu";
+ Object value1 = "george";
+ Object value2 = "geo";
+
+ assertNull(cache.get("enescu"));
+ cache.put(key, value1);
+ cache.replace(key, value2);
+ assertEquals(value2, cache.get(key));
+ cache.remove(key);
+ cache.replace(key, value1);
+ assertNull(cache.get("enescu"));
+ }
+
+ @Test
+ public void testConcurrentReplaceIfEqual() throws Exception {
+ Object key = "enescu";
+ Object value1 = "george";
+ Object value2 = "geo";
+
+ assertNull(cache.get("enescu"));
+ cache.put(key, value1);
+ assertEquals(value1, cache.get(key));
+ // no replace
+ cache.replace(key, value2, value1);
+ assertEquals(value1, cache.get(key));
+ cache.replace(key, value1, value2);
+ assertEquals(value2, cache.get(key));
+ cache.replace(key, value2, value1);
+ assertEquals(value1, cache.get(key));
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml b/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml
new file mode 100644
index 00000000000..93464bd8d49
--- /dev/null
+++ b/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml b/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml
new file mode 100644
index 00000000000..d8b87d79b8f
--- /dev/null
+++ b/org.springframework.context.support/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+