All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.cache.jcache.interceptor.CacheResultInterceptor Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cache.jcache.interceptor;

import javax.cache.annotation.CacheResult;

import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ExceptionTypeFilter;
import org.springframework.util.SerializationUtils;

/**
 * Intercept methods annotated with {@link CacheResult}.
 *
 * @author Stephane Nicoll
 * @since 4.1
 */
@SuppressWarnings("serial")
class CacheResultInterceptor extends AbstractKeyCacheInterceptor {

	public CacheResultInterceptor(CacheErrorHandler errorHandler) {
		super(errorHandler);
	}


	@Override
	@Nullable
	protected Object invoke(
			CacheOperationInvocationContext context, CacheOperationInvoker invoker) {

		CacheResultOperation operation = context.getOperation();
		Object cacheKey = generateKey(context);

		Cache cache = resolveCache(context);
		Cache exceptionCache = resolveExceptionCache(context);

		if (!operation.isAlwaysInvoked()) {
			Cache.ValueWrapper cachedValue = doGet(cache, cacheKey);
			if (cachedValue != null) {
				return cachedValue.get();
			}
			checkForCachedException(exceptionCache, cacheKey);
		}

		try {
			Object invocationResult = invoker.invoke();
			doPut(cache, cacheKey, invocationResult);
			return invocationResult;
		}
		catch (CacheOperationInvoker.ThrowableWrapper ex) {
			Throwable original = ex.getOriginal();
			cacheException(exceptionCache, operation.getExceptionTypeFilter(), cacheKey, original);
			throw ex;
		}
	}

	/**
	 * Check for a cached exception. If the exception is found, throw it directly.
	 */
	protected void checkForCachedException(@Nullable Cache exceptionCache, Object cacheKey) {
		if (exceptionCache == null) {
			return;
		}
		Cache.ValueWrapper result = doGet(exceptionCache, cacheKey);
		if (result != null) {
			Throwable ex = (Throwable) result.get();
			Assert.state(ex != null, "No exception in cache");
			throw rewriteCallStack(ex, getClass().getName(), "invoke");
		}
	}

	protected void cacheException(@Nullable Cache exceptionCache, ExceptionTypeFilter filter, Object cacheKey, Throwable ex) {
		if (exceptionCache == null) {
			return;
		}
		if (filter.match(ex.getClass())) {
			doPut(exceptionCache, cacheKey, ex);
		}
	}

	@Nullable
	private Cache resolveExceptionCache(CacheOperationInvocationContext context) {
		CacheResolver exceptionCacheResolver = context.getOperation().getExceptionCacheResolver();
		if (exceptionCacheResolver != null) {
			return extractFrom(context.getOperation().getExceptionCacheResolver().resolveCaches(context));
		}
		return null;
	}


	/**
	 * Rewrite the call stack of the specified {@code exception} so that it matches
	 * the current call stack up to (included) the specified method invocation.
	 * 

Clone the specified exception. If the exception is not {@code serializable}, * the original exception is returned. If no common ancestor can be found, returns * the original exception. *

Used to make sure that a cached exception has a valid invocation context. * @param exception the exception to merge with the current call stack * @param className the class name of the common ancestor * @param methodName the method name of the common ancestor * @return a clone exception with a rewritten call stack composed of the current call * stack up to (included) the common ancestor specified by the {@code className} and * {@code methodName} arguments, followed by stack trace elements of the specified * {@code exception} after the common ancestor. */ private static CacheOperationInvoker.ThrowableWrapper rewriteCallStack( Throwable exception, String className, String methodName) { Throwable clone = cloneException(exception); if (clone == null) { return new CacheOperationInvoker.ThrowableWrapper(exception); } StackTraceElement[] callStack = new Exception().getStackTrace(); StackTraceElement[] cachedCallStack = exception.getStackTrace(); int index = findCommonAncestorIndex(callStack, className, methodName); int cachedIndex = findCommonAncestorIndex(cachedCallStack, className, methodName); if (index == -1 || cachedIndex == -1) { return new CacheOperationInvoker.ThrowableWrapper(exception); // Cannot find common ancestor } StackTraceElement[] result = new StackTraceElement[cachedIndex + callStack.length - index]; System.arraycopy(cachedCallStack, 0, result, 0, cachedIndex); System.arraycopy(callStack, index, result, cachedIndex, callStack.length - index); clone.setStackTrace(result); return new CacheOperationInvoker.ThrowableWrapper(clone); } @SuppressWarnings("unchecked") @Nullable private static T cloneException(T exception) { try { return (T) SerializationUtils.deserialize(SerializationUtils.serialize(exception)); } catch (Exception ex) { return null; // exception parameter cannot be cloned } } private static int findCommonAncestorIndex(StackTraceElement[] callStack, String className, String methodName) { for (int i = 0; i < callStack.length; i++) { StackTraceElement element = callStack[i]; if (className.equals(element.getClassName()) && methodName.equals(element.getMethodName())) { return i; } } return -1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy