View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.cache.annotation;
18  
19  import java.lang.annotation.ElementType;
20  import java.lang.annotation.Retention;
21  import java.lang.annotation.RetentionPolicy;
22  import java.lang.annotation.Target;
23  import java.lang.reflect.Method;
24  import java.util.Collection;
25  import java.util.Iterator;
26  
27  import org.junit.Rule;
28  import org.junit.Test;
29  import org.junit.rules.ExpectedException;
30  
31  import org.springframework.cache.interceptor.CacheEvictOperation;
32  import org.springframework.cache.interceptor.CacheOperation;
33  import org.springframework.cache.interceptor.CacheableOperation;
34  import org.springframework.util.ReflectionUtils;
35  
36  import static org.junit.Assert.*;
37  
38  /**
39   * @author Costin Leau
40   * @author Stephane Nicoll
41   */
42  public class AnnotationCacheOperationSourceTests {
43  
44  	@Rule
45  	public final ExpectedException thrown = ExpectedException.none();
46  
47  	private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
48  
49  	private Collection<CacheOperation> getOps(Class<?> target, String name,
50  											  int expectedNumberOfOperations) {
51  		Collection<CacheOperation> result = getOps(target, name);
52  		assertEquals("Wrong number of operation(s) for '"+name+"'",
53  				expectedNumberOfOperations, result.size());
54  		return result;
55  	}
56  
57  	private Collection<CacheOperation> getOps(Class<?> target, String name) {
58  		Method method = ReflectionUtils.findMethod(target, name);
59  		return source.getCacheOperations(method, target);
60  	}
61  
62  	@Test
63  	public void testSingularAnnotation() throws Exception {
64  		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singular", 1);
65  		assertTrue(ops.iterator().next() instanceof CacheableOperation);
66  	}
67  
68  	@Test
69  	public void testMultipleAnnotation() throws Exception {
70  		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multiple", 2);
71  		Iterator<CacheOperation> it = ops.iterator();
72  		assertTrue(it.next() instanceof CacheableOperation);
73  		assertTrue(it.next() instanceof CacheEvictOperation);
74  	}
75  
76  	@Test
77  	public void testCaching() throws Exception {
78  		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "caching", 2);
79  		Iterator<CacheOperation> it = ops.iterator();
80  		assertTrue(it.next() instanceof CacheableOperation);
81  		assertTrue(it.next() instanceof CacheEvictOperation);
82  	}
83  
84  	@Test
85  	public void testSingularStereotype() throws Exception {
86  		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleStereotype", 1);
87  		assertTrue(ops.iterator().next() instanceof CacheEvictOperation);
88  	}
89  
90  	@Test
91  	public void testMultipleStereotypes() throws Exception {
92  		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleStereotype", 3);
93  		Iterator<CacheOperation> it = ops.iterator();
94  		assertTrue(it.next() instanceof CacheableOperation);
95  		CacheOperation next = it.next();
96  		assertTrue(next instanceof CacheEvictOperation);
97  		assertTrue(next.getCacheNames().contains("foo"));
98  		next = it.next();
99  		assertTrue(next instanceof CacheEvictOperation);
100 		assertTrue(next.getCacheNames().contains("bar"));
101 	}
102 
103 	@Test
104 	public void testCustomKeyGenerator() {
105 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGenerator", 1);
106 		CacheOperation cacheOperation = ops.iterator().next();
107 		assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
108 	}
109 
110 	@Test
111 	public void testCustomKeyGeneratorInherited() {
112 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGeneratorInherited", 1);
113 		CacheOperation cacheOperation = ops.iterator().next();
114 		assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
115 	}
116 
117 	@Test
118 	public void testKeyAndKeyGeneratorCannotBeSetTogether() {
119 		try {
120 			getOps(AnnotatedClass.class, "invalidKeyAndKeyGeneratorSet");
121 			fail("Should have failed to parse @Cacheable annotation");
122 		} catch (IllegalStateException e) {
123 			// expected
124 		}
125 	}
126 
127 	@Test
128 	public void testCustomCacheManager() {
129 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheManager", 1);
130 		CacheOperation cacheOperation = ops.iterator().next();
131 		assertEquals("Custom cache manager not set", "custom", cacheOperation.getCacheManager());
132 	}
133 
134 	@Test
135 	public void testCustomCacheManagerInherited() {
136 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheManagerInherited", 1);
137 		CacheOperation cacheOperation = ops.iterator().next();
138 		assertEquals("Custom cache manager not set", "custom", cacheOperation.getCacheManager());
139 	}
140 
141 	@Test
142 	public void testCustomCacheResolver() {
143 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheResolver", 1);
144 		CacheOperation cacheOperation = ops.iterator().next();
145 		assertEquals("Custom cache resolver not set", "custom", cacheOperation.getCacheResolver());
146 	}
147 
148 	@Test
149 	public void testCustomCacheResolverInherited() {
150 		Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheResolverInherited", 1);
151 		CacheOperation cacheOperation = ops.iterator().next();
152 		assertEquals("Custom cache resolver not set", "custom", cacheOperation.getCacheResolver());
153 	}
154 
155 	@Test
156 	public void testCacheResolverAndCacheManagerCannotBeSetTogether() {
157 		try {
158 			getOps(AnnotatedClass.class, "invalidCacheResolverAndCacheManagerSet");
159 			fail("Should have failed to parse @Cacheable annotation");
160 		} catch (IllegalStateException e) {
161 			// expected
162 		}
163 	}
164 
165 	@Test
166 	public void fullClassLevelWithCustomCacheName() {
167 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheName", 1);
168 		CacheOperation cacheOperation = ops.iterator().next();
169 		assertSharedConfig(cacheOperation, "classKeyGenerator", "", "classCacheResolver", "custom");
170 	}
171 
172 	@Test
173 	public void fullClassLevelWithCustomKeyManager() {
174 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
175 		CacheOperation cacheOperation = ops.iterator().next();
176 		assertSharedConfig(cacheOperation, "custom", "", "classCacheResolver" , "classCacheName");
177 	}
178 
179 	@Test
180 	public void fullClassLevelWithCustomCacheManager() {
181 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
182 		CacheOperation cacheOperation = ops.iterator().next();
183 		assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
184 	}
185 
186 	@Test
187 	public void fullClassLevelWithCustomCacheResolver() {
188 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheResolver", 1);
189 		CacheOperation cacheOperation = ops.iterator().next();
190 		assertSharedConfig(cacheOperation, "classKeyGenerator", "", "custom" , "classCacheName");
191 	}
192 
193 	@Test
194 	public void validateAtLeastOneCacheNameMustBeSet() {
195 		thrown.expect(IllegalStateException.class);
196 		getOps(AnnotatedClass.class, "noCacheNameSpecified");
197 	}
198 
199 	@Test
200 	public void customClassLevelWithCustomCacheName() {
201 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithCustomDefault.class, "methodLevelCacheName", 1);
202 		CacheOperation cacheOperation = ops.iterator().next();
203 		assertSharedConfig(cacheOperation, "classKeyGenerator", "", "classCacheResolver", "custom");
204 	}
205 
206 	@Test
207 	public void severalCacheConfigUseClosest() {
208 		Collection<CacheOperation> ops = getOps(MultipleCacheConfig.class, "multipleCacheConfig");
209 		CacheOperation cacheOperation = ops.iterator().next();
210 		assertSharedConfig(cacheOperation, "", "", "", "myCache");
211 	}
212 
213 	@Test
214 	public void partialClassLevelWithCustomCacheManager() {
215 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheManager", 1);
216 		CacheOperation cacheOperation = ops.iterator().next();
217 		assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
218 	}
219 
220 	@Test
221 	public void partialClassLevelWithCustomCacheResolver() {
222 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheResolver", 1);
223 		CacheOperation cacheOperation = ops.iterator().next();
224 		assertSharedConfig(cacheOperation, "classKeyGenerator", "", "custom", "classCacheName");
225 	}
226 
227 	@Test
228 	public void partialClassLevelWithNoCustomization() {
229 		Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "noCustomization", 1);
230 		CacheOperation cacheOperation = ops.iterator().next();
231 		assertSharedConfig(cacheOperation, "classKeyGenerator", "classCacheManager", "", "classCacheName");
232 	}
233 
234 	private void assertSharedConfig(CacheOperation actual, String keyGenerator, String cacheManager,
235 									String cacheResolver, String... cacheNames) {
236 		assertEquals("Wrong key manager",  keyGenerator, actual.getKeyGenerator());
237 		assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager());
238 		assertEquals("Wrong cache resolver", cacheResolver, actual.getCacheResolver());
239 		for (String cacheName : cacheNames) {
240 			assertTrue("Cache '"+cacheName+"' not found (got "+actual.getCacheNames(),
241 					actual.getCacheNames().contains(cacheName));
242 		}
243 		assertEquals("Wrong number of cache name(s)", cacheNames.length, actual.getCacheNames().size());
244 	}
245 
246 	private static class AnnotatedClass {
247 		@Cacheable("test")
248 		public void singular() {
249 		}
250 
251 		@CacheEvict("test")
252 		@Cacheable("test")
253 		public void multiple() {
254 		}
255 
256 		@Caching(cacheable = {@Cacheable("test")}, evict = {@CacheEvict("test")})
257 		public void caching() {
258 		}
259 
260 		@Cacheable(value = "test", keyGenerator = "custom")
261 		public void customKeyGenerator() {
262 		}
263 
264 		@Cacheable(value = "test", cacheManager = "custom")
265 		public void customCacheManager() {
266 		}
267 
268 		@Cacheable(value = "test", cacheResolver = "custom")
269 		public void customCacheResolver() {
270 		}
271 
272 		@EvictFoo
273 		public void singleStereotype() {
274 		}
275 
276 		@EvictFoo
277 		@CacheableFoo
278 		@EvictBar
279 		public void multipleStereotype() {
280 		}
281 
282 		@Caching(cacheable = {@Cacheable(value = "test", key = "a"), @Cacheable(value = "test", key = "b")})
283 		public void multipleCaching() {
284 		}
285 
286 		@CacheableFooCustomKeyGenerator
287 		public void customKeyGeneratorInherited() {
288 		}
289 
290 		@Cacheable(value = "test", key = "#root.methodName", keyGenerator = "custom")
291 		public void invalidKeyAndKeyGeneratorSet() {
292 		}
293 
294 		@CacheableFooCustomCacheManager
295 		public void customCacheManagerInherited() {
296 		}
297 
298 		@CacheableFooCustomCacheResolver
299 		public void customCacheResolverInherited() {
300 		}
301 
302 		@Cacheable(value = "test", cacheManager = "custom", cacheResolver = "custom")
303 		public void invalidCacheResolverAndCacheManagerSet() {
304 		}
305 
306 		@Cacheable // cache name can be inherited from CacheConfig. There's none here
307 		public void noCacheNameSpecified() {
308 		}
309 	}
310 
311 	@CacheConfig(cacheNames = "classCacheName",
312 			keyGenerator = "classKeyGenerator",
313 			cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
314 	private static class AnnotatedClassWithFullDefault {
315 
316 		@Cacheable("custom")
317 		public void methodLevelCacheName() {
318 		}
319 
320 		@Cacheable(keyGenerator = "custom")
321 		public void methodLevelKeyGenerator() {
322 		}
323 
324 		@Cacheable(cacheManager = "custom")
325 		public void methodLevelCacheManager() {
326 		}
327 
328 		@Cacheable(cacheResolver = "custom")
329 		public void methodLevelCacheResolver() {
330 		}
331 	}
332 
333 	@CacheConfigFoo
334 	private static class AnnotatedClassWithCustomDefault {
335 
336 		@Cacheable("custom")
337 		public void methodLevelCacheName() {
338 		}
339 	}
340 
341 	@CacheConfig(cacheNames = "classCacheName",
342 			keyGenerator = "classKeyGenerator",
343 			cacheManager = "classCacheManager")
344 	private static class AnnotatedClassWithSomeDefault {
345 
346 		@Cacheable(cacheManager = "custom")
347 		public void methodLevelCacheManager() {
348 		}
349 
350 		@Cacheable(cacheResolver = "custom")
351 		public void methodLevelCacheResolver() {
352 		}
353 
354 		@Cacheable
355 		public void noCustomization() {
356 		}
357 
358 	}
359 
360 	@CacheConfigFoo
361 	@CacheConfig(cacheNames = "myCache") // multiple sources
362 	private static class MultipleCacheConfig {
363 
364 		@Cacheable
365 		public void multipleCacheConfig() {
366 		}
367 	}
368 
369 	@Retention(RetentionPolicy.RUNTIME)
370 	@Target(ElementType.METHOD)
371 	@Cacheable("foo")
372 	public @interface CacheableFoo {
373 	}
374 
375 	@Retention(RetentionPolicy.RUNTIME)
376 	@Target(ElementType.METHOD)
377 	@Cacheable(value = "foo", keyGenerator = "custom")
378 	public @interface CacheableFooCustomKeyGenerator {
379 	}
380 
381 	@Retention(RetentionPolicy.RUNTIME)
382 	@Target(ElementType.METHOD)
383 	@Cacheable(value = "foo", cacheManager = "custom")
384 	public @interface CacheableFooCustomCacheManager {
385 	}
386 
387 	@Retention(RetentionPolicy.RUNTIME)
388 	@Target(ElementType.METHOD)
389 	@Cacheable(value = "foo", cacheResolver = "custom")
390 	public @interface CacheableFooCustomCacheResolver {
391 	}
392 
393 	@Retention(RetentionPolicy.RUNTIME)
394 	@Target(ElementType.METHOD)
395 	@CacheEvict(value = "foo")
396 	public @interface EvictFoo {
397 	}
398 
399 	@Retention(RetentionPolicy.RUNTIME)
400 	@Target(ElementType.METHOD)
401 	@CacheEvict(value = "bar")
402 	public @interface EvictBar {
403 	}
404 
405 	@Retention(RetentionPolicy.RUNTIME)
406 	@Target(ElementType.TYPE)
407 	@CacheConfig(keyGenerator = "classKeyGenerator",
408 			cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
409 	public @interface CacheConfigFoo {
410 	}
411 }