diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java index 813696f7f..3bbcdd1bf 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java @@ -67,11 +67,17 @@ HystrixProperty[] threadPoolProperties() default {}; /** - * Defines exceptions which should be ignored and wrapped to throw in HystrixBadRequestException. - * All methods annotated with @HystrixCommand will automatically inherit this property. - * + * Defines exceptions which should be ignored. + * Optionally these can be wrapped in HystrixRuntimeException if raiseHystrixExceptions contains RUNTIME_EXCEPTION. * * @return exceptions to ignore */ Class[] ignoreExceptions() default {}; + + /** + * When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException. + * + * @return exceptions to wrap + */ + HystrixException[] raiseHystrixExceptions() default {}; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java index fcc7f765a..faa2cd227 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java @@ -100,7 +100,8 @@ HystrixProperty[] threadPoolProperties() default {}; /** - * Defines exceptions which should be ignored and wrapped to throw in HystrixBadRequestException. + * Defines exceptions which should be ignored. + * Optionally these can be wrapped in HystrixRuntimeException if raiseHystrixExceptions contains RUNTIME_EXCEPTION. * * @return exceptions to ignore */ @@ -113,5 +114,12 @@ * @return observable execution mode */ ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER; + + /** + * When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException. + * + * @return exceptions to wrap + */ + HystrixException[] raiseHystrixExceptions() default {}; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java new file mode 100644 index 000000000..ff8331194 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixException.java @@ -0,0 +1,8 @@ +package com.netflix.hystrix.contrib.javanica.annotation; + +/** + * Created by Mike Cowan + */ +public enum HystrixException { + RUNTIME_EXCEPTION, +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java index 448f2b236..3f1347fc2 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java @@ -21,6 +21,7 @@ import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixException; import com.netflix.hystrix.contrib.javanica.command.CommandExecutor; import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory; @@ -103,6 +104,9 @@ public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinP } catch (HystrixBadRequestException e) { throw e.getCause(); } catch (HystrixRuntimeException e) { + if (metaHolder.raiseHystrixExceptionsContains(HystrixException.RUNTIME_EXCEPTION)) { + throw e; + } throw getCause(e); } return result; diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java index 1624d1436..84e8bf337 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java @@ -20,11 +20,7 @@ import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; -import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; -import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; +import com.netflix.hystrix.contrib.javanica.annotation.*; import com.netflix.hystrix.contrib.javanica.command.closure.Closure; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; @@ -299,6 +295,27 @@ public ObservableExecutionMode getObservableExecutionMode() { return observableExecutionMode; } + public boolean raiseHystrixExceptionsContains(HystrixException hystrixException) { + return getRaiseHystrixExceptions().contains(hystrixException); + } + + public List getRaiseHystrixExceptions() { + return getOrDefault(new Supplier>() { + @Override + public List get() { + return ImmutableList.copyOf(hystrixCommand.raiseHystrixExceptions()); + } + }, new Supplier>() { + @Override + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.raiseHystrixExceptions()) + : Collections.emptyList(); + + } + }, this.nonEmptyList()); + } + private String get(String key, String defaultKey) { return StringUtils.isNotBlank(key) ? key : defaultKey; } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java new file mode 100644 index 000000000..d5a6951d0 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultRaiseHystrixExceptionsTest.java @@ -0,0 +1,123 @@ +package com.netflix.hystrix.contrib.javanica.test.common.error; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixException; +import com.netflix.hystrix.exception.HystrixRuntimeException; + +/** + * Created by Mike Cowan + */ +public abstract class BasicDefaultRaiseHystrixExceptionsTest { + + private Service service; + + @Before + public void setUp() throws Exception { + service = createService(); + } + + protected abstract Service createService(); + + @Test(expected = BadRequestException.class) + public void testDefaultIgnoreException() { + service.commandInheritsDefaultIgnoreExceptions(); + } + + @Test(expected = SpecificException.class) + public void testCommandOverridesDefaultIgnoreExceptions() { + service.commandOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = HystrixRuntimeException.class) + public void testCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + // method throws BadRequestException that isn't ignored + service.commandOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = BadRequestException.class) + public void testFallbackCommandInheritsDefaultIgnoreException() { + service.commandWithFallbackInheritsDefaultIgnoreExceptions(); + } + + @Ignore // https://github.com/Netflix/Hystrix/issues/993#issuecomment-229542203 + @Test(expected = SpecificException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(SpecificException.class); + } + + @Test(expected = HystrixRuntimeException.class) + public void testFallbackCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { + service.commandWithFallbackOverridesDefaultIgnoreExceptions(BadRequestException.class); + } + + @DefaultProperties(ignoreExceptions = BadRequestException.class, raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION}) + public static class Service { + @HystrixCommand + public Object commandInheritsDefaultIgnoreExceptions() throws BadRequestException { + // this exception will be ignored (wrapped in HystrixBadRequestException) because specified in default ignore exceptions + throw new BadRequestException("from 'commandInheritsIgnoreExceptionsFromDefault'"); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + public Object commandOverridesDefaultIgnoreExceptions(Class errorType) throws BadRequestException, SpecificException { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because command doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the command's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + + @HystrixCommand(fallbackMethod = "fallbackInheritsDefaultIgnoreExceptions") + public Object commandWithFallbackInheritsDefaultIgnoreExceptions() throws SpecificException { + // isn't ignored, need to trigger fallback + throw new SpecificException("from 'commandWithFallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand + private Object fallbackInheritsDefaultIgnoreExceptions() throws BadRequestException { + // should be ignored because specified in global ignore exception, fallback command inherits default ignore exceptions + throw new BadRequestException("from 'fallbackInheritsDefaultIgnoreExceptions'"); + } + + @HystrixCommand(fallbackMethod = "fallbackOverridesDefaultIgnoreExceptions") + public Object commandWithFallbackOverridesDefaultIgnoreExceptions(Class errorType) { + // isn't ignored, need to trigger fallback + throw new SpecificException(); + } + + @HystrixCommand(ignoreExceptions = SpecificException.class) + private Object fallbackOverridesDefaultIgnoreExceptions(Class errorType) { + if(errorType.equals(BadRequestException.class)){ + // isn't ignored because fallback doesn't specify this exception type in 'ignoreExceptions' + throw new BadRequestException("from 'fallbackOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + // something went wrong, this error is ignored because specified in the fallback's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); + } + } + + public static final class BadRequestException extends RuntimeException { + public BadRequestException() { + } + + public BadRequestException(String message) { + super(message); + } + } + + public static final class SpecificException extends RuntimeException { + public SpecificException() { + } + + public SpecificException(String message) { + super(message); + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java new file mode 100644 index 000000000..8e6505572 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultRaiseHystrixExceptionsTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.error; + +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicDefaultRaiseHystrixExceptionsTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; + +/** + * Created by Mike Cowan + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, DefaultRaiseHystrixExceptionsTest.DefaultRaiseHystrixExceptionsTestConfig.class}) +public class DefaultRaiseHystrixExceptionsTest extends BasicDefaultRaiseHystrixExceptionsTest { + + @Autowired + private BasicDefaultRaiseHystrixExceptionsTest.Service service; + + @Override + protected BasicDefaultRaiseHystrixExceptionsTest.Service createService() { + return service; + } + + @Configurable + public static class DefaultRaiseHystrixExceptionsTestConfig { + + @Bean + public BasicDefaultRaiseHystrixExceptionsTest.Service userService() { + return new BasicDefaultRaiseHystrixExceptionsTest.Service(); + } + } +}