View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.cache.jcache.interceptor;
18  
19  import javax.cache.annotation.CacheResult;
20  
21  import org.springframework.cache.Cache;
22  import org.springframework.cache.interceptor.CacheErrorHandler;
23  import org.springframework.cache.interceptor.CacheOperationInvocationContext;
24  import org.springframework.cache.interceptor.CacheOperationInvoker;
25  import org.springframework.cache.interceptor.CacheResolver;
26  import org.springframework.util.ExceptionTypeFilter;
27  import org.springframework.util.SerializationUtils;
28  
29  /**
30   * Intercept methods annotated with {@link CacheResult}.
31   *
32   * @author Stephane Nicoll
33   * @since 4.1
34   */
35  @SuppressWarnings("serial")
36  class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOperation, CacheResult> {
37  
38  	public CacheResultInterceptor(CacheErrorHandler errorHandler) {
39  		super(errorHandler);
40  	}
41  
42  	@Override
43  	protected Object invoke(CacheOperationInvocationContext<CacheResultOperation> context,
44  			CacheOperationInvoker invoker) {
45  
46  		CacheResultOperation operation = context.getOperation();
47  		Object cacheKey = generateKey(context);
48  
49  		Cache cache = resolveCache(context);
50  		Cache exceptionCache = resolveExceptionCache(context);
51  
52  		if (!operation.isAlwaysInvoked()) {
53  			Cache.ValueWrapper cachedValue = doGet(cache, cacheKey);
54  			if (cachedValue != null) {
55  				return cachedValue.get();
56  			}
57  			checkForCachedException(exceptionCache, cacheKey);
58  		}
59  
60  		try {
61  			Object invocationResult = invoker.invoke();
62  			if (invocationResult != null) {
63  				cache.put(cacheKey, invocationResult);
64  			}
65  			return invocationResult;
66  		}
67  		catch (CacheOperationInvoker.ThrowableWrapper ex) {
68  			Throwable original = ex.getOriginal();
69  			cacheException(exceptionCache, operation.getExceptionTypeFilter(), cacheKey, original);
70  			throw ex;
71  		}
72  	}
73  
74  	/**
75  	 * Check for a cached exception. If the exception is found, throw it directly.
76  	 */
77  	protected void checkForCachedException(Cache exceptionCache, Object cacheKey) {
78  		if (exceptionCache == null) {
79  			return;
80  		}
81  		Cache.ValueWrapper result = doGet(exceptionCache, cacheKey);
82  		if (result != null) {
83  			throw rewriteCallStack((Throwable) result.get(), getClass().getName(), "invoke");
84  		}
85  	}
86  
87  
88  	protected void cacheException(Cache exceptionCache, ExceptionTypeFilter filter, Object cacheKey, Throwable ex) {
89  		if (exceptionCache == null) {
90  			return;
91  		}
92  		if (filter.match(ex.getClass())) {
93  			exceptionCache.put(cacheKey, ex);
94  		}
95  	}
96  
97  
98  	private Cache resolveExceptionCache(CacheOperationInvocationContext<CacheResultOperation> context) {
99  		CacheResolver exceptionCacheResolver = context.getOperation().getExceptionCacheResolver();
100 		if (exceptionCacheResolver != null) {
101 			return extractFrom(context.getOperation().getExceptionCacheResolver().resolveCaches(context));
102 		}
103 		return null;
104 	}
105 
106 	/**
107 	 * Rewrite the call stack of the specified {@code exception} so that it matches
108 	 * the current call stack up-to (included) the specified method invocation.
109 	 * <p>Clone the specified exception. If the exception is not {@code serializable},
110 	 * the original exception is returned. If no common ancestor can be found, returns
111 	 * the original exception.
112 	 * <p>Used to make sure that a cached exception has a valid invocation context.
113 	 * @param exception the exception to merge with the current call stack
114 	 * @param className the class name of the common ancestor
115 	 * @param methodName the method name of the common ancestor
116 	 * @return a clone exception with a rewritten call stack composed of the current
117 	 * call stack up to (included) the common ancestor specified by the {@code className} and
118 	 * {@code methodName} arguments, followed by stack trace elements of the specified
119 	 * {@code exception} after the common ancestor.
120 	 */
121 	private static CacheOperationInvoker.ThrowableWrapper rewriteCallStack(
122 			Throwable exception, String className, String methodName) {
123 
124 		Throwable clone = cloneException(exception);
125 		if (clone == null) {
126 			return new CacheOperationInvoker.ThrowableWrapper(exception);
127 		}
128 
129 		StackTraceElement[] callStack = new Exception().getStackTrace();
130 		StackTraceElement[] cachedCallStack = exception.getStackTrace();
131 
132 		int index = findCommonAncestorIndex(callStack, className, methodName);
133 		int cachedIndex = findCommonAncestorIndex(cachedCallStack, className, methodName);
134 		if (index == -1 || cachedIndex == -1) {
135 			return new CacheOperationInvoker.ThrowableWrapper(exception); // Cannot find common ancestor
136 		}
137 		StackTraceElement[] result = new StackTraceElement[cachedIndex + callStack.length - index];
138 		System.arraycopy(cachedCallStack, 0, result, 0, cachedIndex);
139 		System.arraycopy(callStack, index, result, cachedIndex, callStack.length - index);
140 
141 		clone.setStackTrace(result);
142 		return new CacheOperationInvoker.ThrowableWrapper(clone);
143 	}
144 
145 	@SuppressWarnings("unchecked")
146 	private static <T extends Throwable> T cloneException(T exception) {
147 		try {
148 			return (T) SerializationUtils.deserialize(SerializationUtils.serialize(exception));
149 		}
150 		catch (Exception ex) {
151 			return null;  // exception parameter cannot be cloned
152 		}
153 	}
154 
155 	private static int findCommonAncestorIndex(StackTraceElement[] callStack, String className, String methodName) {
156 		for (int i = 0; i < callStack.length; i++) {
157 			StackTraceElement element = callStack[i];
158 			if (className.equals(element.getClassName()) && methodName.equals(element.getMethodName())) {
159 				return i;
160 			}
161 		}
162 		return -1;
163 	}
164 
165 }