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