Skip to content

Commit dd91778

Browse files
committed
iss1446: initial implementation
1 parent c674545 commit dd91778

File tree

8 files changed

+397
-26
lines changed

8 files changed

+397
-26
lines changed

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,13 @@
8080
* @return exceptions to wrap
8181
*/
8282
HystrixException[] raiseHystrixExceptions() default {};
83+
84+
/**
85+
* Specifies default fallback method for each command in the given class. Every command within the class should
86+
* have a return type which is compatible with default fallback method return type.
87+
* note: default fallback method cannot have parameters.
88+
*
89+
* @return the name of default fallback method
90+
*/
91+
String defaultFallback() default "";
8392
}

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,14 @@
121121
* @return exceptions to wrap
122122
*/
123123
HystrixException[] raiseHystrixExceptions() default {};
124+
125+
/**
126+
* Specifies default fallback method for the command. If both {@link #fallbackMethod} and {@link #defaultFallback}
127+
* methods are specified then specific one is used.
128+
* note: default fallback method cannot have parameters, return type should be compatible with command return type.
129+
*
130+
* @return the name of default fallback method
131+
*/
132+
String defaultFallback() default "";
124133
}
125134

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,17 @@ private CommandAction createFallbackAction(MetaHolder metaHolder) {
105105
if (fallbackMethod.isPresent()) {
106106

107107
Method fMethod = fallbackMethod.getMethod();
108+
Object[] args = fallbackMethod.isDefault() ? new Object[0] : metaHolder.getArgs();
108109
if (fallbackMethod.isCommand()) {
109110
fMethod.setAccessible(true);
110111
HystrixCommand hystrixCommand = fMethod.getAnnotation(HystrixCommand.class);
111112
MetaHolder fmMetaHolder = MetaHolder.builder()
112113
.obj(metaHolder.getObj())
113114
.method(fMethod)
114115
.ajcMethod(getAjcMethod(metaHolder.getObj(), fMethod))
115-
.args(metaHolder.getArgs())
116+
.args(args)
116117
.fallback(true)
118+
.defaultFallback(fallbackMethod.isDefault())
117119
.defaultCollapserKey(metaHolder.getDefaultCollapserKey())
118120
.fallbackMethod(fMethod)
119121
.extendedFallback(fallbackMethod.isExtended())
@@ -131,12 +133,13 @@ private CommandAction createFallbackAction(MetaHolder metaHolder) {
131133
} else {
132134
MetaHolder fmMetaHolder = MetaHolder.builder()
133135
.obj(metaHolder.getObj())
136+
.defaultFallback(fallbackMethod.isDefault())
134137
.method(fMethod)
135138
.fallbackExecutionType(ExecutionType.SYNCHRONOUS)
136139
.extendedFallback(fallbackMethod.isExtended())
137140
.extendedParentFallback(metaHolder.isExtendedFallback())
138141
.ajcMethod(null) // if fallback method isn't annotated with command annotation then we don't need to get ajc method for this
139-
.args(metaHolder.getArgs()).build();
142+
.args(args).build();
140143

141144
fallbackAction = new MethodExecutionAction(fmMetaHolder.getObj(), fMethod, fmMetaHolder.getArgs(), fmMetaHolder);
142145
}

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
/**
3636
* Simple immutable holder to keep all necessary information about current method to build Hystrix command.
3737
*/
38+
// todo: replace fallback related flags with FallbackMethod class
3839
@Immutable
3940
public final class MetaHolder {
4041

@@ -60,6 +61,7 @@ public final class MetaHolder {
6061
private final ExecutionType fallbackExecutionType;
6162
private final boolean fallback;
6263
private boolean extendedParentFallback;
64+
private final boolean defaultFallback;
6365
private final JoinPoint joinPoint;
6466
private final boolean observable;
6567
private final ObservableExecutionMode observableExecutionMode;
@@ -93,6 +95,7 @@ private MetaHolder(Builder builder) {
9395
this.fallbackExecutionType = builder.fallbackExecutionType;
9496
this.joinPoint = builder.joinPoint;
9597
this.extendedFallback = builder.extendedFallback;
98+
this.defaultFallback = builder.defaultFallback;
9699
this.fallback = builder.fallback;
97100
this.extendedParentFallback = builder.extendedParentFallback;
98101
this.observable = builder.observable;
@@ -227,6 +230,10 @@ public boolean isExtendedFallback() {
227230
return extendedFallback;
228231
}
229232

233+
public boolean isDefaultFallback() {
234+
return defaultFallback;
235+
}
236+
230237
@SuppressWarnings("unchecked")
231238
public List<Class<? extends Throwable>> getCommandIgnoreExceptions() {
232239
if (!isCommandAnnotationPresent()) return Collections.emptyList();
@@ -367,6 +374,7 @@ public static final class Builder {
367374
private boolean extendedFallback;
368375
private boolean fallback;
369376
private boolean extendedParentFallback;
377+
private boolean defaultFallback;
370378
private boolean observable;
371379
private JoinPoint joinPoint;
372380
private ObservableExecutionMode observableExecutionMode;
@@ -411,6 +419,11 @@ public Builder extendedParentFallback(boolean extendedParentFallback) {
411419
return this;
412420
}
413421

422+
public Builder defaultFallback(boolean defaultFallback) {
423+
this.defaultFallback = defaultFallback;
424+
return this;
425+
}
426+
414427
public Builder ajcMethod(Method ajcMethod) {
415428
this.ajcMethod = ajcMethod;
416429
return this;

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,19 @@ public class FallbackMethod {
5050

5151
private final Method method;
5252
private final boolean extended;
53+
private final boolean defaultFallback;
5354
private ExecutionType executionType;
5455

55-
public static final FallbackMethod ABSENT = new FallbackMethod(null, false);
56+
public static final FallbackMethod ABSENT = new FallbackMethod(null, false, false);
5657

5758
public FallbackMethod(Method method) {
58-
this(method, false);
59+
this(method, false, false);
5960
}
6061

61-
public FallbackMethod(Method method, boolean extended) {
62+
public FallbackMethod(Method method, boolean extended, boolean defaultFallback) {
6263
this.method = method;
6364
this.extended = extended;
65+
this.defaultFallback = defaultFallback;
6466
if (method != null) {
6567
this.executionType = ExecutionType.getExecutionType(method.getReturnType());
6668
}
@@ -86,7 +88,11 @@ public boolean isExtended() {
8688
return extended;
8789
}
8890

89-
public void validateReturnType(Method commandMethod) {
91+
public boolean isDefault() {
92+
return defaultFallback;
93+
}
94+
95+
public void validateReturnType(Method commandMethod) throws FallbackDefinitionException {
9096
if (isPresent()) {
9197
Class<?> commandReturnType = commandMethod.getReturnType();
9298
if (ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(commandReturnType)) {

hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package com.netflix.hystrix.contrib.javanica.utils;
1717

18+
import com.google.common.base.Function;
1819
import com.google.common.base.Optional;
20+
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
1921
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
2022
import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException;
2123
import org.apache.commons.lang3.ArrayUtils;
@@ -49,6 +51,8 @@ public static MethodProvider getInstance() {
4951
return INSTANCE;
5052
}
5153

54+
private static final FallbackMethodFinder FALLBACK_METHOD_FINDER = new SpecificFallback(new DefaultCallback());
55+
5256
private Map<Method, Method> cache = new ConcurrentHashMap<Method, Method>();
5357

5458
public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod) {
@@ -58,35 +62,139 @@ public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod) {
5862
/**
5963
* Gets fallback method for command method.
6064
*
61-
* @param type type
65+
* @param enclosingType the enclosing class
6266
* @param commandMethod the command method. in the essence it can be a fallback
6367
* method annotated with HystrixCommand annotation that has a fallback as well.
6468
* @param extended true if the given commandMethod was derived using additional parameter, otherwise - false
6569
* @return new instance of {@link FallbackMethod} or {@link FallbackMethod#ABSENT} if there is no suitable fallback method for the given command
6670
*/
67-
public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod, boolean extended) {
71+
public FallbackMethod getFallbackMethod(Class<?> enclosingType, Method commandMethod, boolean extended) {
6872
if (commandMethod.isAnnotationPresent(HystrixCommand.class)) {
69-
HystrixCommand hystrixCommand = commandMethod.getAnnotation(HystrixCommand.class);
70-
if (StringUtils.isNotBlank(hystrixCommand.fallbackMethod())) {
71-
Class<?>[] parameterTypes = commandMethod.getParameterTypes();
72-
if (extended && parameterTypes[parameterTypes.length - 1] == Throwable.class) {
73-
parameterTypes = ArrayUtils.remove(parameterTypes, parameterTypes.length - 1);
74-
}
75-
Class<?>[] exParameterTypes = Arrays.copyOf(parameterTypes, parameterTypes.length + 1);
76-
exParameterTypes[parameterTypes.length] = Throwable.class;
77-
Optional<Method> exFallbackMethod = getMethod(type, hystrixCommand.fallbackMethod(), exParameterTypes);
78-
Optional<Method> fMethod = getMethod(type, hystrixCommand.fallbackMethod(),
79-
parameterTypes);
80-
Method method = exFallbackMethod.or(fMethod).orNull();
81-
if (method == null) {
82-
throw new FallbackDefinitionException("fallback method wasn't found: " + hystrixCommand.fallbackMethod() + "(" + Arrays.toString(parameterTypes) + ")");
83-
}
84-
return new FallbackMethod(method, exFallbackMethod.isPresent());
85-
}
73+
return FALLBACK_METHOD_FINDER.find(enclosingType, commandMethod, extended);
8674
}
8775
return FallbackMethod.ABSENT;
8876
}
8977

78+
private void getDefaultFallback(){
79+
80+
}
81+
82+
private String getClassLevelFallback(Class<?> enclosingClass) {
83+
if (enclosingClass.isAnnotationPresent(DefaultProperties.class)) {
84+
return enclosingClass.getAnnotation(DefaultProperties.class).defaultFallback();
85+
}
86+
return StringUtils.EMPTY;
87+
}
88+
89+
private static class SpecificFallback extends FallbackMethodFinder {
90+
91+
public SpecificFallback(FallbackMethodFinder next) {
92+
super(next);
93+
}
94+
95+
@Override
96+
boolean isSpecific() {
97+
return true;
98+
}
99+
100+
@Override
101+
public String getFallbackName(Class<?> enclosingType, Method commandMethod) {
102+
return commandMethod.getAnnotation(HystrixCommand.class).fallbackMethod();
103+
}
104+
105+
@Override
106+
boolean canHandle(Class<?> enclosingType, Method commandMethod) {
107+
return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod));
108+
}
109+
}
110+
111+
private static class DefaultCallback extends FallbackMethodFinder {
112+
@Override
113+
boolean isDefault() {
114+
return true;
115+
}
116+
117+
@Override
118+
public String getFallbackName(Class<?> enclosingType, Method commandMethod) {
119+
String commandDefaultFallback = commandMethod.getAnnotation(HystrixCommand.class).defaultFallback();
120+
String classDefaultFallback = Optional.fromNullable(enclosingType.getAnnotation(DefaultProperties.class))
121+
.transform(new Function<DefaultProperties, String>() {
122+
@Override
123+
public String apply(DefaultProperties input) {
124+
return input.defaultFallback();
125+
}
126+
}).or(StringUtils.EMPTY);
127+
128+
return StringUtils.defaultIfEmpty(commandDefaultFallback, classDefaultFallback);
129+
}
130+
131+
@Override
132+
boolean canHandle(Class<?> enclosingType, Method commandMethod) {
133+
return StringUtils.isNotBlank(getFallbackName(enclosingType, commandMethod));
134+
}
135+
}
136+
137+
private static abstract class FallbackMethodFinder {
138+
FallbackMethodFinder next;
139+
140+
public FallbackMethodFinder() {
141+
}
142+
143+
public FallbackMethodFinder(FallbackMethodFinder next) {
144+
this.next = next;
145+
}
146+
147+
boolean isDefault() {
148+
return false;
149+
}
150+
151+
boolean isSpecific(){
152+
return false;
153+
}
154+
155+
public abstract String getFallbackName(Class<?> enclosingType, Method commandMethod);
156+
157+
public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
158+
if (canHandle(enclosingType, commandMethod)) {
159+
return doFind(enclosingType, commandMethod, extended);
160+
} else if (next != null) {
161+
return next.find(enclosingType, commandMethod, extended);
162+
} else {
163+
return FallbackMethod.ABSENT;
164+
}
165+
}
166+
167+
abstract boolean canHandle(Class<?> enclosingType, Method commandMethod);
168+
169+
private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
170+
String name = getFallbackName(enclosingType, commandMethod);
171+
Class<?>[] fallbackParameterTypes = null;
172+
if (isDefault()) {
173+
fallbackParameterTypes = new Class[0];
174+
} else {
175+
fallbackParameterTypes = commandMethod.getParameterTypes();
176+
}
177+
178+
if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
179+
fallbackParameterTypes = ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
180+
}
181+
182+
Class<?>[] extendedFallbackParameterTypes = Arrays.copyOf(fallbackParameterTypes,
183+
fallbackParameterTypes.length + 1);
184+
extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
185+
186+
Optional<Method> exFallbackMethod = getMethod(enclosingType, name, extendedFallbackParameterTypes);
187+
Optional<Method> fMethod = getMethod(enclosingType, name, fallbackParameterTypes);
188+
Method method = exFallbackMethod.or(fMethod).orNull();
189+
if (method == null) {
190+
throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
191+
}
192+
return new FallbackMethod(method, exFallbackMethod.isPresent(), isDefault());
193+
}
194+
195+
}
196+
197+
90198
/**
91199
* Gets method by name and parameters types using reflection,
92200
* if the given type doesn't contain required method then continue applying this method for all super classes up to Object class.
@@ -96,7 +204,7 @@ public FallbackMethod getFallbackMethod(Class<?> type, Method commandMethod, boo
96204
* @param parameterTypes the parameters types
97205
* @return Some if method exists otherwise None
98206
*/
99-
public Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes) {
207+
public static Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes) {
100208
Method[] methods = type.getDeclaredMethods();
101209
for (Method method : methods) {
102210
if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), parameterTypes)) {

0 commit comments

Comments
 (0)