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 m) { + for (Map.Entry 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 @@ + + + + + + + + + + + + + + + + + + + + + + + +