Skip to content

Commit

Permalink
Merge pull request #1397 from michaelcowan/feature/optionally-raise-h…
Browse files Browse the repository at this point in the history
…ystrix-runtime-exception

Add option to raise HystrixRuntimeException
  • Loading branch information
mattrjacobs authored Oct 24, 2016
2 parents 8b5a075 + 478c211 commit a735c80
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends Throwable>[] ignoreExceptions() default {};

/**
* When includes RUNTIME_EXCEPTION, any exceptions that are not ignored are wrapped in HystrixRuntimeException.
*
* @return exceptions to wrap
*/
HystrixException[] raiseHystrixExceptions() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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 {};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.netflix.hystrix.contrib.javanica.annotation;

/**
* Created by Mike Cowan
*/
public enum HystrixException {
RUNTIME_EXCEPTION,
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -299,6 +295,27 @@ public ObservableExecutionMode getObservableExecutionMode() {
return observableExecutionMode;
}

public boolean raiseHystrixExceptionsContains(HystrixException hystrixException) {
return getRaiseHystrixExceptions().contains(hystrixException);
}

public List<HystrixException> getRaiseHystrixExceptions() {
return getOrDefault(new Supplier<List<HystrixException>>() {
@Override
public List<HystrixException> get() {
return ImmutableList.copyOf(hystrixCommand.raiseHystrixExceptions());
}
}, new Supplier<List<HystrixException>>() {
@Override
public List<HystrixException> get() {
return hasDefaultProperties()
? ImmutableList.copyOf(defaultProperties.raiseHystrixExceptions())
: Collections.<HystrixException>emptyList();

}
}, this.<HystrixException>nonEmptyList());
}

private String get(String key, String defaultKey) {
return StringUtils.isNotBlank(key) ? key : defaultKey;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends Throwable> 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<? extends Throwable> errorType) {
// isn't ignored, need to trigger fallback
throw new SpecificException();
}

@HystrixCommand(ignoreExceptions = SpecificException.class)
private Object fallbackOverridesDefaultIgnoreExceptions(Class<? extends Throwable> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}

0 comments on commit a735c80

Please sign in to comment.