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

io.github.resilience4j.fallback.FallbackMethod Maven / Gradle / Ivy

Go to download

Resilience4j is a lightweight, easy-to-use fault tolerance library designed for Java8 and functional programming

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright 2019 Kyuhyen Hwang, Mahmoud Romeh
 *
 * 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.github.resilience4j.fallback;

import io.github.resilience4j.core.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

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

/**
 * Reflection utility for invoking a fallback method. Fallback method should have same return type
 * and parameter types of original method but the last additional parameter. The last additional
 * parameter should be a subclass of {@link Throwable}. When {@link FallbackMethod#fallback(Throwable)}
 * is invoked, {@link Throwable} will be passed to that last parameter. If there are multiple
 * fallback method, one of the methods that has most closest superclass parameter of thrown object
 * will be invoked.
 * 
 * For example, there are two fallback methods
 * {@code
 * String fallbackMethod(String parameter, RuntimeException exception)
 * String fallbackMethod(String parameter, IllegalArgumentException exception)
 * }
 * and if try to fallback from {@link NumberFormatException}, {@code String fallbackMethod(String parameter, IllegalArgumentException exception)} will be invoked.
 * 
*/ public class FallbackMethod { private static final Map, Method>> FALLBACK_METHODS_CACHE = new ConcurrentReferenceHashMap<>(); private final Map, Method> fallbackMethods; private final Object[] args; private final Object target; private final Class returnType; /** * create a fallbackMethod method. * * @param fallbackMethods configured and found fallback methods for this invocation * @param originalMethodReturnType the return type of the original source method * @param args arguments those were passed to the original method. They will * be passed to the fallbackMethod method. * @param target target object the fallbackMethod method will be invoked */ private FallbackMethod(Map, Method> fallbackMethods, Class originalMethodReturnType, Object[] args, Object target) { this.fallbackMethods = fallbackMethods; this.args = args; this.target = target; this.returnType = originalMethodReturnType; } /** * @param fallbackMethodName the configured fallback method name * @param originalMethod the original method which has fallback method configured * @param args the original method arguments * @param target the target class that own the original method and fallback method * @return FallbackMethod instance */ public static FallbackMethod create(String fallbackMethodName, Method originalMethod, Object[] args, Object target) throws NoSuchMethodException { MethodMeta methodMeta = new MethodMeta( fallbackMethodName, originalMethod.getParameterTypes(), originalMethod.getReturnType(), target.getClass()); Map, Method> methods = FALLBACK_METHODS_CACHE .computeIfAbsent(methodMeta, FallbackMethod::extractMethods); if (!methods.isEmpty()) { return new FallbackMethod(methods, originalMethod.getReturnType(), args, target); } else { throw new NoSuchMethodException(String.format("%s %s.%s(%s,%s)", methodMeta.returnType, methodMeta.targetClass, methodMeta.fallbackMethodName, StringUtils.arrayToDelimitedString(methodMeta.params, ","), Throwable.class)); } } /** * @param methodMeta the method meta data * @return Map, Method> map of all configure fallback methods for the original * method that match the fallback method name */ private static Map, Method> extractMethods(MethodMeta methodMeta) { Map, Method> methods = new HashMap<>(); ReflectionUtils.doWithMethods(methodMeta.targetClass, method -> merge(method, methods), method -> filter(method, methodMeta) ); return methods; } private static void merge(Method method, Map, Method> methods) { Class[] fallbackParams = method.getParameterTypes(); Class exception = fallbackParams[fallbackParams.length - 1]; Method similar = methods.get(exception); if (similar == null || Arrays .equals(similar.getParameterTypes(), method.getParameterTypes())) { methods.put(exception, method); } else { throw new IllegalStateException( "You have more that one fallback method that cover the same exception type " + exception.getName()); } } private static boolean filter(Method method, MethodMeta methodMeta) { if (!method.getName().equals(methodMeta.fallbackMethodName)) { return false; } if (!methodMeta.returnType.isAssignableFrom(method.getReturnType())) { return false; } if (method.getParameterCount() == 1) { return Throwable.class.isAssignableFrom(method.getParameterTypes()[0]); } if (method.getParameterCount() != methodMeta.params.length + 1) { return false; } Class[] targetParams = method.getParameterTypes(); for (int i = 0; i < methodMeta.params.length; i++) { if (methodMeta.params[i] != targetParams[i]) { return false; } } return Throwable.class.isAssignableFrom(targetParams[methodMeta.params.length]); } /** * try to fallback from {@link Throwable} * * @param thrown {@link Throwable} that should be fallback * @return fallback value * @throws Throwable if throwable is unrecoverable, throwable will be thrown */ @Nullable public Object fallback(Throwable thrown) throws Throwable { if (fallbackMethods.size() == 1) { Map.Entry, Method> entry = fallbackMethods.entrySet().iterator().next(); if (entry.getKey().isAssignableFrom(thrown.getClass())) { return invoke(entry.getValue(), thrown); } else { throw thrown; } } Method fallback = null; Class thrownClass = thrown.getClass(); while (fallback == null && thrownClass != Object.class) { fallback = fallbackMethods.get(thrownClass); thrownClass = thrownClass.getSuperclass(); } if (fallback != null) { return invoke(fallback, thrown); } else { throw thrown; } } /** * get return type of fallbackMethod method * * @return return type of fallbackMethod method */ public Class getReturnType() { return returnType; } /** * invoke the fallback method logic * * @param fallback fallback method * @param throwable the thrown exception * @return the result object if any * @throws IllegalAccessException exception * @throws InvocationTargetException exception */ private Object invoke(Method fallback, Throwable throwable) throws Throwable { boolean accessible = fallback.isAccessible(); try { if (!accessible) { ReflectionUtils.makeAccessible(fallback); } if (args.length != 0) { if (fallback.getParameterTypes().length == 1 && Throwable.class .isAssignableFrom(fallback.getParameterTypes()[0])) { return fallback.invoke(target, throwable); } Object[] newArgs = Arrays.copyOf(args, args.length + 1); newArgs[args.length] = throwable; return fallback.invoke(target, newArgs); } else { return fallback.invoke(target, throwable); } } catch (InvocationTargetException e) { // We want the original fallback-method exception to propagate instead: throw e.getCause(); } finally { if (!accessible) { fallback.setAccessible(false); } } } private static class MethodMeta { final String fallbackMethodName; final Class[] params; final Class returnType; final Class targetClass; /** * @param fallbackMethodName the configured fallback method name * @param returnType the original method return type * @param params the original method arguments * @param targetClass the target class that own the original method and fallback * method */ MethodMeta(String fallbackMethodName, Class[] params, Class returnType, Class targetClass) { this.fallbackMethodName = fallbackMethodName; this.params = params; this.returnType = returnType; this.targetClass = targetClass; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MethodMeta that = (MethodMeta) o; return targetClass.equals(that.targetClass) && fallbackMethodName.equals(that.fallbackMethodName) && returnType.equals(that.returnType) && Arrays.equals(params, that.params); } @Override public int hashCode() { return targetClass.getName().hashCode() ^ fallbackMethodName.hashCode(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy