001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.cache.annotation;
018
019import java.io.Serializable;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.AnnotatedElement;
022import java.lang.reflect.Method;
023import java.util.ArrayList;
024import java.util.Collection;
025
026import org.springframework.cache.interceptor.CacheEvictOperation;
027import org.springframework.cache.interceptor.CacheOperation;
028import org.springframework.cache.interceptor.CachePutOperation;
029import org.springframework.cache.interceptor.CacheableOperation;
030import org.springframework.core.annotation.AnnotatedElementUtils;
031import org.springframework.util.ObjectUtils;
032import org.springframework.util.StringUtils;
033
034/**
035 * Strategy implementation for parsing Spring's {@link Caching}, {@link Cacheable},
036 * {@link CacheEvict}, and {@link CachePut} annotations.
037 *
038 * @author Costin Leau
039 * @author Juergen Hoeller
040 * @author Chris Beams
041 * @author Phillip Webb
042 * @author Stephane Nicoll
043 * @author Sam Brannen
044 * @since 3.1
045 */
046@SuppressWarnings("serial")
047public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
048
049        @Override
050        public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
051                DefaultCacheConfig defaultConfig = getDefaultCacheConfig(type);
052                return parseCacheAnnotations(defaultConfig, type);
053        }
054
055        @Override
056        public Collection<CacheOperation> parseCacheAnnotations(Method method) {
057                DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
058                return parseCacheAnnotations(defaultConfig, method);
059        }
060
061        private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
062                Collection<CacheOperation> ops = null;
063
064                Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
065                if (!cacheables.isEmpty()) {
066                        ops = lazyInit(ops);
067                        for (Cacheable cacheable : cacheables) {
068                                ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
069                        }
070                }
071
072                Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
073                if (!evicts.isEmpty()) {
074                        ops = lazyInit(ops);
075                        for (CacheEvict evict : evicts) {
076                                ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
077                        }
078                }
079
080                Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
081                if (!puts.isEmpty()) {
082                        ops = lazyInit(ops);
083                        for (CachePut put : puts) {
084                                ops.add(parsePutAnnotation(ae, cachingConfig, put));
085                        }
086                }
087
088                Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
089                if (!cachings.isEmpty()) {
090                        ops = lazyInit(ops);
091                        for (Caching caching : cachings) {
092                                Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
093                                if (cachingOps != null) {
094                                        ops.addAll(cachingOps);
095                                }
096                        }
097                }
098
099                return ops;
100        }
101
102        private <T extends Annotation> Collection<CacheOperation> lazyInit(Collection<CacheOperation> ops) {
103                return (ops != null ? ops : new ArrayList<CacheOperation>(1));
104        }
105
106        CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
107                CacheableOperation.Builder builder = new CacheableOperation.Builder();
108
109                builder.setName(ae.toString());
110                builder.setCacheNames(cacheable.cacheNames());
111                builder.setCondition(cacheable.condition());
112                builder.setUnless(cacheable.unless());
113                builder.setKey(cacheable.key());
114                builder.setKeyGenerator(cacheable.keyGenerator());
115                builder.setCacheManager(cacheable.cacheManager());
116                builder.setCacheResolver(cacheable.cacheResolver());
117                builder.setSync(cacheable.sync());
118
119                defaultConfig.applyDefault(builder);
120                CacheableOperation op = builder.build();
121                validateCacheOperation(ae, op);
122
123                return op;
124        }
125
126        CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
127                CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
128
129                builder.setName(ae.toString());
130                builder.setCacheNames(cacheEvict.cacheNames());
131                builder.setCondition(cacheEvict.condition());
132                builder.setKey(cacheEvict.key());
133                builder.setKeyGenerator(cacheEvict.keyGenerator());
134                builder.setCacheManager(cacheEvict.cacheManager());
135                builder.setCacheResolver(cacheEvict.cacheResolver());
136                builder.setCacheWide(cacheEvict.allEntries());
137                builder.setBeforeInvocation(cacheEvict.beforeInvocation());
138
139                defaultConfig.applyDefault(builder);
140                CacheEvictOperation op = builder.build();
141                validateCacheOperation(ae, op);
142
143                return op;
144        }
145
146        CacheOperation parsePutAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
147                CachePutOperation.Builder builder = new CachePutOperation.Builder();
148
149                builder.setName(ae.toString());
150                builder.setCacheNames(cachePut.cacheNames());
151                builder.setCondition(cachePut.condition());
152                builder.setUnless(cachePut.unless());
153                builder.setKey(cachePut.key());
154                builder.setKeyGenerator(cachePut.keyGenerator());
155                builder.setCacheManager(cachePut.cacheManager());
156                builder.setCacheResolver(cachePut.cacheResolver());
157
158                defaultConfig.applyDefault(builder);
159                CachePutOperation op = builder.build();
160                validateCacheOperation(ae, op);
161
162                return op;
163        }
164
165        Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) {
166                Collection<CacheOperation> ops = null;
167
168                Cacheable[] cacheables = caching.cacheable();
169                if (!ObjectUtils.isEmpty(cacheables)) {
170                        ops = lazyInit(ops);
171                        for (Cacheable cacheable : cacheables) {
172                                ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
173                        }
174                }
175                CacheEvict[] cacheEvicts = caching.evict();
176                if (!ObjectUtils.isEmpty(cacheEvicts)) {
177                        ops = lazyInit(ops);
178                        for (CacheEvict cacheEvict : cacheEvicts) {
179                                ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
180                        }
181                }
182                CachePut[] cachePuts = caching.put();
183                if (!ObjectUtils.isEmpty(cachePuts)) {
184                        ops = lazyInit(ops);
185                        for (CachePut cachePut : cachePuts) {
186                                ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
187                        }
188                }
189
190                return ops;
191        }
192
193        /**
194         * Provides the {@link DefaultCacheConfig} instance for the specified {@link Class}.
195         * @param target the class-level to handle
196         * @return the default config (never {@code null})
197         */
198        DefaultCacheConfig getDefaultCacheConfig(Class<?> target) {
199                CacheConfig annotation = AnnotatedElementUtils.getMergedAnnotation(target, CacheConfig.class);
200                if (annotation != null) {
201                        return new DefaultCacheConfig(annotation.cacheNames(), annotation.keyGenerator(),
202                                        annotation.cacheManager(), annotation.cacheResolver());
203                }
204                return new DefaultCacheConfig();
205        }
206
207        /**
208         * Validates the specified {@link CacheOperation}.
209         * <p>Throws an {@link IllegalStateException} if the state of the operation is
210         * invalid. As there might be multiple sources for default values, this ensure
211         * that the operation is in a proper state before being returned.
212         * @param ae the annotated element of the cache operation
213         * @param operation the {@link CacheOperation} to validate
214         */
215        private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
216                if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
217                        throw new IllegalStateException("Invalid cache annotation configuration on '" +
218                                        ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
219                                        "These attributes are mutually exclusive: either set the SpEL expression used to" +
220                                        "compute the key at runtime or set the name of the KeyGenerator bean to use.");
221                }
222                if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
223                        throw new IllegalStateException("Invalid cache annotation configuration on '" +
224                                        ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
225                                        "These attributes are mutually exclusive: the cache manager is used to configure a" +
226                                        "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
227                                        "won't be used.");
228                }
229        }
230
231        @Override
232        public boolean equals(Object other) {
233                return (this == other || other instanceof SpringCacheAnnotationParser);
234        }
235
236        @Override
237        public int hashCode() {
238                return SpringCacheAnnotationParser.class.hashCode();
239        }
240
241
242        /**
243         * Provides default settings for a given set of cache operations.
244         */
245        static class DefaultCacheConfig {
246
247                private final String[] cacheNames;
248
249                private final String keyGenerator;
250
251                private final String cacheManager;
252
253                private final String cacheResolver;
254
255                public DefaultCacheConfig() {
256                        this(null, null, null, null);
257                }
258
259                private DefaultCacheConfig(String[] cacheNames, String keyGenerator, String cacheManager, String cacheResolver) {
260                        this.cacheNames = cacheNames;
261                        this.keyGenerator = keyGenerator;
262                        this.cacheManager = cacheManager;
263                        this.cacheResolver = cacheResolver;
264                }
265
266                /**
267                 * Apply the defaults to the specified {@link CacheOperation.Builder}.
268                 * @param builder the operation builder to update
269                 */
270                public void applyDefault(CacheOperation.Builder builder) {
271                        if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
272                                builder.setCacheNames(this.cacheNames);
273                        }
274                        if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
275                                        StringUtils.hasText(this.keyGenerator)) {
276                                builder.setKeyGenerator(this.keyGenerator);
277                        }
278
279                        if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
280                                // One of these is set so we should not inherit anything
281                        }
282                        else if (StringUtils.hasText(this.cacheResolver)) {
283                                builder.setCacheResolver(this.cacheResolver);
284                        }
285                        else if (StringUtils.hasText(this.cacheManager)) {
286                                builder.setCacheManager(this.cacheManager);
287                        }
288                }
289        }
290
291}