diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java index c5623aba3..75f56a5fa 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java @@ -108,6 +108,10 @@ protected Observable resumeWithFallback() { Object res = commandActions.getFallbackAction().executeWithArgs(executionType, args); if (res instanceof Observable) { return (Observable) res; + } else if (res instanceof Single) { + return ((Single) res).toObservable(); + } else if (res instanceof Completable) { + return ((Completable) res).toObservable(); } else { return Observable.just(res); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java index e2b00d0e2..d620f13fa 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java @@ -25,6 +25,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import rx.Completable; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -92,6 +93,13 @@ public void validateReturnType(Method commandMethod) { if (ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(commandReturnType)) { if (ExecutionType.OBSERVABLE != getExecutionType()) { Type commandParametrizedType = commandMethod.getGenericReturnType(); + + // basically any object can be wrapped into Completable, Completable itself ins't parametrized + if(Completable.class.isAssignableFrom(commandMethod.getReturnType())) { + validateCompletableReturnType(commandMethod, method.getReturnType()); + return; + } + if (isReturnTypeParametrized(commandMethod)) { commandParametrizedType = getFirstParametrizedType(commandMethod); } @@ -137,6 +145,13 @@ private Type getFirstParametrizedType(Method m) { return null; } + // everything can be wrapped into completable except 'void' + private void validateCompletableReturnType(Method commandMethod, Class callbackReturnType) { + if (Void.TYPE == callbackReturnType) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return 'void' if command return type is " + Completable.class.getSimpleName())); + } + } + private void validateReturnType(Method commandMethod, Method fallbackMethod) { if (isGenericReturnType(commandMethod)) { List commandParametrizedTypes = flattenTypeVariables(commandMethod.getGenericReturnType()); diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java index 233bad874..f6f6d1bde 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/fallback/BasicGenericFallbackTest.java @@ -14,6 +14,7 @@ import org.slf4j.helpers.MessageFormatter; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; +import rx.Completable; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; @@ -64,6 +65,7 @@ public Object[] methodGenericDefinitionFailure() { new Object[]{MethodGenericDefinitionFailureCase8.class}, new Object[]{MethodGenericDefinitionFailureCase9.class}, new Object[]{MethodGenericDefinitionFailureCase10.class}, + new Object[]{MethodGenericDefinitionFailureCase11.class}, }; } @@ -247,6 +249,12 @@ public static class MethodGenericDefinitionFailureCase10 { private GenericEntity fallback() { return null; } } + public static class MethodGenericDefinitionFailureCase11 { + @HystrixCommand(fallbackMethod = "fallback") + public Completable command() { throw new IllegalStateException(); } + private void fallback() { return; } + } + /* ====================================================================== */ /* ===================== GENERIC CLASS DEFINITIONS =====+================ */ /* =========================== SUCCESS CASES ============================ */ diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java index 262c24bb6..8af92d951 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java @@ -30,7 +30,7 @@ import rx.Single; import rx.Subscriber; import rx.functions.Action1; -import rx.subjects.ReplaySubject; +import rx.functions.Func0; import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; import static org.junit.Assert.assertEquals; @@ -93,19 +93,82 @@ public void call(User user) { } @Test - public void testCompletable(){ - userService.getUserCompletable("1", "name: "); - com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserCompletable"); + public void testGetCompletableUser(){ + userService.getCompletableUser("1", "name: "); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUser"); assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test - public void testSingle(){ - userService.getUserSingle("1", "name: "); - com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserSingle"); + public void testGetCompletableUserWithRegularFallback() { + Completable completable = userService.getCompletableUserWithRegularFallback(null, "name: "); + completable.toObservable().subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUserWithRegularFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetCompletableUserWithRxFallback() { + Completable completable = userService.getCompletableUserWithRxFallback(null, "name: "); + completable.toObservable().subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getCompletableUserWithRxFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetSingleUser() { + final String id = "1"; + Single user = userService.getSingleUser(id, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals(id, user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUser"); assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } + @Test + public void testGetSingleUserWithRegularFallback(){ + Single user = userService.getSingleUserWithRegularFallback(null, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUserWithRegularFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + + @Test + public void testGetSingleUserWithRxFallback(){ + Single user = userService.getSingleUserWithRxFallback(null, "name: "); + user.subscribe(new Action1() { + @Override + public void call(User user) { + assertEquals("default_id", user.getId()); + } + }); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getSingleUserWithRxFallback"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + @Test public void testGetUserWithRegularFallback() { final User exUser = new User("def", "def"); @@ -179,17 +242,58 @@ public Observable getUser(final String id, final String name) { } @HystrixCommand - public Completable getUserCompletable(final String id, final String name) { - validate(id, name, "getUser has failed"); + public Completable getCompletableUser(final String id, final String name) { + validate(id, name, "getCompletableUser has failed"); return createObservable(id, name).toCompletable(); } + @HystrixCommand(fallbackMethod = "completableUserRegularFallback") + public Completable getCompletableUserWithRegularFallback(final String id, final String name) { + return getCompletableUser(id, name); + } + + @HystrixCommand(fallbackMethod = "completableUserRxFallback") + public Completable getCompletableUserWithRxFallback(final String id, final String name) { + return getCompletableUser(id, name); + } + + public User completableUserRegularFallback(final String id, final String name) { + return new User("default_id", "default_name"); + } + + public Completable completableUserRxFallback(final String id, final String name) { + return Completable.fromCallable(new Func0() { + @Override + public User call() { + return new User("default_id", "default_name"); + } + }); + } + @HystrixCommand - public Single getUserSingle(final String id, final String name) { - validate(id, name, "getUser has failed"); + public Single getSingleUser(final String id, final String name) { + validate(id, name, "getSingleUser has failed"); return createObservable(id, name).toSingle(); } + @HystrixCommand(fallbackMethod = "singleUserRegularFallback") + public Single getSingleUserWithRegularFallback(final String id, final String name) { + return getSingleUser(id, name); + } + + @HystrixCommand(fallbackMethod = "singleUserRxFallback") + public Single getSingleUserWithRxFallback(final String id, final String name) { + return getSingleUser(id, name); + } + + User singleUserRegularFallback(final String id, final String name) { + return new User("default_id", "default_name"); + } + + Single singleUserRxFallback(final String id, final String name) { + return createObservable("default_id", "default_name").toSingle(); + } + @HystrixCommand(fallbackMethod = "regularFallback", observableExecutionMode = ObservableExecutionMode.LAZY) public Observable getUserRegularFallback(final String id, final String name) { validate(id, name, "getUser has failed");