, BasicCacheOperation {
+
+ /**
+ * Return the {@link CacheResolver} instance to use to resolve the cache to
+ * use for this operation.
+ */
+ CacheResolver getCacheResolver();
+
+ /**
+ * Return the {@link CacheInvocationParameter} instances based on the specified
+ * method arguments.
+ * The method arguments must match the signature of the related method invocation
+ * @param values the parameters value for a particular invocation
+ */
+ CacheInvocationParameter[] getAllParameters(Object... values);
+
+}
\ No newline at end of file
diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/model/package-info.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/model/package-info.java
new file mode 100644
index 0000000000..cb6fa53e26
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/model/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Resolved model of JSR-107 cache annotations. Used internally by the interceptors
+ * in org.springframework.cache.jcache.interceptor.
+ */
+package org.springframework.cache.jcache.model;
\ No newline at end of file
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/AbstractJCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/AbstractJCacheTests.java
new file mode 100644
index 0000000000..19c65efca6
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/AbstractJCacheTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TestName;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.concurrent.ConcurrentMapCache;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.interceptor.SimpleCacheResolver;
+import org.springframework.cache.interceptor.SimpleKeyGenerator;
+import org.springframework.cache.jcache.interceptor.SimpleExceptionCacheResolver;
+import org.springframework.cache.support.SimpleCacheManager;
+
+/**
+ * @author Stephane Nicoll
+ */
+public abstract class AbstractJCacheTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public final TestName name = new TestName();
+
+ protected final CacheManager cacheManager = createSimpleCacheManager("default", "simpleCache");
+
+ protected final CacheResolver defaultCacheResolver = new SimpleCacheResolver(cacheManager);
+
+ protected final CacheResolver defaultExceptionCacheResolver = new SimpleExceptionCacheResolver(cacheManager);
+
+ protected final KeyGenerator defaultKeyGenerator = new SimpleKeyGenerator();
+
+ protected static CacheManager createSimpleCacheManager(String... cacheNames) {
+ SimpleCacheManager result = new SimpleCacheManager();
+ List caches = new ArrayList();
+ for (String cacheName : cacheNames) {
+ caches.add(new ConcurrentMapCache(cacheName));
+ }
+ result.setCaches(caches);
+ result.afterPropertiesSet();
+ return result;
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/AbstractJCacheAnnotationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/AbstractJCacheAnnotationTests.java
new file mode 100644
index 0000000000..bfffbad945
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/AbstractJCacheAnnotationTests.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.config;
+
+import static org.junit.Assert.*;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.jcache.interceptor.SimpleGeneratedCacheKey;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * @author Stephane Nicoll
+ */
+public abstract class AbstractJCacheAnnotationTests {
+
+ public static final String DEFAULT_CACHE = "default";
+
+ public static final String EXCEPTION_CACHE = "exception";
+
+ @Rule
+ public final TestName name = new TestName();
+
+ private JCacheableService> service;
+
+ private CacheManager cacheManager;
+
+ protected abstract ApplicationContext getApplicationContext();
+
+ @Before
+ public void setUp() {
+ ApplicationContext context = getApplicationContext();
+ service = context.getBean(JCacheableService.class);
+ cacheManager = context.getBean("cacheManager", CacheManager.class);
+ }
+
+ @Test
+ public void cache() {
+ String keyItem = name.getMethodName();
+
+ Object first = service.cache(keyItem);
+ Object second = service.cache(keyItem);
+ assertSame(first, second);
+ }
+
+ @Test
+ public void cacheException() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(EXCEPTION_CACHE);
+
+ Object key = createKey(keyItem);
+ assertNull(cache.get(key));
+
+ try {
+ service.cacheWithException(keyItem, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(UnsupportedOperationException.class, result.get().getClass());
+ }
+
+ @Test
+ public void cacheExceptionVetoed() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(EXCEPTION_CACHE);
+
+ Object key = createKey(keyItem);
+ assertNull(cache.get(key));
+
+ try {
+ service.cacheWithException(keyItem, false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ assertNull(cache.get(key));
+ }
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ @Test
+ public void cacheExceptionRewriteCallStack() {
+ final String keyItem = name.getMethodName();
+
+ UnsupportedOperationException first = null;
+ long ref = service.exceptionInvocations();
+ try {
+ service.cacheWithException(keyItem, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ first = e;
+ }
+ // Sanity check, this particular call has called the service
+ assertEquals("First call should not have been cached", ref + 1, service.exceptionInvocations());
+
+ UnsupportedOperationException second = methodInCallStack(keyItem);
+ // Sanity check, this particular call has *not* called the service
+ assertEquals("Second call should have been cached", ref + 1, service.exceptionInvocations());
+
+ assertEquals(first.getCause(), second.getCause());
+ assertEquals(first.getMessage(), second.getMessage());
+ assertFalse("Original stack must not contain any reference to methodInCallStack",
+ contain(first, AbstractJCacheAnnotationTests.class.getName(), "methodInCallStack"));
+ assertTrue("Cached stack should have been rewritten with a reference to methodInCallStack",
+ contain(second, AbstractJCacheAnnotationTests.class.getName(), "methodInCallStack"));
+ }
+
+ @Test
+ public void cacheAlwaysInvoke() {
+ String keyItem = name.getMethodName();
+
+ Object first = service.cacheAlwaysInvoke(keyItem);
+ Object second = service.cacheAlwaysInvoke(keyItem);
+ assertNotSame(first, second);
+ }
+
+ @Test
+ public void cacheWithPartialKey() {
+ String keyItem = name.getMethodName();
+
+ Object first = service.cacheWithPartialKey(keyItem, true);
+ Object second = service.cacheWithPartialKey(keyItem, false);
+ assertSame(first, second); // second argument not used, see config
+ }
+
+ @Test
+ public void cacheWithCustomCacheResolver() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ service.cacheWithCustomCacheResolver(keyItem);
+
+ assertNull(cache.get(key)); // Cache in mock cache
+ }
+
+ @Test
+ public void cacheWithCustomKeyGenerator() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ service.cacheWithCustomKeyGenerator(keyItem, "ignored");
+
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void put() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ service.put(keyItem, value);
+
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(value, result.get());
+ }
+
+ @Test
+ public void putWithException() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ try {
+ service.putWithException(keyItem, value, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(value, result.get());
+ }
+
+ @Test
+ public void putWithExceptionVetoPut() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ try {
+ service.putWithException(keyItem, value, false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void earlyPut() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ service.earlyPut(keyItem, value);
+
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(value, result.get());
+ }
+
+ @Test
+ public void earlyPutWithException() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ try {
+ service.earlyPutWithException(keyItem, value, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(value, result.get());
+ }
+
+ @Test
+ public void earlyPutWithExceptionVetoPut() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ assertNull(cache.get(key));
+
+ try {
+ service.earlyPutWithException(keyItem, value, false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ // This will be cached anyway as the earlyPut has updated the cache before
+ Cache.ValueWrapper result = cache.get(key);
+ assertNotNull(result);
+ assertEquals(value, result.get());
+ }
+
+ @Test
+ public void remove() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ service.remove(keyItem);
+
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void removeWithException() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ try {
+ service.removeWithException(keyItem, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void removeWithExceptionVetoRemove() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ try {
+ service.removeWithException(keyItem, false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ Cache.ValueWrapper wrapper = cache.get(key);
+ assertNotNull(wrapper);
+ assertEquals(value, wrapper.get());
+ }
+
+ @Test
+ public void earlyRemove() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ service.earlyRemove(keyItem);
+
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void earlyRemoveWithException() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ try {
+ service.earlyRemoveWithException(keyItem, true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void earlyRemoveWithExceptionVetoRemove() {
+ String keyItem = name.getMethodName();
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(keyItem);
+ Object value = new Object();
+ cache.put(key, value);
+
+ try {
+ service.earlyRemoveWithException(keyItem, false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ // This will be remove anyway as the earlyRemove has removed the cache before
+ assertNull(cache.get(key));
+ }
+
+ @Test
+ public void removeAll() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ service.removeAll();
+
+ assertTrue(isEmpty(cache));
+ }
+
+ @Test
+ public void removeAllWithException() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ try {
+ service.removeAllWithException(true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+
+ assertTrue(isEmpty(cache));
+ }
+
+ @Test
+ public void removeAllWithExceptionVetoRemove() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ try {
+ service.removeAllWithException(false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ assertNotNull(cache.get(key));
+ }
+
+ @Test
+ public void earlyRemoveAll() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ service.earlyRemoveAll();
+
+ assertTrue(isEmpty(cache));
+ }
+
+ @Test
+ public void earlyRemoveAllWithException() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ try {
+ service.earlyRemoveAllWithException(true);
+ fail("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ // This is what we expect
+ }
+ assertTrue(isEmpty(cache));
+ }
+
+ @Test
+ public void earlyRemoveAllWithExceptionVetoRemove() {
+ Cache cache = getCache(DEFAULT_CACHE);
+
+ Object key = createKey(name.getMethodName());
+ cache.put(key, new Object());
+
+ try {
+ service.earlyRemoveAllWithException(false);
+ fail("Should have thrown an exception");
+ }
+ catch (NullPointerException e) {
+ // This is what we expect
+ }
+ // This will be remove anyway as the earlyRemove has removed the cache before
+ assertTrue(isEmpty(cache));
+ }
+
+ protected boolean isEmpty(Cache cache) {
+ ConcurrentHashMap, ?> nativeCache = (ConcurrentHashMap, ?>) cache.getNativeCache();
+ return nativeCache.isEmpty();
+ }
+
+
+ private Object createKey(Object... params) {
+ return new SimpleGeneratedCacheKey(params);
+ }
+
+ private Cache getCache(String name) {
+ Cache cache = cacheManager.getCache(name);
+ assertNotNull("required cache " + name + " does not exist", cache);
+ return cache;
+ }
+
+ /**
+ * The only purpose of this method is to invoke a particular method on the
+ * service so that the call stack is different.
+ */
+ private UnsupportedOperationException methodInCallStack(String keyItem) {
+ try {
+ service.cacheWithException(keyItem, true);
+ throw new IllegalStateException("Should have thrown an exception");
+ }
+ catch (UnsupportedOperationException e) {
+ return e;
+ }
+ }
+
+ private boolean contain(Throwable t, String className, String methodName) {
+ for (StackTraceElement element : t.getStackTrace()) {
+ if (className.equals(element.getClassName()) && methodName.equals(element.getMethodName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java
new file mode 100644
index 0000000000..a15b877114
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.config;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCache;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.interceptor.SimpleCacheResolver;
+import org.springframework.cache.interceptor.SimpleKeyGenerator;
+import org.springframework.cache.jcache.interceptor.AnnotatedJCacheableService;
+import org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource;
+import org.springframework.cache.support.NoOpCacheManager;
+import org.springframework.cache.support.SimpleCacheManager;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class JCacheJavaConfigTests extends AbstractJCacheAnnotationTests {
+
+ @Override
+ protected ApplicationContext getApplicationContext() {
+ return new AnnotationConfigApplicationContext(EnableCachingConfig.class);
+ }
+
+ @Test
+ public void fullCachingConfig() throws Exception {
+ AnnotationConfigApplicationContext context =
+ new AnnotationConfigApplicationContext(FullCachingConfig.class);
+ DefaultJCacheOperationSource cos = context.getBean(DefaultJCacheOperationSource.class);
+ assertSame(context.getBean(KeyGenerator.class), cos.getDefaultKeyGenerator());
+ assertSame(context.getBean("cacheResolver", CacheResolver.class),
+ cos.getDefaultCacheResolver());
+ assertSame(context.getBean("exceptionCacheResolver", CacheResolver.class),
+ cos.getDefaultExceptionCacheResolver());
+ }
+
+
+ @Configuration
+ @EnableCaching
+ public static class EnableCachingConfig {
+
+ @Bean
+ public CacheManager cacheManager() {
+ SimpleCacheManager cm = new SimpleCacheManager();
+ cm.setCaches(Arrays.asList(
+ defaultCache(),
+ new ConcurrentMapCache("primary"),
+ new ConcurrentMapCache("secondary"),
+ new ConcurrentMapCache("exception")));
+ return cm;
+ }
+
+ @Bean
+ public JCacheableService> cacheableService() {
+ return new AnnotatedJCacheableService(defaultCache());
+ }
+
+ @Bean
+ public Cache defaultCache() {
+ return new ConcurrentMapCache("default");
+ }
+ }
+
+ @Configuration
+ @EnableCaching
+ public static class FullCachingConfig implements JCacheConfigurer {
+
+
+ @Override
+ @Bean
+ public CacheManager cacheManager() {
+ return new NoOpCacheManager();
+ }
+
+ @Override
+ @Bean
+ public KeyGenerator keyGenerator() {
+ return new SimpleKeyGenerator();
+ }
+
+ @Override
+ @Bean
+ public CacheResolver cacheResolver() {
+ return new SimpleCacheResolver(cacheManager());
+ }
+
+ @Override
+ @Bean
+ public CacheResolver exceptionCacheResolver() {
+ return new SimpleCacheResolver(cacheManager());
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheNamespaceDrivenTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheNamespaceDrivenTests.java
new file mode 100644
index 0000000000..3cff4bc9d3
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheNamespaceDrivenTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.config;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericXmlApplicationContext;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class JCacheNamespaceDrivenTests extends AbstractJCacheAnnotationTests {
+
+ @Override
+ protected ApplicationContext getApplicationContext() {
+ return new GenericXmlApplicationContext(
+ "/org/springframework/cache/jcache/config/jCacheNamespaceDriven.xml");
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheStandaloneConfigTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheStandaloneConfigTests.java
new file mode 100644
index 0000000000..82a6e40e14
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheStandaloneConfigTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.config;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericXmlApplicationContext;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class JCacheStandaloneConfigTests extends AbstractJCacheAnnotationTests {
+
+ @Override
+ protected ApplicationContext getApplicationContext() {
+ return new GenericXmlApplicationContext(
+ "/org/springframework/cache/jcache/config/jCacheStandaloneConfig.xml");
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheableService.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheableService.java
new file mode 100644
index 0000000000..8b5709ef4a
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheableService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.config;
+
+/**
+ * @author Stephane Nicoll
+ */
+public interface JCacheableService {
+
+ T cache(String id);
+
+ T cacheWithException(String id, boolean matchFilter);
+
+ T cacheAlwaysInvoke(String id);
+
+ T cacheWithPartialKey(String id, boolean notUsed);
+
+ T cacheWithCustomCacheResolver(String id);
+
+ T cacheWithCustomKeyGenerator(String id, String anotherId);
+
+ void put(String id, Object value);
+
+ void putWithException(String id, Object value, boolean matchFilter);
+
+ void earlyPut(String id, Object value);
+
+ void earlyPutWithException(String id, Object value, boolean matchFilter);
+
+ void remove(String id);
+
+ void removeWithException(String id, boolean matchFilter);
+
+ void earlyRemove(String id);
+
+ void earlyRemoveWithException(String id, boolean matchFilter);
+
+ void removeAll();
+
+ void removeAllWithException(boolean matchFilter);
+
+ void earlyRemoveAll();
+
+ void earlyRemoveAllWithException(boolean matchFilter);
+
+ long exceptionInvocations();
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotatedJCacheableService.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotatedJCacheableService.java
new file mode 100644
index 0000000000..94c84a202f
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotatedJCacheableService.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.interceptor;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.cache.annotation.CacheDefaults;
+import javax.cache.annotation.CacheKey;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.CacheValue;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.jcache.config.JCacheableService;
+import org.springframework.cache.jcache.support.TestableCacheKeyGenerator;
+import org.springframework.cache.jcache.support.TestableCacheResolverFactory;
+
+
+/**
+ * Repository sample with a @CacheDefaults annotation
+ *
+ * @author Stephane Nicoll
+ */
+@CacheDefaults(cacheName = "default")
+public class AnnotatedJCacheableService implements JCacheableService {
+
+ private final AtomicLong counter = new AtomicLong();
+
+ private final AtomicLong exceptionCounter = new AtomicLong();
+
+ private final Cache defaultCache;
+
+ public AnnotatedJCacheableService(Cache defaultCache) {
+ this.defaultCache = defaultCache;
+ }
+
+ @Override
+ @CacheResult
+ public Long cache(String id) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @CacheResult(exceptionCacheName = "exception", nonCachedExceptions = NullPointerException.class)
+ public Long cacheWithException(@CacheKey String id, boolean matchFilter) {
+ throwException(matchFilter);
+ return 0L; // Never reached
+ }
+
+ @Override
+ @CacheResult(skipGet = true)
+ public Long cacheAlwaysInvoke(String id) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @CacheResult
+ public Long cacheWithPartialKey(@CacheKey String id, boolean notUsed) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @CacheResult(cacheResolverFactory = TestableCacheResolverFactory.class)
+ public Long cacheWithCustomCacheResolver(String id) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @CacheResult(cacheKeyGenerator = TestableCacheKeyGenerator.class)
+ public Long cacheWithCustomKeyGenerator(String id, String anotherId) {
+ return counter.getAndIncrement();
+ }
+
+ @Override
+ @CachePut
+ public void put(String id, @CacheValue Object value) {
+ }
+
+ @Override
+ @CachePut(cacheFor = UnsupportedOperationException.class)
+ public void putWithException(@CacheKey String id, @CacheValue Object value, boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Override
+ @CachePut(afterInvocation = false)
+ public void earlyPut(String id, @CacheValue Object value) {
+ SimpleGeneratedCacheKey key = new SimpleGeneratedCacheKey(id);
+ Cache.ValueWrapper valueWrapper = defaultCache.get(key);
+ if (valueWrapper == null) {
+ throw new AssertionError("Excepted value to be put in cache with key " + key);
+ }
+ Object actual = valueWrapper.get();
+ if (value != actual) { // instance check on purpose
+ throw new AssertionError("Wrong value set in cache with key " + key + ". " +
+ "Expected=" + value + ", but got=" + actual);
+ }
+ }
+
+ @Override
+ @CachePut(afterInvocation = false)
+ public void earlyPutWithException(@CacheKey String id, @CacheValue Object value, boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Override
+ @CacheRemove
+ public void remove(String id) {
+ }
+
+ @Override
+ @CacheRemove(noEvictFor = NullPointerException.class)
+ public void removeWithException(@CacheKey String id, boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Override
+ @CacheRemove(afterInvocation = false)
+ public void earlyRemove(String id) {
+ SimpleGeneratedCacheKey key = new SimpleGeneratedCacheKey(id);
+ Cache.ValueWrapper valueWrapper = defaultCache.get(key);
+ if (valueWrapper != null) {
+ throw new AssertionError("Value with key " + key + " expected to be already remove from cache");
+ }
+ }
+
+ @Override
+ @CacheRemove(afterInvocation = false, evictFor = UnsupportedOperationException.class)
+ public void earlyRemoveWithException(@CacheKey String id, boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Override
+ @CacheRemoveAll
+ public void removeAll() {
+ }
+
+ @Override
+ @CacheRemoveAll(noEvictFor = NullPointerException.class)
+ public void removeAllWithException(boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Override
+ @CacheRemoveAll(afterInvocation = false)
+ public void earlyRemoveAll() {
+ ConcurrentHashMap, ?> nativeCache = (ConcurrentHashMap, ?>) defaultCache.getNativeCache();
+ if (!nativeCache.isEmpty()) {
+ throw new AssertionError("Cache was expected to be empty");
+ }
+ }
+
+ @Override
+ @CacheRemoveAll(afterInvocation = false, evictFor = UnsupportedOperationException.class)
+ public void earlyRemoveAllWithException(boolean matchFilter) {
+ throwException(matchFilter);
+ }
+
+ @Deprecated
+ public void noAnnotation() {
+ }
+
+ @CacheRemove
+ @CacheRemoveAll
+ public void multiAnnotations() {
+
+ }
+
+ @Override
+ public long exceptionInvocations() {
+ return exceptionCounter.get();
+ }
+
+ private void throwException(boolean matchFilter) {
+ long count = exceptionCounter.getAndIncrement();
+ if (matchFilter) {
+ throw new UnsupportedOperationException("Expected exception (" + count + ")");
+ }
+ else {
+ throw new NullPointerException("Expected exception (" + count + ")");
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java
new file mode 100644
index 0000000000..ad16df23e3
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.interceptor;
+
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
+
+import java.lang.reflect.Method;
+import java.util.Comparator;
+
+import javax.cache.annotation.CacheDefaults;
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CacheResult;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.jcache.AbstractJCacheTests;
+import org.springframework.cache.jcache.model.BaseKeyCacheOperation;
+import org.springframework.cache.jcache.model.CachePutOperation;
+import org.springframework.cache.jcache.model.CacheRemoveAllOperation;
+import org.springframework.cache.jcache.model.CacheRemoveOperation;
+import org.springframework.cache.jcache.model.CacheResultOperation;
+import org.springframework.cache.jcache.model.JCacheOperation;
+import org.springframework.cache.jcache.support.TestableCacheKeyGenerator;
+import org.springframework.cache.jcache.support.TestableCacheResolver;
+import org.springframework.cache.jcache.support.TestableCacheResolverFactory;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class AnnotationCacheOperationSourceTests extends AbstractJCacheTests {
+
+ private final DefaultJCacheOperationSource source = new DefaultJCacheOperationSource();
+
+ private final StaticApplicationContext applicationContext = new StaticApplicationContext();
+
+ @Before
+ public void setUp() {
+ source.setApplicationContext(applicationContext);
+ source.setKeyGenerator(defaultKeyGenerator);
+ source.setCacheResolver(defaultCacheResolver);
+ source.setExceptionCacheResolver(defaultExceptionCacheResolver);
+ source.afterPropertiesSet();
+ }
+
+ @Test
+ public void cache() {
+ CacheResultOperation op = getDefaultCacheOperation(CacheResultOperation.class, String.class);
+ assertDefaults(op);
+ assertNull("Exception caching not enabled so resolver should not be set", op.getExceptionCacheResolver());
+ }
+
+ @Test
+ public void cacheWithException() {
+ CacheResultOperation op = getDefaultCacheOperation(CacheResultOperation.class, String.class, boolean.class);
+ assertDefaults(op);
+ assertEquals(defaultExceptionCacheResolver, op.getExceptionCacheResolver());
+ assertEquals("exception", op.getExceptionCacheName());
+ }
+
+ @Test
+ public void put() {
+ CachePutOperation op = getDefaultCacheOperation(CachePutOperation.class, String.class, Object.class);
+ assertDefaults(op);
+ }
+
+ @Test
+ public void remove() {
+ CacheRemoveOperation op = getDefaultCacheOperation(CacheRemoveOperation.class, String.class);
+ assertDefaults(op);
+ }
+
+ @Test
+ public void removeAll() {
+ CacheRemoveAllOperation op = getDefaultCacheOperation(CacheRemoveAllOperation.class);
+ assertEquals(defaultCacheResolver, op.getCacheResolver());
+ }
+
+ @Test
+ public void noAnnotation() {
+ assertNull(getCacheOperation(AnnotatedJCacheableService.class, name.getMethodName()));
+ }
+
+ @Test
+ public void multiAnnotations() {
+ thrown.expect(IllegalStateException.class);
+ getCacheOperation(AnnotatedJCacheableService.class, name.getMethodName());
+ }
+
+ @Test
+ public void defaultCacheNameWithCandidate() {
+ Method m = ReflectionUtils.findMethod(Object.class, "toString");
+ assertEquals("foo", source.determineCacheName(m, null, "foo"));
+ }
+
+ @Test
+ public void defaultCacheNameWithDefaults() {
+ Method m = ReflectionUtils.findMethod(Object.class, "toString");
+ CacheDefaults mock = mock(CacheDefaults.class);
+ given(mock.cacheName()).willReturn("");
+ assertEquals("java.lang.Object.toString()", source.determineCacheName(m, mock, ""));
+ }
+
+ @Test
+ public void defaultCacheNameNoDefaults() {
+ Method m = ReflectionUtils.findMethod(Object.class, "toString");
+ assertEquals("java.lang.Object.toString()", source.determineCacheName(m, null, ""));
+ }
+
+ @Test
+ public void defaultCacheNameWithParameters() {
+ Method m = ReflectionUtils.findMethod(Comparator.class, "compare", Object.class, Object.class);
+ assertEquals("java.util.Comparator.compare(java.lang.Object,java.lang.Object)",
+ source.determineCacheName(m, null, ""));
+ }
+
+ @Test
+ public void customCacheResolver() {
+ CacheResultOperation operation =
+ getCacheOperation(CacheResultOperation.class, CustomService.class, name.getMethodName(), Long.class);
+ assertJCacheResolver(operation.getCacheResolver(), TestableCacheResolver.class);
+ assertJCacheResolver(operation.getExceptionCacheResolver(), null);
+ assertEquals(defaultKeyGenerator, operation.getKeyGenerator());
+ }
+
+ @Test
+ public void customKeyGenerator() {
+ CacheResultOperation operation =
+ getCacheOperation(CacheResultOperation.class, CustomService.class, name.getMethodName(), Long.class);
+ assertEquals(defaultCacheResolver, operation.getCacheResolver());
+ assertNull(operation.getExceptionCacheResolver());
+ assertCacheKeyGenerator(operation.getKeyGenerator(), TestableCacheKeyGenerator.class);
+ }
+
+ @Test
+ public void customKeyGeneratorSpringBean() {
+ TestableCacheKeyGenerator bean = new TestableCacheKeyGenerator();
+ applicationContext.getBeanFactory().registerSingleton("fooBar", bean);
+ CacheResultOperation operation =
+ getCacheOperation(CacheResultOperation.class, CustomService.class, name.getMethodName(), Long.class);
+ assertEquals(defaultCacheResolver, operation.getCacheResolver());
+ assertNull(operation.getExceptionCacheResolver());
+ KeyGeneratorAdapter adapter = (KeyGeneratorAdapter) operation.getKeyGenerator();
+ assertSame(bean, adapter.getTarget()); // take bean from context
+ }
+
+ @Test
+ public void customKeyGeneratorAndCacheResolver() {
+ CacheResultOperation operation = getCacheOperation(CacheResultOperation.class,
+ CustomServiceWithDefaults.class, name.getMethodName(), Long.class);
+ assertJCacheResolver(operation.getCacheResolver(), TestableCacheResolver.class);
+ assertJCacheResolver(operation.getExceptionCacheResolver(), null);
+ assertCacheKeyGenerator(operation.getKeyGenerator(), TestableCacheKeyGenerator.class);
+ }
+
+ @Test
+ public void customKeyGeneratorAndCacheResolverWithExceptionName() {
+ CacheResultOperation operation = getCacheOperation(CacheResultOperation.class,
+ CustomServiceWithDefaults.class, name.getMethodName(), Long.class);
+ assertJCacheResolver(operation.getCacheResolver(), TestableCacheResolver.class);
+ assertJCacheResolver(operation.getExceptionCacheResolver(), TestableCacheResolver.class);
+ assertCacheKeyGenerator(operation.getKeyGenerator(), TestableCacheKeyGenerator.class);
+ }
+
+ private void assertDefaults(BaseKeyCacheOperation> operation) {
+ assertEquals(defaultCacheResolver, operation.getCacheResolver());
+ assertEquals(defaultKeyGenerator, operation.getKeyGenerator());
+ }
+
+ protected > T getDefaultCacheOperation(Class operationType, Class>... parameterTypes) {
+ return getCacheOperation(operationType, AnnotatedJCacheableService.class, name.getMethodName(), parameterTypes);
+ }
+
+ protected > T getCacheOperation(Class operationType, Class> targetType,
+ String methodName, Class>... parameterTypes) {
+ JCacheOperation> result = getCacheOperation(targetType, methodName, parameterTypes);
+ assertNotNull(result);
+ assertEquals(operationType, result.getClass());
+ return operationType.cast(result);
+ }
+
+ private JCacheOperation> getCacheOperation(Class> targetType, String methodName, Class>... parameterTypes) {
+ Method method = ReflectionUtils.findMethod(targetType, methodName, parameterTypes);
+ Assert.notNull(method, "requested method '" + methodName + "'does not exist");
+ return source.getCacheOperation(method, targetType);
+ }
+
+ private void assertJCacheResolver(CacheResolver actual,
+ Class extends javax.cache.annotation.CacheResolver> expectedTargetType) {
+ if (expectedTargetType == null) {
+ assertNull(actual);
+ }
+ else {
+ assertEquals("Wrong cache resolver implementation", CacheResolverAdapter.class, actual.getClass());
+ CacheResolverAdapter adapter = (CacheResolverAdapter) actual;
+ assertEquals("Wrong target JCache implementation", expectedTargetType, adapter.getTarget().getClass());
+ }
+ }
+
+ private void assertCacheKeyGenerator(KeyGenerator actual,
+ Class extends CacheKeyGenerator> expectedTargetType) {
+ assertEquals("Wrong cache resolver implementation", KeyGeneratorAdapter.class, actual.getClass());
+ KeyGeneratorAdapter adapter = (KeyGeneratorAdapter) actual;
+ assertEquals("Wrong target CacheKeyGenerator implementation", expectedTargetType, adapter.getTarget().getClass());
+ }
+
+
+ static class CustomService {
+
+ @CacheResult(cacheKeyGenerator = TestableCacheKeyGenerator.class)
+ public Object customKeyGenerator(Long id) {
+ return null;
+ }
+
+ @CacheResult(cacheKeyGenerator = TestableCacheKeyGenerator.class)
+ public Object customKeyGeneratorSpringBean(Long id) {
+ return null;
+ }
+
+ @CacheResult(cacheResolverFactory = TestableCacheResolverFactory.class)
+ public Object customCacheResolver(Long id) {
+ return null;
+ }
+ }
+
+ @CacheDefaults(cacheResolverFactory = TestableCacheResolverFactory.class,
+ cacheKeyGenerator = TestableCacheKeyGenerator.class)
+ static class CustomServiceWithDefaults {
+
+ @CacheResult
+ public Object customKeyGeneratorAndCacheResolver(Long id) {
+ return null;
+ }
+
+ @CacheResult(exceptionCacheName = "exception")
+ public Object customKeyGeneratorAndCacheResolverWithExceptionName(Long id) {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java
new file mode 100644
index 0000000000..430d6fa3a7
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.interceptor;
+
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResult;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.jcache.AbstractJCacheTests;
+import org.springframework.cache.jcache.model.CacheResultOperation;
+import org.springframework.cache.jcache.model.DefaultCacheMethodDetails;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class CacheResolverAdapterTests extends AbstractJCacheTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+
+ @Test
+ public void resolveSimpleCache() {
+ DefaultCacheInvocationContext> dummyContext = createDummyContext();
+ CacheResolverAdapter adapter = new CacheResolverAdapter(getCacheResolver(dummyContext, "testCache"));
+ Collection extends Cache> caches = adapter.resolveCaches(dummyContext);
+ assertNotNull(caches);
+ assertEquals(1, caches.size());
+ assertEquals("testCache", caches.iterator().next().getName());
+ }
+
+ @Test
+ public void resolveUnknownCache() {
+ DefaultCacheInvocationContext> dummyContext = createDummyContext();
+ CacheResolverAdapter adapter = new CacheResolverAdapter(getCacheResolver(dummyContext, null));
+
+ thrown.expect(IllegalArgumentException.class);
+ adapter.resolveCaches(dummyContext);
+ }
+
+ protected CacheResolver getCacheResolver(CacheInvocationContext extends Annotation> context, String cacheName) {
+ CacheResolver cacheResolver = mock(CacheResolver.class);
+ final javax.cache.Cache cache;
+ if (cacheName == null) {
+ cache = null;
+ }
+ else {
+ cache = mock(javax.cache.Cache.class);
+ given(cache.getName()).willReturn(cacheName);
+ }
+ given(cacheResolver.resolveCache(context)).willReturn(cache);
+ return cacheResolver;
+ }
+
+ protected DefaultCacheInvocationContext> createDummyContext() {
+ Method method = ReflectionUtils.findMethod(Sample.class, "get", String.class);
+ Assert.notNull(method);
+ CacheResult cacheAnnotation = method.getAnnotation(CacheResult.class);
+ CacheMethodDetails methodDetails =
+ new DefaultCacheMethodDetails<>(method, cacheAnnotation, "test");
+ CacheResultOperation operation = new CacheResultOperation(methodDetails,
+ defaultCacheResolver, defaultKeyGenerator, defaultExceptionCacheResolver);
+ return new DefaultCacheInvocationContext(operation, new Sample(), new Object[] {"id"});
+ }
+
+
+ static class Sample {
+
+ @CacheResult
+ private Object get(String id) {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
new file mode 100644
index 0000000000..92b2f38d54
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.interceptor;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.CacheOperationInvocationContext;
+import org.springframework.cache.interceptor.CacheOperationInvoker;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.jcache.AbstractJCacheTests;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class JCacheInterceptorTests extends AbstractJCacheTests {
+
+ private final CacheOperationInvoker dummyInvoker = new DummyInvoker(null);
+
+ @Test
+ public void severalCachesNotSupported() {
+ JCacheInterceptor interceptor = createInterceptor(createOperationSource(
+ cacheManager, new TestCacheResolver("default", "exception"),
+ defaultExceptionCacheResolver, defaultKeyGenerator));
+
+ AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
+ Method m = ReflectionUtils.findMethod(AnnotatedJCacheableService.class, "cache", String.class);
+
+ try {
+ interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"});
+ }
+ catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("JSR-107 only supports a single cache."));
+ }
+ catch (Throwable t) {
+ fail("Unexpected: " + t);
+ }
+ }
+
+ @Test
+ public void noCacheCouldBeResolved() {
+ JCacheInterceptor interceptor = createInterceptor(createOperationSource(
+ cacheManager, new TestCacheResolver(), // Returns empty list
+ defaultExceptionCacheResolver, defaultKeyGenerator));
+
+ AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
+ Method m = ReflectionUtils.findMethod(AnnotatedJCacheableService.class, "cache", String.class);
+
+ try {
+ interceptor.execute(dummyInvoker, service, m, new Object[] {"myId"});
+ }
+ catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("Cache could not have been resolved for"));
+ }
+ catch (Throwable t) {
+ fail("Unexpected: " + t);
+ }
+ }
+
+ @Test
+ public void cacheManagerMandatoryIfCacheResolverNotSetSet() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("'cacheManager' is required");
+ createOperationSource(null, null, null, defaultKeyGenerator);
+ }
+
+ @Test
+ public void cacheManagerOptionalIfCacheResolversSet() {
+ createOperationSource(null, defaultCacheResolver, defaultExceptionCacheResolver, defaultKeyGenerator);
+ }
+
+ @Test
+ public void cacheResultReturnsProperType() throws Throwable {
+ JCacheInterceptor interceptor = createInterceptor(createOperationSource(
+ cacheManager, defaultCacheResolver,
+ defaultExceptionCacheResolver, defaultKeyGenerator));
+
+ AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
+ Method m = ReflectionUtils.findMethod(AnnotatedJCacheableService.class, "cache", String.class);
+
+ CacheOperationInvoker invoker = new DummyInvoker(0L);
+ Object execute = interceptor.execute(invoker, service, m, new Object[] {"myId"});
+ assertNotNull("result cannot be null.", execute);
+ assertEquals("Wrong result type", Long.class, execute.getClass());
+ assertEquals("Wrong result", 0L, execute);
+ }
+
+ protected JCacheOperationSource createOperationSource(CacheManager cacheManager,
+ CacheResolver cacheResolver,
+ CacheResolver exceptionCacheResolver,
+ KeyGenerator keyGenerator) {
+ DefaultJCacheOperationSource source = new DefaultJCacheOperationSource();
+ source.setApplicationContext(new StaticApplicationContext());
+ source.setCacheManager(cacheManager);
+ source.setCacheResolver(cacheResolver);
+ source.setExceptionCacheResolver(exceptionCacheResolver);
+ source.setKeyGenerator(keyGenerator);
+ source.afterPropertiesSet();
+ return source;
+ }
+
+
+ protected JCacheInterceptor createInterceptor(JCacheOperationSource source) {
+ JCacheInterceptor interceptor = new JCacheInterceptor();
+ interceptor.setCacheOperationSource(source);
+ interceptor.afterPropertiesSet();
+ return interceptor;
+ }
+
+
+ private class TestCacheResolver implements CacheResolver {
+
+ private final String[] cacheNames;
+
+ private TestCacheResolver(String... cacheNames) {
+ this.cacheNames = cacheNames;
+ }
+
+ @Override
+ public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
+ List result = new ArrayList();
+ for (String cacheName : cacheNames) {
+ result.add(cacheManager.getCache(cacheName));
+ }
+ return result;
+ }
+ }
+
+ private static class DummyInvoker implements CacheOperationInvoker {
+
+ private final Object result;
+
+ private DummyInvoker(Object result) {
+ this.result = result;
+ }
+
+ @Override
+ public Object invoke() throws ThrowableWrapper {
+ return result;
+ }
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/AbstractCacheOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/AbstractCacheOperationTests.java
new file mode 100644
index 0000000000..1e72f13922
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/AbstractCacheOperationTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.model;
+
+import static org.junit.Assert.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheMethodDetails;
+
+import org.junit.Test;
+
+import org.springframework.cache.jcache.AbstractJCacheTests;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Stephane Nicoll
+ */
+public abstract class AbstractCacheOperationTests> extends AbstractJCacheTests {
+
+ protected final SampleObject sampleInstance = new SampleObject();
+
+ protected abstract O createSimpleOperation();
+
+
+ @Test
+ public void simple() {
+ O operation = createSimpleOperation();
+ assertEquals("Wrong cache name", "simpleCache", operation.getCacheName());
+ assertEquals("Unexpected number of annotation on " + operation.getMethod(),
+ 1, operation.getAnnotations().size());
+ assertEquals("Wrong method annotation", operation.getCacheAnnotation(),
+ operation.getAnnotations().iterator().next());
+
+ assertNotNull("cache resolver should be set", operation.getCacheResolver());
+ }
+
+ protected void assertCacheInvocationParameter(CacheInvocationParameter actual, Class> targetType,
+ Object value, int position) {
+ assertEquals("wrong parameter type for " + actual, targetType, actual.getRawType());
+ assertEquals("wrong parameter value for " + actual, value, actual.getValue());
+ assertEquals("wrong parameter position for " + actual, position, actual.getParameterPosition());
+ }
+
+ protected CacheMethodDetails create(Class annotationType,
+ Class> targetType, String methodName,
+ Class>... parameterTypes) {
+ Method method = ReflectionUtils.findMethod(targetType, methodName, parameterTypes);
+ Assert.notNull(method, "requested method '" + methodName + "'does not exist");
+ A cacheAnnotation = method.getAnnotation(annotationType);
+ return new DefaultCacheMethodDetails (method, cacheAnnotation, getCacheName(cacheAnnotation));
+ }
+
+ private static String getCacheName(Annotation annotation) {
+ Object cacheName = AnnotationUtils.getValue(annotation, "cacheName");
+ return cacheName != null ? cacheName.toString() : "test";
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CachePutOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CachePutOperationTests.java
new file mode 100644
index 0000000000..727be16a8f
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CachePutOperationTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.model;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CachePut;
+
+import org.junit.Test;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class CachePutOperationTests extends AbstractCacheOperationTests {
+
+ @Override
+ protected CachePutOperation createSimpleOperation() {
+ CacheMethodDetails methodDetails = create(CachePut.class,
+ SampleObject.class, "simplePut", Long.class, SampleObject.class);
+ return createDefaultOperation(methodDetails);
+ }
+
+ @Test
+ public void simplePut() {
+ CachePutOperation operation = createSimpleOperation();
+
+ CacheInvocationParameter[] allParameters = operation.getAllParameters(2L, sampleInstance);
+ assertEquals(2, allParameters.length);
+ assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0);
+ assertCacheInvocationParameter(allParameters[1], SampleObject.class, sampleInstance, 1);
+
+ CacheInvocationParameter valueParameter = operation.getValueParameter(2L, sampleInstance);
+ assertNotNull(valueParameter);
+ assertCacheInvocationParameter(valueParameter, SampleObject.class, sampleInstance, 1);
+ }
+
+ @Test
+ public void noCacheValue() {
+ CacheMethodDetails methodDetails = create(CachePut.class,
+ SampleObject.class, "noCacheValue", Long.class);
+
+ thrown.expect(IllegalArgumentException.class);
+ createDefaultOperation(methodDetails);
+ }
+
+ @Test
+ public void multiCacheValues() {
+ CacheMethodDetails methodDetails = create(CachePut.class,
+ SampleObject.class, "multiCacheValues", Long.class, SampleObject.class, SampleObject.class);
+
+ thrown.expect(IllegalArgumentException.class);
+ createDefaultOperation(methodDetails);
+ }
+
+ @Test
+ public void invokeWithWrongParameters() {
+ CachePutOperation operation = createSimpleOperation();
+
+ thrown.expect(IllegalStateException.class);
+ operation.getValueParameter(2L);
+ }
+
+ @Test
+ public void fullPutConfig() {
+ CacheMethodDetails methodDetails = create(CachePut.class,
+ SampleObject.class, "fullPutConfig", Long.class, SampleObject.class);
+ CachePutOperation operation = createDefaultOperation(methodDetails);
+ assertTrue(operation.isEarlyPut());
+ assertNotNull(operation.getExceptionTypeFilter());
+ assertTrue(operation.getExceptionTypeFilter().match(IOException.class));
+ assertFalse(operation.getExceptionTypeFilter().match(NullPointerException.class));
+ }
+
+ private CachePutOperation createDefaultOperation(CacheMethodDetails methodDetails) {
+ return new CachePutOperation(methodDetails, defaultCacheResolver, defaultKeyGenerator);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveAllOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveAllOperationTests.java
new file mode 100644
index 0000000000..55937e207a
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveAllOperationTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.model;
+
+import static org.junit.Assert.*;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheRemoveAll;
+
+import org.junit.Test;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class CacheRemoveAllOperationTests extends AbstractCacheOperationTests {
+
+ @Override
+ protected CacheRemoveAllOperation createSimpleOperation() {
+ CacheMethodDetails methodDetails = create(CacheRemoveAll.class,
+ SampleObject.class, "simpleRemoveAll");
+
+ return new CacheRemoveAllOperation(methodDetails, defaultCacheResolver);
+ }
+
+ @Test
+ public void simpleRemoveAll() {
+ CacheRemoveAllOperation operation = createSimpleOperation();
+
+ CacheInvocationParameter[] allParameters = operation.getAllParameters();
+ assertEquals(0, allParameters.length);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveOperationTests.java
new file mode 100644
index 0000000000..91dbc9d650
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheRemoveOperationTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.model;
+
+import static org.junit.Assert.*;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheRemove;
+
+import org.junit.Test;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class CacheRemoveOperationTests extends AbstractCacheOperationTests {
+
+ @Override
+ protected CacheRemoveOperation createSimpleOperation() {
+ CacheMethodDetails methodDetails = create(CacheRemove.class,
+ SampleObject.class, "simpleRemove", Long.class);
+
+ return new CacheRemoveOperation(methodDetails, defaultCacheResolver, defaultKeyGenerator);
+ }
+
+ @Test
+ public void simpleRemove() {
+ CacheRemoveOperation operation = createSimpleOperation();
+
+ CacheInvocationParameter[] allParameters = operation.getAllParameters(2L);
+ assertEquals(1, allParameters.length);
+ assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheResultOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheResultOperationTests.java
new file mode 100644
index 0000000000..e6102eaeaa
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/CacheResultOperationTests.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.model;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.cache.annotation.CacheInvocationParameter;
+import javax.cache.annotation.CacheKey;
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheResult;
+
+import org.junit.Test;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class CacheResultOperationTests extends AbstractCacheOperationTests {
+
+ @Override
+ protected CacheResultOperation createSimpleOperation() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "simpleGet", Long.class);
+
+ return new CacheResultOperation(methodDetails, defaultCacheResolver, defaultKeyGenerator,
+ defaultExceptionCacheResolver);
+ }
+
+ @Test
+ public void simpleGet() {
+ CacheResultOperation operation = createSimpleOperation();
+
+ assertNotNull(operation.getKeyGenerator());
+ assertNotNull(operation.getExceptionCacheResolver());
+
+ assertNull(operation.getExceptionCacheName());
+ assertEquals(defaultExceptionCacheResolver, operation.getExceptionCacheResolver());
+
+ CacheInvocationParameter[] allParameters = operation.getAllParameters(2L);
+ assertEquals(1, allParameters.length);
+ assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0);
+
+ CacheInvocationParameter[] keyParameters = operation.getKeyParameters(2L);
+ assertEquals(1, keyParameters.length);
+ assertCacheInvocationParameter(keyParameters[0], Long.class, 2L, 0);
+ }
+
+ @Test
+ public void multiParameterKey() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "multiKeysGet", Long.class, Boolean.class, String.class);
+ CacheResultOperation operation = createDefaultOperation(methodDetails);
+
+ CacheInvocationParameter[] keyParameters = operation.getKeyParameters(3L, Boolean.TRUE, "Foo");
+ assertEquals(2, keyParameters.length);
+ assertCacheInvocationParameter(keyParameters[0], Long.class, 3L, 0);
+ assertCacheInvocationParameter(keyParameters[1], String.class, "Foo", 2);
+ }
+
+ @Test
+ public void invokeWithWrongParameters() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "anotherSimpleGet", String.class, Long.class);
+ CacheResultOperation operation = createDefaultOperation(methodDetails);
+
+ thrown.expect(IllegalStateException.class);
+ operation.getAllParameters("bar"); // missing one argument
+ }
+
+ @Test
+ public void tooManyKeyValues() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "anotherSimpleGet", String.class, Long.class);
+ CacheResultOperation operation = createDefaultOperation(methodDetails);
+
+ thrown.expect(IllegalStateException.class);
+ operation.getKeyParameters("bar"); // missing one argument
+ }
+
+ @Test
+ public void annotatedGet() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "annotatedGet", Long.class, String.class);
+ CacheResultOperation operation = createDefaultOperation(methodDetails);
+ CacheInvocationParameter[] parameters = operation.getAllParameters(2L, "foo");
+
+ Set firstParameterAnnotations = parameters[0].getAnnotations();
+ assertEquals(1, firstParameterAnnotations.size());
+ assertEquals(CacheKey.class, firstParameterAnnotations.iterator().next().annotationType());
+
+ Set secondParameterAnnotations = parameters[1].getAnnotations();
+ assertEquals(1, secondParameterAnnotations.size());
+ assertEquals(Value.class, secondParameterAnnotations.iterator().next().annotationType());
+ }
+
+ @Test
+ public void fullGetConfig() {
+ CacheMethodDetails methodDetails = create(CacheResult.class,
+ SampleObject.class, "fullGetConfig", Long.class);
+ CacheResultOperation operation = createDefaultOperation(methodDetails);
+ assertTrue(operation.isAlwaysInvoked());
+ assertNotNull(operation.getExceptionTypeFilter());
+ assertTrue(operation.getExceptionTypeFilter().match(IOException.class));
+ assertFalse(operation.getExceptionTypeFilter().match(NullPointerException.class));
+ }
+
+ private CacheResultOperation createDefaultOperation(CacheMethodDetails methodDetails) {
+ return new CacheResultOperation(methodDetails,
+ defaultCacheResolver, defaultKeyGenerator, defaultCacheResolver);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/model/SampleObject.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/SampleObject.java
new file mode 100644
index 0000000000..baf2d077a8
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/model/SampleObject.java
@@ -0,0 +1,80 @@
+package org.springframework.cache.jcache.model;
+
+import javax.cache.annotation.CacheKey;
+import javax.cache.annotation.CachePut;
+import javax.cache.annotation.CacheRemove;
+import javax.cache.annotation.CacheRemoveAll;
+import javax.cache.annotation.CacheResult;
+import javax.cache.annotation.CacheValue;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * @author Stephane Nicoll
+ */
+class SampleObject {
+
+ // Simple
+
+ @CacheResult(cacheName = "simpleCache")
+ public SampleObject simpleGet(Long id) {
+ return null;
+ }
+
+ @CachePut(cacheName = "simpleCache")
+ public void simplePut(Long id, @CacheValue SampleObject instance) {
+ }
+
+ @CacheRemove(cacheName = "simpleCache")
+ public void simpleRemove(Long id) {
+ }
+
+ @CacheRemoveAll(cacheName = "simpleCache")
+ public void simpleRemoveAll() {
+ }
+
+ @CacheResult(cacheName = "testSimple")
+ public SampleObject anotherSimpleGet(String foo, Long bar) {
+ return null;
+ }
+
+ // @CacheKey
+
+ @CacheResult
+ public SampleObject multiKeysGet(@CacheKey Long id, Boolean notUsed,
+ @CacheKey String domain) {
+ return null;
+ }
+
+ // @CacheValue
+
+ @CachePut(cacheName = "simpleCache")
+ public void noCacheValue(Long id) {
+ }
+
+ @CachePut(cacheName = "simpleCache")
+ public void multiCacheValues(Long id, @CacheValue SampleObject instance,
+ @CacheValue SampleObject anotherInstance) {
+ }
+
+ // Parameter annotation
+
+ @CacheResult(cacheName = "simpleCache")
+ public SampleObject annotatedGet(@CacheKey Long id, @Value("${foo}") String foo) {
+ return null;
+ }
+
+ // Full config
+
+ @CacheResult(cacheName = "simpleCache", skipGet = true,
+ cachedExceptions = Exception.class, nonCachedExceptions = RuntimeException.class)
+ public SampleObject fullGetConfig(@CacheKey Long id) {
+ return null;
+ }
+
+ @CachePut(cacheName = "simpleCache", afterInvocation = false,
+ cacheFor = Exception.class, noCacheFor = RuntimeException.class)
+ public void fullPutConfig(@CacheKey Long id, @CacheValue SampleObject instance) {
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheKeyGenerator.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheKeyGenerator.java
new file mode 100644
index 0000000000..db260026f2
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheKeyGenerator.java
@@ -0,0 +1,25 @@
+package org.springframework.cache.jcache.support;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.annotation.CacheKeyGenerator;
+import javax.cache.annotation.CacheKeyInvocationContext;
+import javax.cache.annotation.GeneratedCacheKey;
+
+import org.springframework.cache.jcache.interceptor.SimpleGeneratedCacheKey;
+
+/**
+ * A simple test key generator that only takes the first key arguments into
+ * account. To be used with a multi parameters key to validate it has been
+ * used properly.
+ *
+ * @author Stephane Nicoll
+ */
+public class TestableCacheKeyGenerator implements CacheKeyGenerator {
+
+ @Override
+ public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext extends Annotation> context) {
+ return new SimpleGeneratedCacheKey(context.getKeyParameters()[0]);
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolver.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolver.java
new file mode 100644
index 0000000000..a5d2444dc6
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.support;
+
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.mock;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.Cache;
+import javax.cache.annotation.CacheInvocationContext;
+import javax.cache.annotation.CacheResolver;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class TestableCacheResolver implements CacheResolver {
+
+ @Override
+ public Cache resolveCache(CacheInvocationContext extends Annotation> cacheInvocationContext) {
+ String cacheName = cacheInvocationContext.getCacheName();
+ Cache mock = mock(Cache.class);
+ given(mock.getName()).willReturn(cacheName);
+ return mock;
+ }
+
+}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolverFactory.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolverFactory.java
new file mode 100644
index 0000000000..48888e5962
--- /dev/null
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/support/TestableCacheResolverFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.jcache.support;
+
+import java.lang.annotation.Annotation;
+
+import javax.cache.annotation.CacheMethodDetails;
+import javax.cache.annotation.CacheResolver;
+import javax.cache.annotation.CacheResolverFactory;
+import javax.cache.annotation.CacheResult;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class TestableCacheResolverFactory implements CacheResolverFactory {
+
+ @Override
+ public CacheResolver getCacheResolver(CacheMethodDetails extends Annotation> cacheMethodDetails) {
+ return new TestableCacheResolver();
+ }
+
+ @Override
+ public CacheResolver getExceptionCacheResolver(CacheMethodDetails cacheMethodDetails) {
+ return new TestableCacheResolver();
+ }
+
+}
diff --git a/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheNamespaceDriven.xml b/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheNamespaceDriven.xml
new file mode 100644
index 0000000000..76565f1e4e
--- /dev/null
+++ b/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheNamespaceDriven.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheStandaloneConfig.xml b/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheStandaloneConfig.xml
new file mode 100644
index 0000000000..467604f4ae
--- /dev/null
+++ b/spring-context-support/src/test/resources/org/springframework/cache/jcache/config/jCacheStandaloneConfig.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
index dc49cd0383..c9f88bd778 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,11 +35,12 @@ import org.springframework.util.CollectionUtils;
* Spring's annotation-driven cache management capability.
*
* @author Chris Beams
+ * @author Stephane Nicoll
* @since 3.1
* @see EnableCaching
*/
@Configuration
-public abstract class AbstractCachingConfiguration implements ImportAware {
+public abstract class AbstractCachingConfiguration implements ImportAware {
protected AnnotationAttributes enableCaching;
@@ -51,7 +52,7 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
private Collection cacheManagerBeans;
@Autowired(required=false)
- private Collection cachingConfigurers;
+ private Collection cachingConfigurers;
@Override
@@ -82,9 +83,8 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
"Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
}
- CachingConfigurer cachingConfigurer = cachingConfigurers.iterator().next();
- this.cacheManager = cachingConfigurer.cacheManager();
- this.keyGenerator = cachingConfigurer.keyGenerator();
+ C cachingConfigurer = cachingConfigurers.iterator().next();
+ useCachingConfigurer(cachingConfigurer);
}
else if (!CollectionUtils.isEmpty(cacheManagerBeans)) {
int nManagers = cacheManagerBeans.size();
@@ -95,8 +95,7 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
"to make explicit which CacheManager should be used for " +
"annotation-driven cache management.");
}
- CacheManager cacheManager = cacheManagerBeans.iterator().next();
- this.cacheManager = cacheManager;
+ this.cacheManager = cacheManagerBeans.iterator().next();
// keyGenerator remains null; will fall back to default within CacheInterceptor
}
else {
@@ -105,4 +104,13 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
"from your configuration.");
}
}
+
+ /**
+ * Extract the configuration from the nominated {@link CachingConfigurer}.
+ */
+ protected void useCachingConfigurer(C config) {
+ this.cacheManager = config.cacheManager();
+ this.keyGenerator = config.keyGenerator();
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
index b4fb7e5c83..08478cc316 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurationSelector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,24 +16,39 @@
package org.springframework.cache.annotation;
+import java.util.ArrayList;
+import java.util.List;
+
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AutoProxyRegistrar;
+import org.springframework.util.ClassUtils;
/**
- * Selects which implementation of {@link AbstractCachingConfiguration} should be used
+ * Select which implementation of {@link AbstractCachingConfiguration} should be used
* based on the value of {@link EnableCaching#mode} on the importing {@code @Configuration}
* class.
+ * Detect the presence of JSR-107 and enables JCache support accordingly.
*
* @author Chris Beams
+ * @author Stephane Nicoll
* @since 3.1
* @see EnableCaching
* @see ProxyCachingConfiguration
* @see AnnotationConfigUtils#CACHE_ASPECT_CONFIGURATION_CLASS_NAME
+ * @see AnnotationConfigUtils#JCACHE_ASPECT_CONFIGURATION_CLASS_NAME
*/
public class CachingConfigurationSelector extends AdviceModeImportSelector {
+ private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
+ "org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
+
+ private static final boolean jsr107Present = ClassUtils.isPresent(
+ "javax.cache.Cache", CachingConfigurationSelector.class.getClassLoader());
+ private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ PROXY_JCACHE_CONFIGURATION_CLASS, CachingConfigurationSelector.class.getClassLoader());
+
/**
* {@inheritDoc}
* @return {@link ProxyCachingConfiguration} or {@code AspectJCacheConfiguration} for
@@ -43,12 +58,47 @@ public class CachingConfigurationSelector extends AdviceModeImportSelectorTake care of adding the necessary JSR-107 import if it is available.
+ */
+ private String[] getProxyImports() {
+ List result = new ArrayList();
+ result.add(AutoProxyRegistrar.class.getName());
+ result.add(ProxyCachingConfiguration.class.getName());
+ if (isJCacheAvailable()) {
+ result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#ASPECTJ}.
+ * Take care of adding the necessary JSR-107 import if it is available.
+ */
+ private String[] getAspectJImports() {
+ List result = new ArrayList();
+ result.add(AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
+ if (isJCacheAvailable()) {
+ result.add(AnnotationConfigUtils.JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * Specify if the JSR-107 API and Spring's jCache implementation are available
+ * in the classpath.
+ */
+ private boolean isJCacheAvailable() {
+ return jsr107Present && jCacheImplPresent;
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
index 2a477b9e8b..c475a3f8e4 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java
@@ -75,6 +75,12 @@ import org.springframework.core.Ordered;
* proxy- or AspectJ-based advice that weaves the interceptor into the call stack when
* {@link org.springframework.cache.annotation.Cacheable @Cacheable} methods are invoked.
*
+ * If the JSR-107 API and Spring's JCache implementation are present, the necessary
+ * components to manage standard cache annotations are also registered. This creates the
+ * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when
+ * methods annotated with {@code CacheResult}, {@code CachePut}, {@code CacheRemove} or
+ * {@code CacheRemoveAll} are invoked.
+ *
*
A bean of type {@link org.springframework.cache.CacheManager CacheManager}
* must be registered , as there is no reasonable default that the framework can
* use as a convention. And whereas the {@code } element assumes
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
index 91e5366f5d..1e1fbc52d0 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import org.springframework.context.annotation.Role;
* @see CachingConfigurationSelector
*/
@Configuration
-public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
+public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(name=AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
diff --git a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
index 6ed83c4026..1fb2cd55db 100644
--- a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
+++ b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package org.springframework.cache.config;
import static org.springframework.context.annotation.AnnotationConfigUtils.*;
+import org.w3c.dom.Element;
+
import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -29,7 +31,7 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
-import org.w3c.dom.Element;
+import org.springframework.util.ClassUtils;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
@@ -43,11 +45,23 @@ import org.w3c.dom.Element;
* '{@code proxy-target-class}' attribute to '{@code true}', which will
* result in class-based proxies being created.
*
+ * If the JSR-107 API and Spring's JCache implementation are present,
+ * the necessary infrastructure beans required to handle methods annotated
+ * with {@code CacheResult}, {@code CachePut}, {@code CacheRemove} or
+ * {@code CacheRemoveAll} are also registered.
+ *
* @author Costin Leau
+ * @author Stephane Nicoll
* @since 3.1
*/
class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser {
+ private static final boolean jsr107Present = ClassUtils.isPresent(
+ "javax.cache.Cache", AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
+
+ private static final boolean jCacheImplPresent = ClassUtils.isPresent(
+ JCACHE_OPERATION_SOURCE_CLASS, AnnotationDrivenCacheBeanDefinitionParser.class.getClassLoader());
+
/**
* Parses the '{@code }' tag. Will
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary
@@ -62,46 +76,39 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
}
else {
// mode="proxy"
- AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
+ registerCacheAdvisor(element, parserContext);
}
return null;
}
+ private void registerCacheAspect(Element element, ParserContext parserContext) {
+ SpringCachingConfigurer.registerCacheAspect(element, parserContext);
+ if (jsr107Present && jCacheImplPresent) { // Register JCache aspect
+ JCacheCachingConfigurer.registerCacheAspect(element, parserContext);
+ }
+ }
+
+ private void registerCacheAdvisor(Element element, ParserContext parserContext) {
+ AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
+ SpringCachingConfigurer.registerCacheAdvisor(element, parserContext);
+ if (jsr107Present && jCacheImplPresent) { // Register JCache advisor
+ JCacheCachingConfigurer.registerCacheAdvisor(element, parserContext);
+ }
+ }
+
private static void parseCacheManagerProperty(Element element, BeanDefinition def) {
def.getPropertyValues().add("cacheManager",
new RuntimeBeanReference(CacheNamespaceHandler.extractCacheManager(element)));
}
- /**
- * Registers a
- *
- *
- *
- *
- *
- *
- */
- 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");
- parseCacheManagerProperty(element, def);
- CacheNamespaceHandler.parseKeyGenerator(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.
+ * Configure the necessary infrastructure to support the Spring's caching annotations.
*/
- private static class AopAutoProxyConfigurer {
-
- public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
- AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
+ private static class SpringCachingConfigurer {
+ private static void registerCacheAdvisor(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ADVISOR_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element);
@@ -139,5 +146,93 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
parserContext.registerComponent(compositeDef);
}
}
+
+ /**
+ * Registers a
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+ private static 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");
+ parseCacheManagerProperty(element, def);
+ CacheNamespaceHandler.parseKeyGenerator(element, def);
+ parserContext.registerBeanComponent(new BeanComponentDefinition(def, CACHE_ASPECT_BEAN_NAME));
+ }
+ }
}
+
+ /**
+ * Configure the necessary infrastructure to support the standard JSR-107 caching annotations.
+ */
+ private static class JCacheCachingConfigurer {
+
+ private static void registerCacheAdvisor(Element element, ParserContext parserContext) {
+ if (!parserContext.getRegistry().containsBeanDefinition(JCACHE_ADVISOR_BEAN_NAME)) {
+ Object eleSource = parserContext.extractSource(element);
+
+ // Create the CacheOperationSource definition.
+ BeanDefinition sourceDef = createJCacheOperationSourceBeanDefinition(element, eleSource);
+ String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
+
+ // Create the CacheInterceptor definition.
+ RootBeanDefinition interceptorDef = new RootBeanDefinition(JCACHE_INTERCEPTOR_CLASS);
+ interceptorDef.setSource(eleSource);
+ interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ interceptorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
+ String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
+
+ // Create the CacheAdvisor definition.
+ RootBeanDefinition advisorDef = new RootBeanDefinition(JCACHE_ADVISOR_FACTORY_CLASS);
+ advisorDef.setSource(eleSource);
+ advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ advisorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
+ advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
+ if (element.hasAttribute("order")) {
+ advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
+ }
+ parserContext.getRegistry().registerBeanDefinition(JCACHE_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, JCACHE_ADVISOR_BEAN_NAME));
+ parserContext.registerComponent(compositeDef);
+ }
+ }
+
+ private static void registerCacheAspect(Element element, ParserContext parserContext) {
+ if (!parserContext.getRegistry().containsBeanDefinition(JCACHE_ASPECT_BEAN_NAME)) {
+ Object eleSource = parserContext.extractSource(element);
+ RootBeanDefinition def = new RootBeanDefinition();
+ def.setBeanClassName(JCACHE_ASPECT_CLASS_NAME);
+ def.setFactoryMethodName("aspectOf");
+ BeanDefinition sourceDef = createJCacheOperationSourceBeanDefinition(element, eleSource);
+ String sourceName =
+ parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
+ def.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
+
+ parserContext.registerBeanComponent(new BeanComponentDefinition(sourceDef, sourceName));
+ parserContext.registerBeanComponent(new BeanComponentDefinition(def, JCACHE_ASPECT_BEAN_NAME));
+ }
+ }
+
+ private static RootBeanDefinition createJCacheOperationSourceBeanDefinition(
+ Element element, Object eleSource) {
+ RootBeanDefinition sourceDef = new RootBeanDefinition(JCACHE_OPERATION_SOURCE_CLASS);
+ sourceDef.setSource(eleSource);
+ sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ parseCacheManagerProperty(element, sourceDef);
+ CacheNamespaceHandler.parseKeyGenerator(element, sourceDef);
+ return sourceDef;
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
index 11dc537f03..8eb1eb9117 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
@@ -28,7 +28,6 @@ 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 CacheOperation} that caches
@@ -68,14 +67,13 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
protected final Log logger = LogFactory.getLog(getClass());
/**
- * Cache of CacheOperations, keyed by DefaultCacheKey (Method + target Class).
+ * Cache of CacheOperations, keyed by {@link MethodCacheKey} (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>(1024);
-
/**
* Determine the caching attribute for this method invocation.
* Defaults to the class's caching attribute if no method attribute is found.
@@ -123,7 +121,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, Class> targetClass) {
- return new DefaultCacheKey(method, targetClass);
+ return new MethodCacheKey(method, targetClass);
}
private Collection computeCacheOperations(Method method, Class> targetClass) {
@@ -188,38 +186,4 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
protected boolean allowPublicMethodsOnly() {
return false;
}
-
-
- /**
- * Default cache key for the CacheOperation 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);
- }
- }
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/BasicCacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/BasicCacheOperation.java
new file mode 100644
index 0000000000..477d808f2a
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/BasicCacheOperation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+import java.util.Set;
+
+/**
+ * The base interface that all cache operations must implement.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface BasicCacheOperation {
+
+ /**
+ * Return the cache name(s) associated to the operation.
+ */
+ Set getCacheNames();
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
index 802fcbda21..0f04f3f19d 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -182,7 +182,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
return new CacheOperationContext(operation, method, args, target, targetClass);
}
- protected Object execute(Invoker invoker, Object target, Method method, Object[] args) {
+ protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// check whether aspect is enabled
// to cope with cases where the AJ is pulled in automatically
if (this.initialized) {
@@ -204,7 +204,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
return targetClass;
}
- private Object execute(Invoker invoker, CacheOperationContexts contexts) {
+ private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);
@@ -343,12 +343,6 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
}
- public interface Invoker {
-
- Object invoke();
- }
-
-
private class CacheOperationContexts {
private final MultiValueMap, CacheOperationContext> contexts =
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java
index b4b19bea3b..f411ac42ab 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheInterceptor.java
@@ -45,12 +45,13 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
- Invoker aopAllianceInvoker = new Invoker() {
+ CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
@Override
public Object invoke() {
try {
return invocation.proceed();
- } catch (Throwable ex) {
+ }
+ catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
@@ -58,17 +59,9 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
- } catch (ThrowableWrapper th) {
- throw th.original;
}
- }
-
-
- private static class ThrowableWrapper extends RuntimeException {
- private final Throwable original;
-
- ThrowableWrapper(Throwable original) {
- this.original = original;
+ catch (CacheOperationInvoker.ThrowableWrapper th) {
+ throw th.getOriginal();
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvocationContext.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvocationContext.java
new file mode 100644
index 0000000000..f643e6d0f4
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvocationContext.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+import java.lang.reflect.Method;
+
+/**
+ * Represent the context of the invocation of a cache operation.
+ *
+ * The cache operation is static and independent of a particular invocation,
+ * this gathers the operation and a particular invocation.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface CacheOperationInvocationContext {
+
+ /**
+ * Return the cache operation
+ */
+ O getOperation();
+
+ /**
+ * Return the target instance on which the method was invoked
+ */
+ Object getTarget();
+
+ /**
+ * Return the method
+ */
+ Method getMethod();
+
+ /**
+ * Return the argument used to invoke the method
+ */
+ Object[] getArgs();
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
new file mode 100644
index 0000000000..4ca3642c8d
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+/**
+ * Abstract the invocation of a cache operation.
+ *
+ * Provide a special exception that can be used to indicate that the
+ * underlying invocation has thrown a checked exception, allowing the
+ * callers to threat these in a different manner if necessary.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface CacheOperationInvoker {
+
+ /**
+ * Invoke the cache operation defined by this instance. Can throw a
+ * {@link ThrowableWrapper} if that operation wants to explicitly
+ * indicate that a checked exception has occurred.
+ * @return the result of the operation
+ * @throws ThrowableWrapper if a checked exception has been thrown
+ */
+ Object invoke() throws ThrowableWrapper;
+
+ /**
+ * Wrap any exception thrown while invoking {@link #invoke()}
+ */
+ @SuppressWarnings("serial")
+ public static class ThrowableWrapper extends RuntimeException {
+
+ private final Throwable original;
+
+ public ThrowableWrapper(Throwable original) {
+ super(original.getMessage(), original);
+ this.original = original;
+ }
+
+ public Throwable getOriginal() {
+ return original;
+ }
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheResolver.java
new file mode 100644
index 0000000000..55534e888e
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheResolver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+import java.util.Collection;
+
+import org.springframework.cache.Cache;
+
+/**
+ * Determine the {@link Cache} instance(s) to use for an intercepted method invocation.
+ *
+ *
Implementations MUST be thread-safe.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface CacheResolver {
+
+ /**
+ * Return the cache(s) to use for the specified invocation.
+ *
+ * @param context the context of the particular invocation
+ * @return the cache(s) to use (never null)
+ */
+ Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context);
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java
new file mode 100644
index 0000000000..e1076a2a9f
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java
@@ -0,0 +1,47 @@
+package org.springframework.cache.interceptor;
+
+import java.lang.reflect.Method;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Represent a method on a particular {@link Class} and is suitable as a key.
+ *
Mainly for internal use within the framework.
+ *
+ * @author Costin Leau
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public final class MethodCacheKey {
+
+ private final Method method;
+
+ private final Class> targetClass;
+
+ public MethodCacheKey(Method method, Class> targetClass) {
+ Assert.notNull(method, "method must be set.");
+ Assert.notNull(targetClass, "targetClass must be set.");
+ this.method = method;
+ this.targetClass = targetClass;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MethodCacheKey)) {
+ return false;
+ }
+ MethodCacheKey otherKey = (MethodCacheKey) 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);
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java
new file mode 100644
index 0000000000..a33f457d28
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cache.interceptor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * A simple {@link CacheResolver} that resolves the {@link Cache} instance(s)
+ * based on a configurable {@link CacheManager} and the name of the
+ * cache(s): {@link BasicCacheOperation#getCacheNames()}
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see BasicCacheOperation#getCacheNames()
+ */
+public class SimpleCacheResolver implements CacheResolver {
+
+ private final CacheManager cacheManager;
+
+ public SimpleCacheResolver(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ @Override
+ public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
+ Collection result = new ArrayList();
+ for (String cacheName : context.getOperation().getCacheNames()) {
+ Cache cache = cacheManager.getCache(cacheName);
+ Assert.notNull(cache, "Cannot find cache named '" + cacheName + "' for " + context.getOperation());
+ result.add(cache);
+ }
+ return result;
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
index 097be2d20b..e8fb72bc4f 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java
@@ -30,11 +30,12 @@ import org.springframework.util.StringUtils;
* @see SimpleKeyGenerator
*/
@SuppressWarnings("serial")
-public final class SimpleKey implements Serializable {
+public class SimpleKey implements Serializable {
public static final SimpleKey EMPTY = new SimpleKey();
private final Object[] params;
+ private final int hashCode;
/**
@@ -45,9 +46,9 @@ public final class SimpleKey implements Serializable {
Assert.notNull(elements, "Elements must not be null");
this.params = new Object[elements.length];
System.arraycopy(elements, 0, this.params, 0, elements.length);
+ this.hashCode = Arrays.deepHashCode(this.params);
}
-
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof SimpleKey
@@ -55,13 +56,13 @@ public final class SimpleKey implements Serializable {
}
@Override
- public int hashCode() {
- return Arrays.deepHashCode(this.params);
+ public final int hashCode() {
+ return hashCode;
}
@Override
public String toString() {
- return "SimpleKey [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
+ return getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
index ad0481dee0..20353ae664 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ import org.springframework.util.ClassUtils;
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
+ * @author Stephane Nicoll
* @since 2.5
* @see ContextAnnotationAutowireCandidateResolver
* @see CommonAnnotationBeanPostProcessor
@@ -147,6 +148,48 @@ public class AnnotationConfigUtils {
public static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJCachingConfiguration";
+ /**
+ * The bean name of the internally managed JSR-107 cache advisor.
+ */
+ public static final String JCACHE_ADVISOR_BEAN_NAME =
+ "org.springframework.cache.config.internalJCacheAdvisor";
+
+ /**
+ * The class name of the JSR-107 cache operation source.
+ */
+ public static final String JCACHE_OPERATION_SOURCE_CLASS
+ = "org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource";
+
+ /**
+ * The class name of the JSR-107 cache interceptor.
+ */
+ public static final String JCACHE_INTERCEPTOR_CLASS =
+ "org.springframework.cache.jcache.interceptor.JCacheInterceptor";
+
+ /**
+ * The class name of the JSR-107 cache advisor factory.
+ */
+ public static final String JCACHE_ADVISOR_FACTORY_CLASS =
+ "org.springframework.cache.jcache.interceptor.BeanFactoryJCacheOperationSourceAdvisor";
+
+ /**
+ * The bean name of the internally managed JSR-107 cache aspect.
+ */
+ public static final String JCACHE_ASPECT_BEAN_NAME =
+ "org.springframework.cache.config.internalJCacheAspect";
+
+ /**
+ * The class name of the AspectJ JSR-107 cache aspect.
+ */
+ public static final String JCACHE_ASPECT_CLASS_NAME =
+ "org.springframework.cache.aspectj.JCacheCacheAspect";
+
+ /**
+ * The name of the AspectJ JSR-107 cache aspect @{@code Configuration} class.
+ */
+ public static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
+ "org.springframework.cache.aspectj.AspectJJCacheConfiguration";
+
/**
* The bean name of the internally managed JPA annotation processor.
*/
diff --git a/spring-core/src/main/java/org/springframework/util/filter/ExceptionTypeFilter.java b/spring-core/src/main/java/org/springframework/util/filter/ExceptionTypeFilter.java
new file mode 100644
index 0000000000..cd005e426d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/filter/ExceptionTypeFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.filter;
+
+import java.util.Collection;
+
+/**
+ * An {@link InstanceFilter} implementation that handles exception types. A type
+ * will match against a given candidate if it is assignable to that candidate.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class ExceptionTypeFilter extends InstanceFilter> {
+
+ public ExceptionTypeFilter(Collection extends Class extends Throwable>> includes,
+ Collection extends Class extends Throwable>> excludes, boolean matchIfEmpty) {
+ super(includes, excludes, matchIfEmpty);
+ }
+
+ @Override
+ protected boolean match(Class extends Throwable> instance, Class extends Throwable> candidate) {
+ return candidate.isAssignableFrom(instance);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/filter/InstanceFilter.java b/spring-core/src/main/java/org/springframework/util/filter/InstanceFilter.java
new file mode 100644
index 0000000000..99f96000ce
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/filter/InstanceFilter.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.filter;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.springframework.util.Assert;
+
+/**
+ * A simple instance filter that checks if a given instance match based on
+ * a collection of includes and excludes element.
+ *
+ * Subclasses may want to override {@link #match(Object, Object)} to provide
+ * a custom matching algorithm.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class InstanceFilter {
+
+ private final Collection extends T> includes;
+
+ private final Collection extends T> excludes;
+
+ private final boolean matchIfEmpty;
+
+ /**
+ * Create a new instance based on includes/excludes collections.
+ * A particular element will match if it "matches" the one of the element in the
+ * includes list and does not match one of the element in the excludes list.
+ *
Subclasses may redefine what matching means. By default, an element match with
+ * another if it is equals according to {@link Object#equals(Object)}
+ *
If both collections are empty, {@code matchIfEmpty} defines if
+ * an element matches or not.
+ *
+ * @param includes the collection of includes
+ * @param excludes the collection of excludes
+ * @param matchIfEmpty the matching result if both the includes and the excludes
+ * collections are empty
+ */
+ public InstanceFilter(Collection extends T> includes,
+ Collection extends T> excludes, boolean matchIfEmpty) {
+
+ this.includes = includes != null ? includes : Collections.emptyList();
+ this.excludes = excludes != null ? excludes : Collections.emptyList();
+ this.matchIfEmpty = matchIfEmpty;
+ }
+
+ /**
+ * Determine if the specified {code instance} matches this filter.
+ */
+ public boolean match(T instance) {
+ Assert.notNull(instance, "The instance to match is mandatory.");
+
+ boolean includesSet = !includes.isEmpty();
+ boolean excludesSet = !excludes.isEmpty();
+ if (!includesSet && !excludesSet) {
+ return matchIfEmpty;
+ }
+
+ boolean matchIncludes = match(instance, includes);
+ boolean matchExcludes = match(instance, excludes);
+
+ if (!includesSet) {
+ return !matchExcludes;
+ }
+
+ if (!excludesSet) {
+ return matchIncludes;
+ }
+ return matchIncludes && !matchExcludes;
+ }
+
+ /**
+ * Determine if the specified {@code instance} is equal to the
+ * specified {@code candidate}.
+ *
+ * @param instance the instance to handle
+ * @param candidate a candidate defined by this filter
+ * @return {@code true} if the instance matches the candidate
+ */
+ protected boolean match(T instance, T candidate) {
+ return instance.equals(candidate);
+ }
+
+ /**
+ * Determine if the specified {@code instance} matches one of the candidates.
+ * If the candidates collection is {@code null}, returns {@code false}.
+ *
+ * @param instance the instance to check
+ * @param candidates a list of candidates
+ * @return {@code true} if the instance match or the candidates collection is null
+ */
+ protected boolean match(T instance, Collection extends T> candidates) {
+ for (T candidate : candidates) {
+ if (match(instance, candidate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "includes=" + includes + ", excludes=" + excludes + ", matchIfEmpty=" + matchIfEmpty;
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/util/filter/ExceptionTypeFilterTests.java b/spring-core/src/test/java/org/springframework/util/filter/ExceptionTypeFilterTests.java
new file mode 100644
index 0000000000..d8a05481ff
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/util/filter/ExceptionTypeFilterTests.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.filter;
+
+import static java.util.Arrays.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class ExceptionTypeFilterTests {
+
+ @Test
+ public void subClassMatch() {
+ ExceptionTypeFilter filter = new ExceptionTypeFilter(
+ asList(RuntimeException.class), null, true);
+ assertTrue(filter.match(RuntimeException.class));
+ assertTrue(filter.match(IllegalStateException.class));
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/util/filter/InstanceFilterTests.java b/spring-core/src/test/java/org/springframework/util/filter/InstanceFilterTests.java
new file mode 100644
index 0000000000..48c12156b3
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/util/filter/InstanceFilterTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.filter;
+
+import static java.util.Arrays.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * @author Stephane Nicoll
+ */
+public class InstanceFilterTests {
+
+ @Test
+ public void emptyFilterApplyMatchIfEmpty() {
+ InstanceFilter filter = new InstanceFilter(null, null, true);
+ match(filter, "foo");
+ match(filter, "bar");
+ }
+
+ @Test
+ public void includesFilter() {
+ InstanceFilter filter = new InstanceFilter(
+ asList("First", "Second"), null, true);
+ match(filter, "Second");
+ doNotMatch(filter, "foo");
+ }
+
+ @Test
+ public void excludesFilter() {
+ InstanceFilter filter = new InstanceFilter(
+ null, asList("First", "Second"), true);
+ doNotMatch(filter, "Second");
+ match(filter, "foo");
+ }
+
+ @Test
+ public void includesAndExcludesFilters() {
+ InstanceFilter filter = new InstanceFilter(
+ asList("foo", "Bar"), asList("First", "Second"), true);
+ doNotMatch(filter, "Second");
+ match(filter, "foo");
+ }
+
+ @Test
+ public void includesAndExcludesFiltersConflict() {
+ InstanceFilter filter = new InstanceFilter(
+ asList("First"), asList("First"), true);
+ doNotMatch(filter, "First");
+ }
+
+ private void match(InstanceFilter filter, T candidate) {
+ assertTrue("filter '" + filter + "' should match " + candidate, filter.match(candidate));
+ }
+
+ private void doNotMatch(InstanceFilter filter, T candidate) {
+ assertFalse("filter '" + filter + "' should not match " + candidate, filter.match(candidate));
+ }
+
+}