001/*
002 * Copyright 2002-2015 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.jcache.interceptor;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.List;
023import javax.cache.annotation.CacheDefaults;
024import javax.cache.annotation.CacheKeyGenerator;
025import javax.cache.annotation.CacheMethodDetails;
026import javax.cache.annotation.CachePut;
027import javax.cache.annotation.CacheRemove;
028import javax.cache.annotation.CacheRemoveAll;
029import javax.cache.annotation.CacheResolverFactory;
030import javax.cache.annotation.CacheResult;
031
032import org.springframework.cache.interceptor.CacheResolver;
033import org.springframework.cache.interceptor.KeyGenerator;
034import org.springframework.util.StringUtils;
035
036/**
037 * Implementation of the {@link JCacheOperationSource} interface that reads
038 * the JSR-107 {@link CacheResult}, {@link CachePut}, {@link CacheRemove} and
039 * {@link CacheRemoveAll} annotations.
040 *
041 * @author Stephane Nicoll
042 * @since 4.1
043 */
044public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource {
045
046        @Override
047        protected JCacheOperation<?> findCacheOperation(Method method, Class<?> targetType) {
048                CacheResult cacheResult = method.getAnnotation(CacheResult.class);
049                CachePut cachePut = method.getAnnotation(CachePut.class);
050                CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);
051                CacheRemoveAll cacheRemoveAll = method.getAnnotation(CacheRemoveAll.class);
052
053                int found = countNonNull(cacheResult, cachePut, cacheRemove, cacheRemoveAll);
054                if (found == 0) {
055                        return null;
056                }
057                if (found > 1) {
058                        throw new IllegalStateException("More than one cache annotation found on '" + method + "'");
059                }
060
061                CacheDefaults defaults = getCacheDefaults(method, targetType);
062                if (cacheResult != null) {
063                        return createCacheResultOperation(method, defaults, cacheResult);
064                }
065                else if (cachePut != null) {
066                        return createCachePutOperation(method, defaults, cachePut);
067                }
068                else if (cacheRemove != null) {
069                        return createCacheRemoveOperation(method, defaults, cacheRemove);
070                }
071                else {
072                        return createCacheRemoveAllOperation(method, defaults, cacheRemoveAll);
073                }
074        }
075
076        protected CacheDefaults getCacheDefaults(Method method, Class<?> targetType) {
077                CacheDefaults annotation = method.getDeclaringClass().getAnnotation(CacheDefaults.class);
078                if (annotation != null) {
079                        return annotation;
080                }
081                return targetType.getAnnotation(CacheDefaults.class);
082        }
083
084        protected CacheResultOperation createCacheResultOperation(Method method, CacheDefaults defaults, CacheResult ann) {
085                String cacheName = determineCacheName(method, defaults, ann.cacheName());
086                CacheResolverFactory cacheResolverFactory =
087                                determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
088                KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator());
089
090                CacheMethodDetails<CacheResult> methodDetails = createMethodDetails(method, ann, cacheName);
091
092                CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
093                CacheResolver exceptionCacheResolver = null;
094                final String exceptionCacheName = ann.exceptionCacheName();
095                if (StringUtils.hasText(exceptionCacheName)) {
096                        exceptionCacheResolver = getExceptionCacheResolver(cacheResolverFactory, methodDetails);
097                }
098
099                return new CacheResultOperation(methodDetails, cacheResolver, keyGenerator, exceptionCacheResolver);
100        }
101
102        protected CachePutOperation createCachePutOperation(Method method, CacheDefaults defaults, CachePut ann) {
103                String cacheName = determineCacheName(method, defaults, ann.cacheName());
104                CacheResolverFactory cacheResolverFactory =
105                                determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
106                KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator());
107
108                CacheMethodDetails<CachePut> methodDetails = createMethodDetails(method, ann, cacheName);
109                CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
110                return new CachePutOperation(methodDetails, cacheResolver, keyGenerator);
111        }
112
113        protected CacheRemoveOperation createCacheRemoveOperation(Method method, CacheDefaults defaults, CacheRemove ann) {
114                String cacheName = determineCacheName(method, defaults, ann.cacheName());
115                CacheResolverFactory cacheResolverFactory =
116                                determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
117                KeyGenerator keyGenerator = determineKeyGenerator(defaults, ann.cacheKeyGenerator());
118
119                CacheMethodDetails<CacheRemove> methodDetails = createMethodDetails(method, ann, cacheName);
120                CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
121                return new CacheRemoveOperation(methodDetails, cacheResolver, keyGenerator);
122        }
123
124        protected CacheRemoveAllOperation createCacheRemoveAllOperation(Method method, CacheDefaults defaults, CacheRemoveAll ann) {
125                String cacheName = determineCacheName(method, defaults, ann.cacheName());
126                CacheResolverFactory cacheResolverFactory =
127                                determineCacheResolverFactory(defaults, ann.cacheResolverFactory());
128
129                CacheMethodDetails<CacheRemoveAll> methodDetails = createMethodDetails(method, ann, cacheName);
130                CacheResolver cacheResolver = getCacheResolver(cacheResolverFactory, methodDetails);
131                return new CacheRemoveAllOperation(methodDetails, cacheResolver);
132        }
133
134        private <A extends Annotation> CacheMethodDetails<A> createMethodDetails(Method method, A annotation, String cacheName) {
135                return new DefaultCacheMethodDetails<A>(method, annotation, cacheName);
136        }
137
138        protected CacheResolver getCacheResolver(CacheResolverFactory factory, CacheMethodDetails<?> details) {
139                if (factory != null) {
140                        javax.cache.annotation.CacheResolver cacheResolver = factory.getCacheResolver(details);
141                        return new CacheResolverAdapter(cacheResolver);
142                }
143                else {
144                        return getDefaultCacheResolver();
145                }
146        }
147
148        protected CacheResolver getExceptionCacheResolver(CacheResolverFactory factory,
149                        CacheMethodDetails<CacheResult> details) {
150
151                if (factory != null) {
152                        javax.cache.annotation.CacheResolver cacheResolver = factory.getExceptionCacheResolver(details);
153                        return new CacheResolverAdapter(cacheResolver);
154                }
155                else {
156                        return getDefaultExceptionCacheResolver();
157                }
158        }
159
160        protected CacheResolverFactory determineCacheResolverFactory(CacheDefaults defaults,
161                        Class<? extends CacheResolverFactory> candidate) {
162
163                if (CacheResolverFactory.class != candidate) {
164                        return getBean(candidate);
165                }
166                else if (defaults != null && CacheResolverFactory.class != defaults.cacheResolverFactory()) {
167                        return getBean(defaults.cacheResolverFactory());
168                }
169                else {
170                        return null;
171                }
172        }
173
174        protected KeyGenerator determineKeyGenerator(CacheDefaults defaults, Class<? extends CacheKeyGenerator> candidate) {
175                if (CacheKeyGenerator.class != candidate) {
176                        return new KeyGeneratorAdapter(this, getBean(candidate));
177                }
178                else if (defaults != null && CacheKeyGenerator.class != defaults.cacheKeyGenerator()) {
179                        return new KeyGeneratorAdapter(this, getBean(defaults.cacheKeyGenerator()));
180                }
181                else {
182                        return getDefaultKeyGenerator();
183                }
184        }
185
186        protected String determineCacheName(Method method, CacheDefaults defaults, String candidate) {
187                if (StringUtils.hasText(candidate)) {
188                        return candidate;
189                }
190                if (defaults != null && StringUtils.hasText(defaults.cacheName())) {
191                        return defaults.cacheName();
192                }
193                return generateDefaultCacheName(method);
194        }
195
196        /**
197         * Generate a default cache name for the specified {@link Method}.
198         * @param method the annotated method
199         * @return the default cache name, according to JSR-107
200         */
201        protected String generateDefaultCacheName(Method method) {
202                Class<?>[] parameterTypes = method.getParameterTypes();
203                List<String> parameters = new ArrayList<String>(parameterTypes.length);
204                for (Class<?> parameterType : parameterTypes) {
205                        parameters.add(parameterType.getName());
206                }
207
208                StringBuilder sb = new StringBuilder(method.getDeclaringClass().getName());
209                sb.append(".").append(method.getName());
210                sb.append("(").append(StringUtils.collectionToCommaDelimitedString(parameters)).append(")");
211                return sb.toString();
212        }
213
214        private int countNonNull(Object... instances) {
215                int result = 0;
216                for (Object instance : instances) {
217                        if (instance != null) {
218                                result += 1;
219                        }
220                }
221                return result;
222        }
223
224
225        /**
226         * Locate or create an instance of the specified cache strategy {@code type}.
227         * @param type the type of the bean to manage
228         * @return the required bean
229         */
230        protected abstract <T> T getBean(Class<T> type);
231
232        /**
233         * Return the default {@link CacheResolver} if none is set.
234         */
235        protected abstract CacheResolver getDefaultCacheResolver();
236
237        /**
238         * Return the default exception {@link CacheResolver} if none is set.
239         */
240        protected abstract CacheResolver getDefaultExceptionCacheResolver();
241
242        /**
243         * Return the default {@link KeyGenerator} if none is set.
244         */
245        protected abstract KeyGenerator getDefaultKeyGenerator();
246
247}