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

io.bitsensor.plugins.shaded.org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2014 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 io.bitsensor.plugins.shaded.org.springframework.retry.annotation;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import io.bitsensor.plugins.shaded.org.springframework.classify.SubclassClassifier;
import io.bitsensor.plugins.shaded.org.springframework.core.annotation.AnnotationUtils;
import io.bitsensor.plugins.shaded.org.springframework.retry.ExhaustedRetryException;
import io.bitsensor.plugins.shaded.org.springframework.retry.interceptor.MethodInvocationRecoverer;
import io.bitsensor.plugins.shaded.org.springframework.util.ReflectionUtils;
import io.bitsensor.plugins.shaded.org.springframework.util.ReflectionUtils.MethodCallback;

/**
 * A recoverer for method invocations based on the @Recover annotation. A
 * suitable recovery method is one with a Throwable type as the first parameter and the
 * same return type and arguments as the method that failed. The Throwable first argument
 * is optional and if omitted the method is treated as a default (called when there are no
 * other matches). Generally the best matching method is chosen based on the type of the
 * first parameter and the type of the exception being handled. The closest match in the
 * class hierarchy is chosen, so for instance if an IllegalArgumentException is being
 * handled and there is a method whose first argument is RuntimeException, then it will be
 * preferred over a method whose first argument is Throwable.
 * 
 * @author Dave Syer
 * @author Josh Long
 */
public class RecoverAnnotationRecoveryHandler implements MethodInvocationRecoverer {

	private SubclassClassifier classifier = new SubclassClassifier();
	private Map methods = new HashMap();
	private Object target;

	public RecoverAnnotationRecoveryHandler(Object target, Method method) {
		this.target = target;
		init(target, method);
	}

	@Override
	public T recover(Object[] args, Throwable cause) {
		Method method = findClosestMatch(cause.getClass());
		if (method == null) {
			throw new ExhaustedRetryException("Cannot locate recovery method", cause);
		}
		SimpleMetadata meta = methods.get(method);
		Object[] argsToUse = meta.getArgs(cause, args);
		boolean methodAccessible = method.isAccessible();
		try {
			ReflectionUtils.makeAccessible(method);
			@SuppressWarnings("unchecked")
			T result = (T) ReflectionUtils.invokeMethod(method, target, argsToUse);
			return result;
		}
		finally {
			if (methodAccessible != method.isAccessible()) {
				method.setAccessible(methodAccessible);
			}
		}
	}

	private Method findClosestMatch(Class cause) {
		int min = Integer.MAX_VALUE;
		Method result = null;
		for (Method method : methods.keySet()) {
			SimpleMetadata meta = methods.get(method);
			Class type = meta.getType();
			if (type == null) {
				type = Throwable.class;
			}
			if (type.isAssignableFrom(cause)) {
				int distance = calculateDistance(cause, type);
				if (distance < min) {
					min = distance;
					result = method;
				}
			}
		}
		return result;
	}

	private int calculateDistance(Class cause,
			Class type) {
		int result = 0;
		Class current = cause;
		while (current != type && current != Throwable.class) {
			result++;
			current = current.getSuperclass();
		}
		return result;
	}

	private void init(Object target, Method method) {
		final Map, Method> types = new HashMap, Method>();
		final Method failingMethod = method;
		ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(),
				new MethodCallback() {
					@Override
					public void doWith(Method method) throws IllegalArgumentException,
							IllegalAccessException {
						Recover recover = AnnotationUtils.findAnnotation(method,
								Recover.class);
						if (recover != null
								&& failingMethod.getReturnType().isAssignableFrom(
										method.getReturnType())) {
							Class[] parameterTypes = method.getParameterTypes();
							if (parameterTypes.length > 0
									&& Throwable.class
											.isAssignableFrom(parameterTypes[0])) {
								@SuppressWarnings("unchecked")
								Class type = (Class) parameterTypes[0];
								types.put(type, method);
								methods.put(method, new SimpleMetadata(
										parameterTypes.length, type));
							} else {
								classifier.setDefaultValue(method);
								methods.put(method, new SimpleMetadata(
										parameterTypes.length, null));
							}
						}
					}
				});
		classifier.setTypeMap(types);
	}

	private static class SimpleMetadata {
		private int argCount;
		private Class type;

		public SimpleMetadata(int argCount, Class type) {
			super();
			this.argCount = argCount;
			this.type = type;
		}

		public int getArgCount() {
			return argCount;
		}

		public Class getType() {
			return type;
		}

		public Object[] getArgs(Throwable t, Object[] args) {
			Object[] result = new Object[getArgCount()];
			int startArgs = 0;
			if (type != null) {
				result[0] = t;
				startArgs = 1;
			}
			System.arraycopy(args, 0, result, startArgs, result.length - startArgs);
			return result;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy