Skip to content

Commit

Permalink
iss1446: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
dmgcodevil committed Feb 21, 2017
1 parent c674545 commit dd91778
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,13 @@
* @return exceptions to wrap
*/
HystrixException[] raiseHystrixExceptions() default {};

/**
* Specifies default fallback method for each command in the given class. Every command within the class should
* have a return type which is compatible with default fallback method return type.
* note: default fallback method cannot have parameters.
*
* @return the name of default fallback method
*/
String defaultFallback() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,14 @@
* @return exceptions to wrap
*/
HystrixException[] raiseHystrixExceptions() default {};

/**
* Specifies default fallback method for the command. If both {@link #fallbackMethod} and {@link #defaultFallback}
* methods are specified then specific one is used.
* note: default fallback method cannot have parameters, return type should be compatible with command return type.
*
* @return the name of default fallback method
*/
String defaultFallback() default "";
}

Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,17 @@ private CommandAction createFallbackAction(MetaHolder metaHolder) {
if (fallbackMethod.isPresent()) {

Method fMethod = fallbackMethod.getMethod();
Object[] args = fallbackMethod.isDefault() ? new Object[0] : metaHolder.getArgs();
if (fallbackMethod.isCommand()) {
fMethod.setAccessible(true);
HystrixCommand hystrixCommand = fMethod.getAnnotation(HystrixCommand.class);
MetaHolder fmMetaHolder = MetaHolder.builder()
.obj(metaHolder.getObj())
.method(fMethod)
.ajcMethod(getAjcMethod(metaHolder.getObj(), fMethod))
.args(metaHolder.getArgs())
.args(args)
.fallback(true)
.defaultFallback(fallbackMethod.isDefault())
.defaultCollapserKey(metaHolder.getDefaultCollapserKey())
.fallbackMethod(fMethod)
.extendedFallback(fallbackMethod.isExtended())
Expand All @@ -131,12 +133,13 @@ private CommandAction createFallbackAction(MetaHolder metaHolder) {
} else {
MetaHolder fmMetaHolder = MetaHolder.builder()
.obj(metaHolder.getObj())
.defaultFallback(fallbackMethod.isDefault())
.method(fMethod)
.fallbackExecutionType(ExecutionType.SYNCHRONOUS)
.extendedFallback(fallbackMethod.isExtended())
.extendedParentFallback(metaHolder.isExtendedFallback())
.ajcMethod(null) // if fallback method isn't annotated with command annotation then we don't need to get ajc method for this
.args(metaHolder.getArgs()).build();
.args(args).build();

fallbackAction = new MethodExecutionAction(fmMetaHolder.getObj(), fMethod, fmMetaHolder.getArgs(), fmMetaHolder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
/**
* Simple immutable holder to keep all necessary information about current method to build Hystrix command.
*/
// todo: replace fallback related flags with FallbackMethod class
@Immutable
public final class MetaHolder {

Expand All @@ -60,6 +61,7 @@ public final class MetaHolder {
private final ExecutionType fallbackExecutionType;
private final boolean fallback;
private boolean extendedParentFallback;
private final boolean defaultFallback;
private final JoinPoint joinPoint;
private final boolean observable;
private final ObservableExecutionMode observableExecutionMode;
Expand Down Expand Up @@ -93,6 +95,7 @@ private MetaHolder(Builder builder) {
this.fallbackExecutionType = builder.fallbackExecutionType;
this.joinPoint = builder.joinPoint;
this.extendedFallback = builder.extendedFallback;
this.defaultFallback = builder.defaultFallback;
this.fallback = builder.fallback;
this.extendedParentFallback = builder.extendedParentFallback;
this.observable = builder.observable;
Expand Down Expand Up @@ -227,6 +230,10 @@ public boolean isExtendedFallback() {
return extendedFallback;
}

public boolean isDefaultFallback() {
return defaultFallback;
}

@SuppressWarnings("unchecked")
public List<Class<? extends Throwable>> getCommandIgnoreExceptions() {
if (!isCommandAnnotationPresent()) return Collections.emptyList();
Expand Down Expand Up @@ -367,6 +374,7 @@ public static final class Builder {
private boolean extendedFallback;
private boolean fallback;
private boolean extendedParentFallback;
private boolean defaultFallback;
private boolean observable;
private JoinPoint joinPoint;
private ObservableExecutionMode observableExecutionMode;
Expand Down Expand Up @@ -411,6 +419,11 @@ public Builder extendedParentFallback(boolean extendedParentFallback) {
return this;
}

public Builder defaultFallback(boolean defaultFallback) {
this.defaultFallback = defaultFallback;
return this;
}

public Builder ajcMethod(Method ajcMethod) {
this.ajcMethod = ajcMethod;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ public class FallbackMethod {

private final Method method;
private final boolean extended;
private final boolean defaultFallback;
private ExecutionType executionType;

public static final FallbackMethod ABSENT = new FallbackMethod(null, false);
public static final FallbackMethod ABSENT = new FallbackMethod(null, false, false);

public FallbackMethod(Method method) {
this(method, false);
this(method, false, false);
}

public FallbackMethod(Method method, boolean extended) {
public FallbackMethod(Method method, boolean extended, boolean defaultFallback) {
this.method = method;
this.extended = extended;
this.defaultFallback = defaultFallback;
if (method != null) {
this.executionType = ExecutionType.getExecutionType(method.getReturnType());
}
Expand All @@ -86,7 +88,11 @@ public boolean isExtended() {
return extended;
}

public void validateReturnType(Method commandMethod) {
public boolean isDefault() {
return defaultFallback;
}

public void validateReturnType(Method commandMethod) throws FallbackDefinitionException {
if (isPresent()) {
Class<?> commandReturnType = commandMethod.getReturnType();
if (ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(commandReturnType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package com.netflix.hystrix.contrib.javanica.utils;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException;
import org.apache.commons.lang3.ArrayUtils;
Expand Down Expand Up @@ -49,6 +51,8 @@ public static MethodProvider getInstance() {
return INSTANCE;
}

private static final FallbackMethodFinder FALLBACK_METHOD_FINDER = new SpecificFallback(new DefaultCallback());

private Map<Method, Method> cache = new ConcurrentHashMap<Method, Method>();

public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod) {
Expand All @@ -58,35 +62,139 @@ public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod) {
/**
* Gets fallback method for command method.
*
* @param type type
* @param enclosingType the enclosing class
* @param commandMethod the command method. in the essence it can be a fallback
* method annotated with HystrixCommand annotation that has a fallback as well.
* @param extended true if the given commandMethod was derived using additional parameter, otherwise - false
* @return new instance of {@link FallbackMethod} or {@link FallbackMethod#ABSENT} if there is no suitable fallback method for the given command
*/
public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod, boolean extended) {
public FallbackMethod getFallbackMethod(Class<?> enclosingType, Method commandMethod, boolean extended) {
if (commandMethod.isAnnotationPresent(HystrixCommand.class)) {
HystrixCommand hystrixCommand = commandMethod.getAnnotation(HystrixCommand.class);
if (StringUtils.isNotBlank(hystrixCommand.fallbackMethod())) {
Class<?>[] parameterTypes = commandMethod.getParameterTypes();
if (extended && parameterTypes[parameterTypes.length - 1] == Throwable.class) {
parameterTypes = ArrayUtils.remove(parameterTypes, parameterTypes.length - 1);
}
Class<?>[] exParameterTypes = Arrays.copyOf(parameterTypes, parameterTypes.length + 1);
exParameterTypes[parameterTypes.length] = Throwable.class;
Optional<Method> exFallbackMethod = getMethod(type, hystrixCommand.fallbackMethod(), exParameterTypes);
Optional<Method> fMethod = getMethod(type, hystrixCommand.fallbackMethod(),
parameterTypes);
Method method = exFallbackMethod.or(fMethod).orNull();
if (method == null) {
throw new FallbackDefinitionException("fallback method wasn't found: " + hystrixCommand.fallbackMethod() + "(" + Arrays.toString(parameterTypes) + ")");
}
return new FallbackMethod(method, exFallbackMethod.isPresent());
}
return FALLBACK_METHOD_FINDER.find(enclosingType, commandMethod, extended);
}
return FallbackMethod.ABSENT;
}

private void getDefaultFallback(){

}

private String getClassLevelFallback(Class<?> enclosingClass) {
if (enclosingClass.isAnnotationPresent(DefaultProperties.class)) {
return enclosingClass.getAnnotation(DefaultProperties.class).defaultFallback();
}
return StringUtils.EMPTY;
}

private static class SpecificFallback extends FallbackMethodFinder {

public SpecificFallback(FallbackMethodFinder next) {
super(next);
}

@Override
boolean isSpecific() {
return true;
}

@Override
public String getFallbackName(Class<?> enclosingType, Method commandMethod) {
return commandMethod.getAnnotation(HystrixCommand.class).fallbackMethod();
}

@Override
boolean canHandle(Class<?> enclosingType, Method commandMethod) {
return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod));
}
}

private static class DefaultCallback extends FallbackMethodFinder {
@Override
boolean isDefault() {
return true;
}

@Override
public String getFallbackName(Class<?> enclosingType, Method commandMethod) {
String commandDefaultFallback = commandMethod.getAnnotation(HystrixCommand.class).defaultFallback();
String classDefaultFallback = Optional.fromNullable(enclosingType.getAnnotation(DefaultProperties.class))
.transform(new Function<DefaultProperties, String>() {
@Override
public String apply(DefaultProperties input) {
return input.defaultFallback();
}
}).or(StringUtils.EMPTY);

return StringUtils.defaultIfEmpty(commandDefaultFallback, classDefaultFallback);
}

@Override
boolean canHandle(Class<?> enclosingType, Method commandMethod) {
return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod));
}
}

private static abstract class FallbackMethodFinder {
FallbackMethodFinder next;

public FallbackMethodFinder() {
}

public FallbackMethodFinder(FallbackMethodFinder next) {
this.next = next;
}

boolean isDefault() {
return false;
}

boolean isSpecific(){
return false;
}

public abstract String getFallbackName(Class<?> enclosingType, Method commandMethod);

public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
if (canHandle(enclosingType, commandMethod)) {
return doFind(enclosingType, commandMethod, extended);
} else if (next != null) {
return next.find(enclosingType, commandMethod, extended);
} else {
return FallbackMethod.ABSENT;
}
}

abstract boolean canHandle(Class<?> enclosingType, Method commandMethod);

private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
String name = getFallbackName(enclosingType, commandMethod);
Class<?>[] fallbackParameterTypes = null;
if (isDefault()) {
fallbackParameterTypes = new Class[0];
} else {
fallbackParameterTypes = commandMethod.getParameterTypes();
}

if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
fallbackParameterTypes = ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
}

Class<?>[] extendedFallbackParameterTypes = Arrays.copyOf(fallbackParameterTypes,
fallbackParameterTypes.length + 1);
extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;

Optional<Method> exFallbackMethod = getMethod(enclosingType, name, extendedFallbackParameterTypes);
Optional<Method> fMethod = getMethod(enclosingType, name, fallbackParameterTypes);
Method method = exFallbackMethod.or(fMethod).orNull();
if (method == null) {
throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
}
return new FallbackMethod(method, exFallbackMethod.isPresent(), isDefault());
}

}


/**
* Gets method by name and parameters types using reflection,
* if the given type doesn't contain required method then continue applying this method for all super classes up to Object class.
Expand All @@ -96,7 +204,7 @@ public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod, boo
* @param parameterTypes the parameters types
* @return Some if method exists otherwise None
*/
public Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes) {
public static Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes) {
Method[] methods = type.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), parameterTypes)) {
Expand Down
Loading

0 comments on commit dd91778

Please sign in to comment.