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;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.aop.framework.AopProxyUtils;
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.cache.interceptor.AbstractCacheInvoker;
028import org.springframework.cache.interceptor.BasicOperation;
029import org.springframework.cache.interceptor.CacheOperationInvocationContext;
030import org.springframework.cache.interceptor.CacheOperationInvoker;
031import org.springframework.lang.Nullable;
032import org.springframework.util.Assert;
033
034/**
035 * Base class for JSR-107 caching aspects, such as the {@link JCacheInterceptor}
036 * or an AspectJ aspect.
037 *
038 * <p>Use the Spring caching abstraction for cache-related operations. No JSR-107
039 * {@link javax.cache.Cache} or {@link javax.cache.CacheManager} are required to
040 * process standard JSR-107 cache annotations.
041 *
042 * <p>The {@link JCacheOperationSource} is used for determining caching operations
043 *
044 * <p>A cache aspect is serializable if its {@code JCacheOperationSource} is serializable.
045 *
046 * @author Stephane Nicoll
047 * @since 4.1
048 * @see org.springframework.cache.interceptor.CacheAspectSupport
049 * @see KeyGeneratorAdapter
050 * @see CacheResolverAdapter
051 */
052public class JCacheAspectSupport extends AbstractCacheInvoker implements InitializingBean {
053
054        protected final Log logger = LogFactory.getLog(getClass());
055
056        @Nullable
057        private JCacheOperationSource cacheOperationSource;
058
059        @Nullable
060        private CacheResultInterceptor cacheResultInterceptor;
061
062        @Nullable
063        private CachePutInterceptor cachePutInterceptor;
064
065        @Nullable
066        private CacheRemoveEntryInterceptor cacheRemoveEntryInterceptor;
067
068        @Nullable
069        private CacheRemoveAllInterceptor cacheRemoveAllInterceptor;
070
071        private boolean initialized = false;
072
073
074        /**
075         * Set the CacheOperationSource for this cache aspect.
076         */
077        public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) {
078                Assert.notNull(cacheOperationSource, "JCacheOperationSource must not be null");
079                this.cacheOperationSource = cacheOperationSource;
080        }
081
082        /**
083         * Return the CacheOperationSource for this cache aspect.
084         */
085        public JCacheOperationSource getCacheOperationSource() {
086                Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSource' property is required: " +
087                                "If there are no cacheable methods, then don't use a cache aspect.");
088                return this.cacheOperationSource;
089        }
090
091        @Override
092        public void afterPropertiesSet() {
093                getCacheOperationSource();
094
095                this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler());
096                this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler());
097                this.cacheRemoveEntryInterceptor = new CacheRemoveEntryInterceptor(getErrorHandler());
098                this.cacheRemoveAllInterceptor = new CacheRemoveAllInterceptor(getErrorHandler());
099
100                this.initialized = true;
101        }
102
103
104        @Nullable
105        protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
106                // Check whether aspect is enabled to cope with cases where the AJ is pulled in automatically
107                if (this.initialized) {
108                        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
109                        JCacheOperation<?> operation = getCacheOperationSource().getCacheOperation(method, targetClass);
110                        if (operation != null) {
111                                CacheOperationInvocationContext<?> context =
112                                                createCacheOperationInvocationContext(target, args, operation);
113                                return execute(context, invoker);
114                        }
115                }
116
117                return invoker.invoke();
118        }
119
120        @SuppressWarnings("unchecked")
121        private CacheOperationInvocationContext<?> createCacheOperationInvocationContext(
122                        Object target, Object[] args, JCacheOperation<?> operation) {
123
124                return new DefaultCacheInvocationContext<>(
125                                (JCacheOperation<Annotation>) operation, target, args);
126        }
127
128        @SuppressWarnings("unchecked")
129        @Nullable
130        private Object execute(CacheOperationInvocationContext<?> context, CacheOperationInvoker invoker) {
131                CacheOperationInvoker adapter = new CacheOperationInvokerAdapter(invoker);
132                BasicOperation operation = context.getOperation();
133
134                if (operation instanceof CacheResultOperation) {
135                        Assert.state(this.cacheResultInterceptor != null, "No CacheResultInterceptor");
136                        return this.cacheResultInterceptor.invoke(
137                                        (CacheOperationInvocationContext<CacheResultOperation>) context, adapter);
138                }
139                else if (operation instanceof CachePutOperation) {
140                        Assert.state(this.cachePutInterceptor != null, "No CachePutInterceptor");
141                        return this.cachePutInterceptor.invoke(
142                                        (CacheOperationInvocationContext<CachePutOperation>) context, adapter);
143                }
144                else if (operation instanceof CacheRemoveOperation) {
145                        Assert.state(this.cacheRemoveEntryInterceptor != null, "No CacheRemoveEntryInterceptor");
146                        return this.cacheRemoveEntryInterceptor.invoke(
147                                        (CacheOperationInvocationContext<CacheRemoveOperation>) context, adapter);
148                }
149                else if (operation instanceof CacheRemoveAllOperation) {
150                        Assert.state(this.cacheRemoveAllInterceptor != null, "No CacheRemoveAllInterceptor");
151                        return this.cacheRemoveAllInterceptor.invoke(
152                                        (CacheOperationInvocationContext<CacheRemoveAllOperation>) context, adapter);
153                }
154                else {
155                        throw new IllegalArgumentException("Cannot handle " + operation);
156                }
157        }
158
159        /**
160         * Execute the underlying operation (typically in case of cache miss) and return
161         * the result of the invocation. If an exception occurs it will be wrapped in
162         * a {@code ThrowableWrapper}: the exception can be handled or modified but it
163         * <em>must</em> be wrapped in a {@code ThrowableWrapper} as well.
164         * @param invoker the invoker handling the operation being cached
165         * @return the result of the invocation
166         * @see CacheOperationInvoker#invoke()
167         */
168        protected Object invokeOperation(CacheOperationInvoker invoker) {
169                return invoker.invoke();
170        }
171
172
173        private class CacheOperationInvokerAdapter implements CacheOperationInvoker {
174
175                private final CacheOperationInvoker delegate;
176
177                public CacheOperationInvokerAdapter(CacheOperationInvoker delegate) {
178                        this.delegate = delegate;
179                }
180
181                @Override
182                public Object invoke() throws ThrowableWrapper {
183                        return invokeOperation(this.delegate);
184                }
185        }
186
187}