1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40
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
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
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
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")
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 }