001/*
002 * Copyright 2002-2019 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;
025import java.util.LinkedHashSet;
026import java.util.Set;
027
028import org.springframework.cache.interceptor.CacheEvictOperation;
029import org.springframework.cache.interceptor.CacheOperation;
030import org.springframework.cache.interceptor.CachePutOperation;
031import org.springframework.cache.interceptor.CacheableOperation;
032import org.springframework.core.annotation.AnnotatedElementUtils;
033import org.springframework.core.annotation.AnnotationUtils;
034import org.springframework.lang.Nullable;
035import org.springframework.util.StringUtils;
036
037/**
038 * Strategy implementation for parsing Spring's {@link Caching}, {@link Cacheable},
039 * {@link CacheEvict}, and {@link CachePut} annotations.
040 *
041 * @author Costin Leau
042 * @author Juergen Hoeller
043 * @author Chris Beams
044 * @author Phillip Webb
045 * @author Stephane Nicoll
046 * @author Sam Brannen
047 * @since 3.1
048 */
049@SuppressWarnings("serial")
050public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
051
052        private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
053
054        static {
055                CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
056                CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
057                CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
058                CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
059        }
060
061
062        @Override
063        public boolean isCandidateClass(Class<?> targetClass) {
064                return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS);
065        }
066
067        @Override
068        @Nullable
069        public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
070                DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
071                return parseCacheAnnotations(defaultConfig, type);
072        }
073
074        @Override
075        @Nullable
076        public Collection<CacheOperation> parseCacheAnnotations(Method method) {
077                DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
078                return parseCacheAnnotations(defaultConfig, method);
079        }
080
081        @Nullable
082        private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
083                Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
084                if (ops != null && ops.size() > 1) {
085                        // More than one operation found -> local declarations override interface-declared ones...
086                        Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
087                        if (localOps != null) {
088                                return localOps;
089                        }
090                }
091                return ops;
092        }
093
094        @Nullable
095        private Collection<CacheOperation> parseCacheAnnotations(
096                        DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
097
098                Collection<? extends Annotation> anns = (localOnly ?
099                                AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
100                                AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
101                if (anns.isEmpty()) {
102                        return null;
103                }
104
105                final Collection<CacheOperation> ops = new ArrayList<>(1);
106                anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
107                                ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
108                anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
109                                ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
110                anns.stream().filter(ann -> ann instanceof CachePut).forEach(
111                                ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
112                anns.stream().filter(ann -> ann instanceof Caching).forEach(
113                                ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
114                return ops;
115        }
116
117        private CacheableOperation parseCacheableAnnotation(
118                        AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
119
120                CacheableOperation.Builder builder = new CacheableOperation.Builder();
121
122                builder.setName(ae.toString());
123                builder.setCacheNames(cacheable.cacheNames());
124                builder.setCondition(cacheable.condition());
125                builder.setUnless(cacheable.unless());
126                builder.setKey(cacheable.key());
127                builder.setKeyGenerator(cacheable.keyGenerator());
128                builder.setCacheManager(cacheable.cacheManager());
129                builder.setCacheResolver(cacheable.cacheResolver());
130                builder.setSync(cacheable.sync());
131
132                defaultConfig.applyDefault(builder);
133                CacheableOperation op = builder.build();
134                validateCacheOperation(ae, op);
135
136                return op;
137        }
138
139        private CacheEvictOperation parseEvictAnnotation(
140                        AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
141
142                CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
143
144                builder.setName(ae.toString());
145                builder.setCacheNames(cacheEvict.cacheNames());
146                builder.setCondition(cacheEvict.condition());
147                builder.setKey(cacheEvict.key());
148                builder.setKeyGenerator(cacheEvict.keyGenerator());
149                builder.setCacheManager(cacheEvict.cacheManager());
150                builder.setCacheResolver(cacheEvict.cacheResolver());
151                builder.setCacheWide(cacheEvict.allEntries());
152                builder.setBeforeInvocation(cacheEvict.beforeInvocation());
153
154                defaultConfig.applyDefault(builder);
155                CacheEvictOperation op = builder.build();
156                validateCacheOperation(ae, op);
157
158                return op;
159        }
160
161        private CacheOperation parsePutAnnotation(
162                        AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
163
164                CachePutOperation.Builder builder = new CachePutOperation.Builder();
165
166                builder.setName(ae.toString());
167                builder.setCacheNames(cachePut.cacheNames());
168                builder.setCondition(cachePut.condition());
169                builder.setUnless(cachePut.unless());
170                builder.setKey(cachePut.key());
171                builder.setKeyGenerator(cachePut.keyGenerator());
172                builder.setCacheManager(cachePut.cacheManager());
173                builder.setCacheResolver(cachePut.cacheResolver());
174
175                defaultConfig.applyDefault(builder);
176                CachePutOperation op = builder.build();
177                validateCacheOperation(ae, op);
178
179                return op;
180        }
181
182        private void parseCachingAnnotation(
183                        AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {
184
185                Cacheable[] cacheables = caching.cacheable();
186                for (Cacheable cacheable : cacheables) {
187                        ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
188                }
189                CacheEvict[] cacheEvicts = caching.evict();
190                for (CacheEvict cacheEvict : cacheEvicts) {
191                        ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
192                }
193                CachePut[] cachePuts = caching.put();
194                for (CachePut cachePut : cachePuts) {
195                        ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
196                }
197        }
198
199
200        /**
201         * Validates the specified {@link CacheOperation}.
202         * <p>Throws an {@link IllegalStateException} if the state of the operation is
203         * invalid. As there might be multiple sources for default values, this ensure
204         * that the operation is in a proper state before being returned.
205         * @param ae the annotated element of the cache operation
206         * @param operation the {@link CacheOperation} to validate
207         */
208        private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
209                if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
210                        throw new IllegalStateException("Invalid cache annotation configuration on '" +
211                                        ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
212                                        "These attributes are mutually exclusive: either set the SpEL expression used to" +
213                                        "compute the key at runtime or set the name of the KeyGenerator bean to use.");
214                }
215                if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
216                        throw new IllegalStateException("Invalid cache annotation configuration on '" +
217                                        ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
218                                        "These attributes are mutually exclusive: the cache manager is used to configure a" +
219                                        "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
220                                        "won't be used.");
221                }
222        }
223
224        @Override
225        public boolean equals(@Nullable Object other) {
226                return (this == other || other instanceof SpringCacheAnnotationParser);
227        }
228
229        @Override
230        public int hashCode() {
231                return SpringCacheAnnotationParser.class.hashCode();
232        }
233
234
235        /**
236         * Provides default settings for a given set of cache operations.
237         */
238        private static class DefaultCacheConfig {
239
240                private final Class<?> target;
241
242                @Nullable
243                private String[] cacheNames;
244
245                @Nullable
246                private String keyGenerator;
247
248                @Nullable
249                private String cacheManager;
250
251                @Nullable
252                private String cacheResolver;
253
254                private boolean initialized = false;
255
256                public DefaultCacheConfig(Class<?> target) {
257                        this.target = target;
258                }
259
260                /**
261                 * Apply the defaults to the specified {@link CacheOperation.Builder}.
262                 * @param builder the operation builder to update
263                 */
264                public void applyDefault(CacheOperation.Builder builder) {
265                        if (!this.initialized) {
266                                CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);
267                                if (annotation != null) {
268                                        this.cacheNames = annotation.cacheNames();
269                                        this.keyGenerator = annotation.keyGenerator();
270                                        this.cacheManager = annotation.cacheManager();
271                                        this.cacheResolver = annotation.cacheResolver();
272                                }
273                                this.initialized = true;
274                        }
275
276                        if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
277                                builder.setCacheNames(this.cacheNames);
278                        }
279                        if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
280                                        StringUtils.hasText(this.keyGenerator)) {
281                                builder.setKeyGenerator(this.keyGenerator);
282                        }
283
284                        if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
285                                // One of these is set so we should not inherit anything
286                        }
287                        else if (StringUtils.hasText(this.cacheResolver)) {
288                                builder.setCacheResolver(this.cacheResolver);
289                        }
290                        else if (StringUtils.hasText(this.cacheManager)) {
291                                builder.setCacheManager(this.cacheManager);
292                        }
293                }
294        }
295
296}