From ea27ba14a08c3b35810746b0a79fbfcb11ffbc75 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sun, 26 Jun 2016 23:40:28 -0400 Subject: [PATCH 01/10] iss993: added DefaultProperties annotation [initial commit] --- .../annotation/DefaultProperties.java | 44 ++++++++++++++++++ .../aop/aspectj/HystrixCommandAspect.java | 4 +- .../command/HystrixCommandBuilderFactory.java | 10 +++- .../contrib/javanica/command/MetaHolder.java | 22 +++++++++ .../contrib/javanica/utils/AopUtils.java | 22 +++++++++ .../BasicDefaultIgnoreExceptionsTest.java | 46 +++++++++++++++++++ .../error/DefaultIgnoreExceptionsTest.java | 36 +++++++++++++++ 7 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java 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 new file mode 100644 index 000000000..4bbc107c2 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/DefaultProperties.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.hystrix.contrib.javanica.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * todo: add doc.. + * + * @author dmgcodevil + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface DefaultProperties { + + /** + * Defines exceptions which should be ignored and wrapped to throw in HystrixBadRequestException. + * All methods annotated with @HystrixCommand will automatically inherit this property. + * + * + * @return exceptions to ignore + */ + Class[] ignoreExceptions() default {}; +} 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 eb3fe77c8..6e163c441 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 @@ -17,14 +17,15 @@ import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; -import com.netflix.hystrix.HystrixExecutable; import com.netflix.hystrix.HystrixInvokable; +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.command.CommandExecutor; import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.utils.AopUtils; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; import com.netflix.hystrix.exception.HystrixBadRequestException; @@ -206,6 +207,7 @@ public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint); + builder.defaultProperties(AopUtils.getAnnotation(joinPoint, DefaultProperties.class)); return builder.defaultCommandKey(method.getName()) .hystrixCommand(hystrixCommand) .observableExecutionMode(hystrixCommand.observableExecutionMode()) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java index 2523e4c21..548eec8a0 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -15,13 +15,16 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.google.common.base.Function; import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; @@ -61,7 +64,12 @@ public HystrixCommandBuilder create(MetaHolder metaHolder, Collection[]>() { + @Override + public Class[] apply(DefaultProperties input) { + return input.ignoreExceptions(); + } + }).or(metaHolder.getHystrixCommand().ignoreExceptions())) .executionType(metaHolder.getExecutionType()) .build(); } 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 e3fdf1015..9f6a3ea22 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 @@ -15,7 +15,9 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.google.common.base.Optional; 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; @@ -23,6 +25,7 @@ import com.netflix.hystrix.contrib.javanica.command.closure.Closure; import org.aspectj.lang.JoinPoint; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import java.lang.reflect.Method; import java.util.Arrays; @@ -37,6 +40,7 @@ public class MetaHolder { private final HystrixCollapser hystrixCollapser; private final HystrixCommand hystrixCommand; + private final Optional defaultProperties; private final Method method; private final Method cacheKeyMethod; @@ -72,6 +76,7 @@ private MetaHolder(Builder builder) { this.defaultGroupKey = builder.defaultGroupKey; this.defaultCommandKey = builder.defaultCommandKey; this.defaultCollapserKey = builder.defaultCollapserKey; + this.defaultProperties = builder.defaultProperties; this.hystrixCollapser = builder.hystrixCollapser; this.executionType = builder.executionType; this.collapserExecutionType = builder.collapserExecutionType; @@ -144,6 +149,10 @@ public String getDefaultCollapserKey() { return defaultCollapserKey; } + public Optional getDefaultProperties() { + return defaultProperties; + } + public Class[] getParameterTypes() { return method.getParameterTypes(); } @@ -210,8 +219,11 @@ public ObservableExecutionMode getObservableExecutionMode() { public static final class Builder { + private static final Class[] EMPTY_ARRAY_OF_TYPES= new Class[0]; + private HystrixCollapser hystrixCollapser; private HystrixCommand hystrixCommand; + private Optional defaultProperties; private Method method; private Method cacheKeyMethod; private Method fallbackMethod; @@ -323,6 +335,16 @@ public Builder defaultCollapserKey(String defCollapserKey) { return this; } + public Builder defaultProperties(@Nullable DefaultProperties defaultProperties) { + this.defaultProperties = Optional.fromNullable(defaultProperties); + return this; + } + + public Builder defaultProperties(Optional defaultProperties) { + this.defaultProperties = defaultProperties; + return this; + } + public Builder joinPoint(JoinPoint joinPoint) { this.joinPoint = joinPoint; return this; diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java index ccec4e007..40d3f4ece 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java @@ -15,11 +15,14 @@ */ package com.netflix.hystrix.contrib.javanica.utils; +import com.google.common.base.Optional; import com.google.common.base.Throwables; +import org.apache.commons.lang3.Validate; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** @@ -101,4 +104,23 @@ public static Method getDeclaredMethod(Class type, String methodName, Class Optional getAnnotation(JoinPoint joinPoint, Class annotation) { + return getAnnotation(joinPoint.getTarget().getClass(), annotation); + } + + public static Optional getAnnotation(Class type, Class annotation) { + Validate.notNull(annotation, "annotation cannot be null"); + Validate.notNull(type, "type cannot be null"); + for (Annotation ann : type.getDeclaredAnnotations()) { + if (ann.annotationType().equals(annotation)) return Optional.of((T) ann); + } + + Class superType = type.getSuperclass(); + if (superType != null && !superType.equals(Object.class)) { + return getAnnotation(superType, annotation); + } + + return Optional.absent(); + } + } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java new file mode 100644 index 000000000..f36304793 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java @@ -0,0 +1,46 @@ +package com.netflix.hystrix.contrib.javanica.test.common.error; + +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import org.junit.Before; +import org.junit.Test; + +/** + * todo [dmgcodevil]: add docs + *

+ * Created by dmgcodevil. + */ +public abstract class BasicDefaultIgnoreExceptionsTest { + private UserService userService; + + @Before + public void setUp() throws Exception { + userService = createUserService(); + } + + protected abstract UserService createUserService(); + + @Test(expected = IllegalArgumentException.class) + public void testDefaultIgnoreException() { + userService.getById(""); + } + + @DefaultProperties(ignoreExceptions = IllegalArgumentException.class) + public static class UserService { + @HystrixCommand + public User getById(String id) throws IllegalArgumentException { + if (id == null || "".equals(id)) { + throw new IllegalArgumentException("bad id"); + } + return new User(id); + } + } + + public static class User { + private String id; + + public User(String id) { + this.id = id; + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java new file mode 100644 index 000000000..c9f4be858 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.error; + +import com.netflix.hystrix.contrib.javanica.test.common.error.BasicDefaultIgnoreExceptionsTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +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; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, DefaultIgnoreExceptionsTest.DefaultIgnoreExceptionsTestConfig.class}) +public class DefaultIgnoreExceptionsTest extends BasicDefaultIgnoreExceptionsTest { + + + @Autowired + private BasicDefaultIgnoreExceptionsTest.UserService userService; + + @Override + protected BasicDefaultIgnoreExceptionsTest.UserService createUserService() { + return userService; + } + + @Configurable + public static class DefaultIgnoreExceptionsTestConfig { + + @Bean + public BasicDefaultIgnoreExceptionsTest.UserService userService() { + return new BasicDefaultIgnoreExceptionsTest.UserService(); + } + } +} From 9a56749f88a6adb419ace651403db1894f1fe140 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Tue, 28 Jun 2016 23:21:35 -0400 Subject: [PATCH 02/10] iss993: added the following properties in DefaultProperties annotation: groupKey, threadPoolKey, commandProperties, threadPoolProperties --- .../annotation/DefaultProperties.java | 35 +++++++- .../aop/aspectj/HystrixCommandAspect.java | 52 ++++++++---- .../command/HystrixCommandBuilderFactory.java | 35 +------- .../contrib/javanica/command/MetaHolder.java | 84 ++++++++++++++++--- 4 files changed, 148 insertions(+), 58 deletions(-) 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 4bbc107c2..d331ffbe9 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 @@ -23,7 +23,8 @@ import java.lang.annotation.Target; /** - * todo: add doc.. + * This annotation is used to specify default parameters for + * hystrix commands (methods annotated with {@code @HystrixCommand} annotation). * * @author dmgcodevil */ @@ -33,6 +34,38 @@ @Documented public @interface DefaultProperties { + /** + * Specifies default group key used for each hystrix command by default unless a command specifies it's own group key. + * For additional info about this property see {@link HystrixCommand#groupKey()}. + * + * @return default group key + */ + String groupKey() default ""; + + /** + * Specifies default thread pool key used for each hystrix command by default unless a command specifies it's own thread pool key. + * For additional info about this property see {@link HystrixCommand#threadPoolKey()} + * + * @return default thread pool + */ + String threadPoolKey() default ""; + + /** + * Specifies command properties that will be used for + * each hystrix command be default unless command properties explicitly specified in @HystrixCommand. + * + * @return command properties + */ + HystrixProperty[] commandProperties() default {}; + + /** + * Specifies thread pool properties that will be used for + * each hystrix command be default unless thread pool properties explicitly specified in @HystrixCommand. + * + * @return thread pool properties + */ + 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. 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 6e163c441..496592577 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 @@ -15,6 +15,7 @@ */ package com.netflix.hystrix.contrib.javanica.aop.aspectj; +import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.netflix.hystrix.HystrixInvokable; @@ -29,6 +30,7 @@ import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; @@ -112,19 +114,10 @@ public MetaHolder create(final ProceedingJoinPoint joinPoint) { MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { MetaHolder.Builder builder = MetaHolder.builder() .args(args).method(method).obj(obj).proxyObj(proxy) - .defaultGroupKey(obj.getClass().getSimpleName()) .joinPoint(joinPoint); - if (isCompileWeaving()) { - builder.ajcMethod(getAjcMethodFromTarget(joinPoint)); - } - FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(obj.getClass(), method); - if (fallbackMethod.isPresent()) { - fallbackMethod.validateReturnType(method); - builder - .fallbackMethod(fallbackMethod.getMethod()) - .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType())); - } + setFallbackMethod(builder, obj.getClass(), method); + builder = setDefaultProperties(builder, obj.getClass(), joinPoint); return builder; } } @@ -174,10 +167,8 @@ public MetaHolder create(Object proxy, Method collapserMethod, Object obj, Objec } // method of batch hystrix command must be passed to metaholder because basically collapser doesn't have any actions // that should be invoked upon intercepted method, it's required only for underlying batch command - MetaHolder.Builder builder = MetaHolder.builder() - .args(args).method(batchCommandMethod).obj(obj).proxyObj(proxy) - .defaultGroupKey(obj.getClass().getSimpleName()) - .joinPoint(joinPoint); + + MetaHolder.Builder builder = metaHolderBuilder(proxy, batchCommandMethod, obj, args, joinPoint); if (isCompileWeaving()) { builder.ajcMethod(getAjcMethodAroundAdvice(obj.getClass(), batchCommandMethod.getName(), List.class)); @@ -207,7 +198,9 @@ public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint); - builder.defaultProperties(AopUtils.getAnnotation(joinPoint, DefaultProperties.class)); + if (isCompileWeaving()) { + builder.ajcMethod(getAjcMethodFromTarget(joinPoint)); + } return builder.defaultCommandKey(method.getName()) .hystrixCommand(hystrixCommand) .observableExecutionMode(hystrixCommand.observableExecutionMode()) @@ -241,4 +234,31 @@ private static Class getGenericParameter(Type type) { } } + private static MetaHolder.Builder setDefaultProperties(MetaHolder.Builder builder, Class declaringClass, final ProceedingJoinPoint joinPoint) { + Optional defaultPropertiesOpt = AopUtils.getAnnotation(joinPoint, DefaultProperties.class); + builder.defaultGroupKey(declaringClass.getSimpleName()); + if (defaultPropertiesOpt.isPresent()) { + DefaultProperties defaultProperties = defaultPropertiesOpt.get(); + builder.defaultProperties(defaultProperties); + if (StringUtils.isNotBlank(defaultProperties.groupKey())) { + builder.defaultGroupKey(defaultProperties.groupKey()); + } + if (StringUtils.isNotBlank(defaultProperties.threadPoolKey())) { + builder.defaultThreadPoolKey(defaultProperties.threadPoolKey()); + } + } + return builder; + } + + private static MetaHolder.Builder setFallbackMethod(MetaHolder.Builder builder, Class declaringClass, Method commandMethod) { + FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(declaringClass, commandMethod); + if (fallbackMethod.isPresent()) { + fallbackMethod.validateReturnType(commandMethod); + builder + .fallbackMethod(fallbackMethod.getMethod()) + .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType())); + } + return builder; + } + } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java index 548eec8a0..6f6c03b9f 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -21,10 +21,8 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; @@ -81,10 +79,10 @@ private void validateMetaHolder(MetaHolder metaHolder) { private GenericSetterBuilder createGenericSetterBuilder(MetaHolder metaHolder) { GenericSetterBuilder.Builder setterBuilder = GenericSetterBuilder.builder() - .groupKey(createGroupKey(metaHolder)) - .threadPoolKey(createThreadPoolKey(metaHolder)) - .commandKey(createCommandKey(metaHolder)) - .collapserKey(createCollapserKey(metaHolder)) + .groupKey(metaHolder.getCommandGroupKey()) + .threadPoolKey(metaHolder.getThreadPoolKey()) + .commandKey(metaHolder.getCommandKey()) + .collapserKey(metaHolder.getCollapserKey()) .commandProperties(metaHolder.getCommandProperties()) .threadPoolProperties(metaHolder.getThreadPoolProperties()) .collapserProperties(metaHolder.getCollapserProperties()); @@ -94,31 +92,6 @@ private GenericSetterBuilder createGenericSetterBuilder(MetaHolder metaHolder) { return setterBuilder.build(); } - private String createGroupKey(MetaHolder metaHolder) { - return createKey(metaHolder.getHystrixCommand().groupKey(), metaHolder.getDefaultGroupKey()); - } - - private String createThreadPoolKey(MetaHolder metaHolder) { - // this key is created without default value because intrinsically Hystrix knows how to derive this key properly if it's absent - return metaHolder.getHystrixCommand().threadPoolKey(); - } - - private String createCommandKey(MetaHolder metaHolder) { - return createKey(metaHolder.getHystrixCommand().commandKey(), metaHolder.getDefaultCommandKey()); - } - - private String createCollapserKey(MetaHolder metaHolder) { - if (metaHolder.isCollapserAnnotationPresent()) { - return createKey(metaHolder.getHystrixCollapser().collapserKey(), metaHolder.getDefaultCollapserKey()); - } - return null; - } - - private String createKey(String key, String defKey) { - return StringUtils.isNotBlank(key) ? key : defKey; - } - - private CommandActions createCommandActions(MetaHolder metaHolder) { CommandAction commandAction = createCommandAction(metaHolder); CommandAction fallbackAction = createFallbackAction(metaHolder); 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 9f6a3ea22..a693ac25f 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 @@ -16,6 +16,8 @@ package com.netflix.hystrix.contrib.javanica.command; import com.google.common.base.Optional; +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; @@ -23,6 +25,8 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; import com.netflix.hystrix.contrib.javanica.command.closure.Closure; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.aspectj.lang.JoinPoint; import javax.annotation.Nullable; @@ -40,7 +44,7 @@ public class MetaHolder { private final HystrixCollapser hystrixCollapser; private final HystrixCommand hystrixCommand; - private final Optional defaultProperties; + private final DefaultProperties defaultProperties; private final Method method; private final Method cacheKeyMethod; @@ -53,6 +57,7 @@ public class MetaHolder { private final String defaultGroupKey; private final String defaultCommandKey; private final String defaultCollapserKey; + private final String defaultThreadPoolKey; private final ExecutionType executionType; private final boolean extendedFallback; private final ExecutionType collapserExecutionType; @@ -75,6 +80,7 @@ private MetaHolder(Builder builder) { this.closure = builder.closure; this.defaultGroupKey = builder.defaultGroupKey; this.defaultCommandKey = builder.defaultCommandKey; + this.defaultThreadPoolKey = builder.defaultThreadPoolKey; this.defaultCollapserKey = builder.defaultCollapserKey; this.defaultProperties = builder.defaultProperties; this.hystrixCollapser = builder.hystrixCollapser; @@ -137,10 +143,27 @@ public Object[] getArgs() { return args != null ? Arrays.copyOf(args, args.length) : new Object[]{}; } + public String getCommandGroupKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.groupKey(), defaultGroupKey) : ""; + } + + @Deprecated // use getCommandGroupKey that returns default group key if command annotation doesn't specify it public String getDefaultGroupKey() { return defaultGroupKey; } + public String getCollapserKey() { + return isCollapserAnnotationPresent() ? get(hystrixCollapser.collapserKey(), defaultCollapserKey) : ""; + } + + public String getCommandKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.commandKey(), defaultCommandKey) : ""; + } + + public String getThreadPoolKey() { + return isCommandAnnotationPresent() ? get(hystrixCommand.threadPoolKey(), defaultThreadPoolKey) : ""; + } + public String getDefaultCommandKey() { return defaultCommandKey; } @@ -149,8 +172,12 @@ public String getDefaultCollapserKey() { return defaultCollapserKey; } + public boolean hasDefaultProperties() { + return defaultProperties != null; + } + public Optional getDefaultProperties() { - return defaultProperties; + return Optional.fromNullable(defaultProperties); } public Class[] getParameterTypes() { @@ -198,7 +225,18 @@ public ExecutionType getFallbackExecutionType() { } public List getCommandProperties() { - return isCommandAnnotationPresent() ? ImmutableList.copyOf(hystrixCommand.commandProperties()) : Collections.emptyList(); + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getProperties(new Supplier() { + @Override + public HystrixProperty[] get() { + return hystrixCommand.commandProperties(); + } + }, new Supplier() { + @Override + public HystrixProperty[] get() { + return hasDefaultProperties() ? defaultProperties.commandProperties() : new HystrixProperty[0]; + } + }); } public List getCollapserProperties() { @@ -206,7 +244,18 @@ public List getCollapserProperties() { } public List getThreadPoolProperties() { - return isCommandAnnotationPresent() ? ImmutableList.copyOf(hystrixCommand.threadPoolProperties()) : Collections.emptyList(); + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getProperties(new Supplier() { + @Override + public HystrixProperty[] get() { + return hystrixCommand.threadPoolProperties(); + } + }, new Supplier() { + @Override + public HystrixProperty[] get() { + return hasDefaultProperties() ? defaultProperties.threadPoolProperties() : new HystrixProperty[0]; + } + }); } public boolean isObservable() { @@ -217,13 +266,25 @@ public ObservableExecutionMode getObservableExecutionMode() { return observableExecutionMode; } + private String get(String key, String defaultKey) { + return StringUtils.isNotBlank(key) ? key : defaultKey; + } + + private List getProperties(Supplier props, Supplier defaultProps) { + HystrixProperty[] p = props.get(); + if (p.length > 0) { + return ImmutableList.copyOf(p); + } + return ImmutableList.copyOf(defaultProps.get()); + } + public static final class Builder { private static final Class[] EMPTY_ARRAY_OF_TYPES= new Class[0]; private HystrixCollapser hystrixCollapser; private HystrixCommand hystrixCommand; - private Optional defaultProperties; + private DefaultProperties defaultProperties; private Method method; private Method cacheKeyMethod; private Method fallbackMethod; @@ -235,6 +296,7 @@ public static final class Builder { private String defaultGroupKey; private String defaultCommandKey; private String defaultCollapserKey; + private String defaultThreadPoolKey; private ExecutionType executionType; private ExecutionType collapserExecutionType; private ExecutionType fallbackExecutionType; @@ -330,17 +392,17 @@ public Builder defaultCommandKey(String defCommandKey) { return this; } - public Builder defaultCollapserKey(String defCollapserKey) { - this.defaultCollapserKey = defCollapserKey; + public Builder defaultThreadPoolKey(String defaultThreadPoolKey) { + this.defaultThreadPoolKey = defaultThreadPoolKey; return this; } - public Builder defaultProperties(@Nullable DefaultProperties defaultProperties) { - this.defaultProperties = Optional.fromNullable(defaultProperties); + public Builder defaultCollapserKey(String defCollapserKey) { + this.defaultCollapserKey = defCollapserKey; return this; } - public Builder defaultProperties(Optional defaultProperties) { + public Builder defaultProperties(@Nullable DefaultProperties defaultProperties) { this.defaultProperties = defaultProperties; return this; } @@ -368,6 +430,8 @@ public Builder observableExecutionMode(ObservableExecutionMode observableExecuti public MetaHolder build() { return new MetaHolder(this); } + + } } From e902a38eb39ebef868902f026feaffeb0b1bbde6 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Wed, 29 Jun 2016 23:27:24 -0400 Subject: [PATCH 03/10] iss993: added test for default ignore exceptions --- .../command/HystrixCommandBuilder.java | 2 +- .../command/HystrixCommandBuilderFactory.java | 9 +- .../contrib/javanica/command/MetaHolder.java | 86 ++++++++++---- .../BasicDefaultIgnoreExceptionsTest.java | 109 +++++++++++++++--- .../error/DefaultIgnoreExceptionsTest.java | 10 +- 5 files changed, 165 insertions(+), 51 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java index 4f9a7e49a..c862bed5d 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java @@ -155,7 +155,7 @@ public Builder collapsedRequests(Collection[] pIgnoreExceptions) { + public Builder ignoreExceptions(List> pIgnoreExceptions) { this.ignoreExceptions = ImmutableList.copyOf(pIgnoreExceptions); return this; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java index 6f6c03b9f..c368004d4 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -15,9 +15,7 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.google.common.base.Function; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; @@ -62,12 +60,7 @@ public HystrixCommandBuilder create(MetaHolder metaHolder, Collection[]>() { - @Override - public Class[] apply(DefaultProperties input) { - return input.ignoreExceptions(); - } - }).or(metaHolder.getHystrixCommand().ignoreExceptions())) + .ignoreExceptions(metaHolder.getCommandIgnoreExceptions()) .executionType(metaHolder.getExecutionType()) .build(); } 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 a693ac25f..4ed2d0ea6 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 @@ -15,6 +15,7 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; @@ -26,7 +27,6 @@ import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; import com.netflix.hystrix.contrib.javanica.command.closure.Closure; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; import org.aspectj.lang.JoinPoint; import javax.annotation.Nullable; @@ -40,7 +40,7 @@ * Simple immutable holder to keep all necessary information about current method to build Hystrix command. */ @Immutable -public class MetaHolder { +public final class MetaHolder { private final HystrixCollapser hystrixCollapser; private final HystrixCommand hystrixCommand; @@ -68,6 +68,14 @@ public class MetaHolder { private final boolean observable; private final ObservableExecutionMode observableExecutionMode; + private static final Function identityFun = new Function() { + @Nullable + @Override + public Object apply(@Nullable Object input) { + return input; + } + }; + private MetaHolder(Builder builder) { this.hystrixCommand = builder.hystrixCommand; this.method = builder.method; @@ -220,23 +228,43 @@ public boolean isExtendedFallback() { return extendedFallback; } + @SuppressWarnings("unchecked") + public List> getCommandIgnoreExceptions() { + if (!isCommandAnnotationPresent()) return Collections.emptyList(); + return getOrDefault(new Supplier>>() { + @Override + public List> get() { + return ImmutableList.>copyOf(hystrixCommand.ignoreExceptions()); + } + }, new Supplier>>() { + @Override + public List> get() { + return hasDefaultProperties() + ? ImmutableList.>copyOf(defaultProperties.ignoreExceptions()) + : Collections.>emptyList(); + } + }, this.>nonEmptyList()); + } + public ExecutionType getFallbackExecutionType() { return fallbackExecutionType; } public List getCommandProperties() { if (!isCommandAnnotationPresent()) return Collections.emptyList(); - return getProperties(new Supplier() { + return getOrDefault(new Supplier>() { @Override - public HystrixProperty[] get() { - return hystrixCommand.commandProperties(); + public List get() { + return ImmutableList.copyOf(hystrixCommand.commandProperties()); } - }, new Supplier() { + }, new Supplier>() { @Override - public HystrixProperty[] get() { - return hasDefaultProperties() ? defaultProperties.commandProperties() : new HystrixProperty[0]; + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.commandProperties()) + : Collections.emptyList(); } - }); + }, this.nonEmptyList()); } public List getCollapserProperties() { @@ -245,17 +273,19 @@ public List getCollapserProperties() { public List getThreadPoolProperties() { if (!isCommandAnnotationPresent()) return Collections.emptyList(); - return getProperties(new Supplier() { + return getOrDefault(new Supplier>() { @Override - public HystrixProperty[] get() { - return hystrixCommand.threadPoolProperties(); + public List get() { + return ImmutableList.copyOf(hystrixCommand.threadPoolProperties()); } - }, new Supplier() { + }, new Supplier>() { @Override - public HystrixProperty[] get() { - return hasDefaultProperties() ? defaultProperties.threadPoolProperties() : new HystrixProperty[0]; + public List get() { + return hasDefaultProperties() + ? ImmutableList.copyOf(defaultProperties.threadPoolProperties()) + : Collections.emptyList(); } - }); + }, this.nonEmptyList()); } public boolean isObservable() { @@ -270,12 +300,26 @@ private String get(String key, String defaultKey) { return StringUtils.isNotBlank(key) ? key : defaultKey; } - private List getProperties(Supplier props, Supplier defaultProps) { - HystrixProperty[] p = props.get(); - if (p.length > 0) { - return ImmutableList.copyOf(p); + private Predicate> nonEmptyList() { + return new Predicate>() { + @Override + public boolean apply(@Nullable List input) { + return input != null && !input.isEmpty(); + } + }; + } + + @SuppressWarnings("unchecked") + private T getOrDefault(Supplier source, Supplier defaultChoice, Predicate isDefined) { + return getOrDefault(source, defaultChoice, isDefined, (Function) identityFun); + } + + private T getOrDefault(Supplier source, Supplier defaultChoice, Predicate isDefined, Function map) { + T res = source.get(); + if (!isDefined.apply(res)) { + res = defaultChoice.get(); } - return ImmutableList.copyOf(defaultProps.get()); + return map.apply(res); } public static final class Builder { diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java index f36304793..d02286914 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java @@ -2,45 +2,122 @@ import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.exception.HystrixRuntimeException; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; /** - * todo [dmgcodevil]: add docs + * Test for {@link DefaultProperties#ignoreExceptions()} feature. + * *

* Created by dmgcodevil. */ public abstract class BasicDefaultIgnoreExceptionsTest { - private UserService userService; + private Service service; @Before public void setUp() throws Exception { - userService = createUserService(); + service = createService(); } - protected abstract UserService createUserService(); + protected abstract Service createService(); - @Test(expected = IllegalArgumentException.class) + @Test(expected = BadRequestException.class) public void testDefaultIgnoreException() { - userService.getById(""); + service.commandInheritsDefaultIgnoreExceptions(); } - @DefaultProperties(ignoreExceptions = IllegalArgumentException.class) - public static class UserService { + @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) + 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 - public User getById(String id) throws IllegalArgumentException { - if (id == null || "".equals(id)) { - throw new IllegalArgumentException("bad id"); + 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()); } - return new User(id); + // something went wrong, this error is ignored because specified in the fallback's ignoreExceptions + throw new SpecificException("from 'commandOverridesDefaultIgnoreExceptions', cause: " + errorType.getSimpleName()); } } - public static class User { - private String id; + 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 User(String id) { - this.id = id; + public SpecificException(String message) { + super(message); } } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java index c9f4be858..c57677057 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/DefaultIgnoreExceptionsTest.java @@ -18,19 +18,19 @@ public class DefaultIgnoreExceptionsTest extends BasicDefaultIgnoreExceptionsTes @Autowired - private BasicDefaultIgnoreExceptionsTest.UserService userService; + private BasicDefaultIgnoreExceptionsTest.Service service; @Override - protected BasicDefaultIgnoreExceptionsTest.UserService createUserService() { - return userService; + protected BasicDefaultIgnoreExceptionsTest.Service createService() { + return service; } @Configurable public static class DefaultIgnoreExceptionsTestConfig { @Bean - public BasicDefaultIgnoreExceptionsTest.UserService userService() { - return new BasicDefaultIgnoreExceptionsTest.UserService(); + public BasicDefaultIgnoreExceptionsTest.Service userService() { + return new BasicDefaultIgnoreExceptionsTest.Service(); } } } From e87b4e6badc28c97da0cf4a76cf306f2b00eb1df Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Wed, 29 Jun 2016 23:46:55 -0400 Subject: [PATCH 04/10] iss993: added test for default group and thread pool key --- .../BasicCommandDefaultPropertiesTest.java | 82 +++++++++++++++++++ .../command/CommandDefaultPropertiesTest.java | 36 ++++++++ .../command/CommandPropertiesTest.java | 16 ---- 3 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java new file mode 100644 index 000000000..0dc598c6b --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java @@ -0,0 +1,82 @@ +package com.netflix.hystrix.contrib.javanica.test.common.configuration.command; + +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by dmgcodevil. + */ +public abstract class BasicCommandDefaultPropertiesTest extends BasicHystrixTest { + + private Service service; + + protected abstract Service createService(); + + @Before + public void setUp() throws Exception { + service = createService(); + } + + @Test + public void testCommandInheritsDefaultGroupKey() { + service.commandInheritsGroupKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("DefaultGroupKey", command.getCommandGroup().name()); + } + + @Test + public void testCommandOverridesDefaultGroupKey() { + service.commandOverridesGroupKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("SpecificGroupKey", command.getCommandGroup().name()); + } + + @Test + public void testCommandInheritsDefaultThreadPoolKey() { + service.commandInheritsThreadPoolKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("DefaultThreadPoolKey", command.getThreadPoolKey().name()); + } + + @Test + public void testCommandOverridesDefaultThreadPoolKey() { + service.commandOverridesThreadPoolKey(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals("SpecificThreadPoolKey", command.getThreadPoolKey().name()); + } + + @DefaultProperties(groupKey = "DefaultGroupKey", threadPoolKey = "DefaultThreadPoolKey") + public static class Service { + + @HystrixCommand + public Object commandInheritsGroupKey() { + return null; + } + + @HystrixCommand(groupKey = "SpecificGroupKey") + public Object commandOverridesGroupKey() { + return null; + } + + @HystrixCommand + public Object commandInheritsThreadPoolKey() { + return null; + } + + @HystrixCommand(threadPoolKey = "SpecificThreadPoolKey") + public Object commandOverridesThreadPoolKey() { + return null; + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java new file mode 100644 index 000000000..1b0d8ac3e --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandDefaultPropertiesTest.java @@ -0,0 +1,36 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.command; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.command.BasicCommandDefaultPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +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.context.annotation.Scope; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Created by dmgcodevil. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandDefaultPropertiesTest.Config.class}) +public class CommandDefaultPropertiesTest extends BasicCommandDefaultPropertiesTest { + + @Autowired + private Service service; + + @Override + protected Service createService() { + return service; + } + + @Configurable + public static class Config { + @Bean + @Scope(value = "prototype") + public BasicCommandDefaultPropertiesTest.Service service() { + return new BasicCommandDefaultPropertiesTest.Service(); + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java index 98bfa9fac..55a8b7430 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java @@ -15,19 +15,8 @@ */ package com.netflix.hystrix.contrib.javanica.test.spring.configuration.command; -import com.netflix.hystrix.HystrixEventType; -import com.netflix.hystrix.HystrixInvokableInfo; -import com.netflix.hystrix.HystrixRequestLog; -import com.netflix.hystrix.HystrixThreadPool; -import com.netflix.hystrix.HystrixThreadPoolProperties; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.test.common.configuration.command.BasicCommandPropertiesTest; import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; -import com.netflix.hystrix.contrib.javanica.test.common.domain.User; -import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; - -import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; @@ -35,11 +24,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.lang.reflect.Field; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {AopCglibConfig.class, CommandPropertiesTest.CommandPropertiesTestConfig.class}) public class CommandPropertiesTest extends BasicCommandPropertiesTest { From 584f3622fccb9ea7aedabe2c402e19c208dda0d6 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Thu, 30 Jun 2016 10:56:34 -0400 Subject: [PATCH 05/10] iss993: added tests for default command properties --- .../test/common/BasicHystrixTest.java | 23 ++++++ .../BasicCommandDefaultPropertiesTest.java | 73 +++++++++++++++++-- .../command/BasicCommandPropertiesTest.java | 11 +-- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java index d1dbfa35b..45f4e9746 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/BasicHystrixTest.java @@ -15,10 +15,16 @@ */ package com.netflix.hystrix.contrib.javanica.test.common; +import com.google.common.base.Throwables; import com.hystrix.junit.HystrixRequestContextRule; +import com.netflix.hystrix.HystrixInvokableInfo; +import com.netflix.hystrix.HystrixThreadPool; +import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import org.junit.Rule; +import java.lang.reflect.Field; + /** * Created by dmgcodevil */ @@ -33,4 +39,21 @@ protected final HystrixRequestContext getHystrixContext() { protected void resetContext() { request.reset(); } + + protected final HystrixThreadPoolProperties getThreadPoolProperties(HystrixInvokableInfo command) { + try { + Field field = command.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("threadPool"); + field.setAccessible(true); + HystrixThreadPool threadPool = (HystrixThreadPool) field.get(command); + + Field field2 = HystrixThreadPool.HystrixThreadPoolDefault.class.getDeclaredField("properties"); + field2.setAccessible(true); + return (HystrixThreadPoolProperties) field2.get(threadPool); + + } catch (NoSuchFieldException e) { + throw Throwables.propagate(e); + } catch (IllegalAccessException e) { + throw Throwables.propagate(e); + } + } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java index 0dc598c6b..cdf815e38 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandDefaultPropertiesTest.java @@ -2,12 +2,15 @@ import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; import org.junit.Before; import org.junit.Test; +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS; import static org.junit.Assert.assertEquals; /** @@ -26,7 +29,7 @@ public void setUp() throws Exception { @Test public void testCommandInheritsDefaultGroupKey() { - service.commandInheritsGroupKey(); + service.commandInheritsDefaultProperties(); HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); assertEquals("DefaultGroupKey", command.getCommandGroup().name()); @@ -42,7 +45,7 @@ public void testCommandOverridesDefaultGroupKey() { @Test public void testCommandInheritsDefaultThreadPoolKey() { - service.commandInheritsThreadPoolKey(); + service.commandInheritsDefaultProperties(); HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); assertEquals("DefaultThreadPoolKey", command.getThreadPoolKey().name()); @@ -56,11 +59,56 @@ public void testCommandOverridesDefaultThreadPoolKey() { assertEquals("SpecificThreadPoolKey", command.getThreadPoolKey().name()); } - @DefaultProperties(groupKey = "DefaultGroupKey", threadPoolKey = "DefaultThreadPoolKey") + @Test + public void testCommandInheritsDefaultCommandProperties() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals(456, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultCommandProperties() { + service.commandOverridesDefaultCommandProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + assertEquals(654, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testCommandInheritsThreadPollProperties() { + service.commandInheritsDefaultProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); + + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultThreadPollProperties() { + service.commandOverridesDefaultThreadPoolProperties(); + HystrixInvokableInfo command = HystrixRequestLog.getCurrentRequest() + .getAllExecutedCommands().iterator().next(); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); + + assertEquals(321, properties.maxQueueSize().get().intValue()); + } + + @DefaultProperties(groupKey = "DefaultGroupKey", threadPoolKey = "DefaultThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "456") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "123") + } + ) public static class Service { @HystrixCommand - public Object commandInheritsGroupKey() { + public Object commandInheritsDefaultProperties() { return null; } @@ -69,13 +117,22 @@ public Object commandOverridesGroupKey() { return null; } - @HystrixCommand - public Object commandInheritsThreadPoolKey() { + @HystrixCommand(threadPoolKey = "SpecificThreadPoolKey") + public Object commandOverridesThreadPoolKey() { return null; } - @HystrixCommand(threadPoolKey = "SpecificThreadPoolKey") - public Object commandOverridesThreadPoolKey() { + @HystrixCommand(commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }) + public Object commandOverridesDefaultCommandProperties() { + return null; + } + + @HystrixCommand(threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }) + public Object commandOverridesDefaultThreadPoolProperties() { return null; } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java index b4792ff3b..572657a57 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java @@ -19,7 +19,6 @@ import com.netflix.hystrix.HystrixEventType; import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.HystrixRequestLog; -import com.netflix.hystrix.HystrixThreadPool; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; @@ -28,8 +27,6 @@ import org.junit.Before; import org.junit.Test; -import java.lang.reflect.Field; - import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED; import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE; import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.CIRCUIT_BREAKER_FORCE_CLOSED; @@ -84,13 +81,7 @@ public void testGetUser() throws NoSuchFieldException, IllegalAccessException { assertEquals(110, command.getProperties().executionTimeoutInMilliseconds().get().intValue()); assertEquals(false, command.getProperties().executionIsolationThreadInterruptOnTimeout().get()); - Field field = command.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("threadPool"); - field.setAccessible(true); - HystrixThreadPool threadPool = (HystrixThreadPool) field.get(command); - - Field field2 = HystrixThreadPool.HystrixThreadPoolDefault.class.getDeclaredField("properties"); - field2.setAccessible(true); - HystrixThreadPoolProperties properties = (HystrixThreadPoolProperties) field2.get(threadPool); + HystrixThreadPoolProperties properties = getThreadPoolProperties(command); assertEquals(30, (int) properties.coreSize().get()); assertEquals(101, (int) properties.maxQueueSize().get()); From c226fdda6fe5aacb086eb9767418bacb28638831 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Fri, 1 Jul 2016 22:21:27 -0400 Subject: [PATCH 06/10] iss993: implemented support for default properties for fallback commands --- .../annotation/DefaultProperties.java | 4 +- .../command/HystrixCommandBuilderFactory.java | 2 + .../command/LazyCommandExecutionAction.java | 4 + .../contrib/javanica/command/MetaHolder.java | 5 +- .../BasicFallbackDefaultPropertiesTest.java | 149 ++++++++++++++++++ .../FallbackDefaultPropertiesTest.java | 31 ++++ 6 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java 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 d331ffbe9..813696f7f 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 @@ -35,7 +35,7 @@ public @interface DefaultProperties { /** - * Specifies default group key used for each hystrix command by default unless a command specifies it's own group key. + * Specifies default group key used for each hystrix command by default unless a command specifies group key explicitly. * For additional info about this property see {@link HystrixCommand#groupKey()}. * * @return default group key @@ -43,7 +43,7 @@ String groupKey() default ""; /** - * Specifies default thread pool key used for each hystrix command by default unless a command specifies it's own thread pool key. + * Specifies default thread pool key used for each hystrix command by default unless a command specifies thread pool key explicitly. * For additional info about this property see {@link HystrixCommand#threadPoolKey()} * * @return default thread pool diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java index c368004d4..c334fe73b 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -122,6 +122,8 @@ private CommandAction createFallbackAction(MetaHolder metaHolder) { .observable(ExecutionType.OBSERVABLE == fallbackMethod.getExecutionType()) .defaultCommandKey(fMethod.getName()) .defaultGroupKey(metaHolder.getDefaultGroupKey()) + .defaultThreadPoolKey(metaHolder.getDefaultThreadPoolKey()) + .defaultProperties(metaHolder.getDefaultProperties().orNull()) .hystrixCollapser(metaHolder.getHystrixCollapser()) .observableExecutionMode(hystrixCommand.observableExecutionMode()) .hystrixCommand(hystrixCommand).build(); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java index 893f082a4..20cd874e0 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java @@ -81,6 +81,8 @@ private MetaHolder createCopy(MetaHolder source, ExecutionType executionType) { .defaultCollapserKey(source.getDefaultCollapserKey()) .defaultCommandKey(source.getDefaultCommandKey()) .defaultGroupKey(source.getDefaultGroupKey()) + .defaultThreadPoolKey(source.getDefaultThreadPoolKey()) + .defaultProperties(source.getDefaultProperties().orNull()) .hystrixCollapser(source.getHystrixCollapser()) .hystrixCommand(source.getHystrixCommand()).build(); } @@ -100,6 +102,8 @@ private MetaHolder createCopy(MetaHolder source, ExecutionType executionType, Ob .defaultCollapserKey(source.getDefaultCollapserKey()) .defaultCommandKey(source.getDefaultCommandKey()) .defaultGroupKey(source.getDefaultGroupKey()) + .defaultThreadPoolKey(source.getDefaultThreadPoolKey()) + .defaultProperties(source.getDefaultProperties().orNull()) .hystrixCollapser(source.getHystrixCollapser()) .hystrixCommand(source.getHystrixCommand()).build(); } 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 4ed2d0ea6..1624d1436 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 @@ -155,11 +155,14 @@ public String getCommandGroupKey() { return isCommandAnnotationPresent() ? get(hystrixCommand.groupKey(), defaultGroupKey) : ""; } - @Deprecated // use getCommandGroupKey that returns default group key if command annotation doesn't specify it public String getDefaultGroupKey() { return defaultGroupKey; } + public String getDefaultThreadPoolKey() { + return defaultThreadPoolKey; + } + public String getCollapserKey() { return isCollapserAnnotationPresent() ? get(hystrixCollapser.collapserKey(), defaultCollapserKey) : ""; } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java new file mode 100644 index 000000000..9f96a7e64 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/fallback/BasicFallbackDefaultPropertiesTest.java @@ -0,0 +1,149 @@ +package com.netflix.hystrix.contrib.javanica.test.common.configuration.fallback; + +import com.netflix.hystrix.HystrixThreadPoolProperties; +import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; +import org.junit.Before; +import org.junit.Test; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS; +import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; + + +public abstract class BasicFallbackDefaultPropertiesTest extends BasicHystrixTest { + + private Service service; + + protected abstract Service createService(); + + @Before + public void setUp() throws Exception { + service = createService(); + } + + @Test + public void testFallbackInheritsDefaultGroupKey() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals("DefaultGroupKey", fallbackCommand.getCommandGroup().name()); + } + + @Test + public void testFallbackInheritsDefaultThreadPoolKey() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals("DefaultThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + } + + @Test + public void testFallbackInheritsDefaultCommandProperties() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + assertEquals(456, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testFallbackInheritsThreadPollProperties() { + service.commandWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testFallbackOverridesDefaultGroupKey() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals("FallbackGroupKey", fallbackCommand.getCommandGroup().name()); + } + + @Test + public void testFallbackOverridesDefaultThreadPoolKey() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals("FallbackThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + } + + @Test + public void testFallbackOverridesDefaultCommandProperties() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + assertEquals(654, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + } + + @Test + public void testFallbackOverridesThreadPollProperties() { + service.commandWithFallbackOverridesDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackOverridesDefaultProperties"); + + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + + assertEquals(321, properties.maxQueueSize().get().intValue()); + } + + @Test + public void testCommandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties(){ + service.commandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties(); + com.netflix.hystrix.HystrixInvokableInfo fallbackCommand = getHystrixCommandByKey("fallbackInheritsDefaultProperties"); + HystrixThreadPoolProperties properties = getThreadPoolProperties(fallbackCommand); + assertEquals("DefaultGroupKey", fallbackCommand.getCommandGroup().name()); + assertEquals("DefaultThreadPoolKey", fallbackCommand.getThreadPoolKey().name()); + assertEquals(456, fallbackCommand.getProperties().executionTimeoutInMilliseconds().get().intValue()); + assertEquals(123, properties.maxQueueSize().get().intValue()); + } + + @DefaultProperties(groupKey = "DefaultGroupKey", + threadPoolKey = "DefaultThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "456") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "123") + }) + public static class Service { + + @HystrixCommand(fallbackMethod = "fallbackInheritsDefaultProperties") + public Object commandWithFallbackInheritsDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand(fallbackMethod = "fallbackOverridesDefaultProperties") + public Object commandWithFallbackOverridesDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand(groupKey = "CommandGroupKey", + threadPoolKey = "CommandThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }, fallbackMethod = "fallbackInheritsDefaultProperties") + public Object commandOverridesDefaultPropertiesWithFallbackInheritsDefaultProperties() { + throw new RuntimeException(); + } + + @HystrixCommand + private Object fallbackInheritsDefaultProperties() { + return null; + } + + @HystrixCommand(groupKey = "FallbackGroupKey", + threadPoolKey = "FallbackThreadPoolKey", + commandProperties = { + @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "654") + }, + threadPoolProperties = { + @HystrixProperty(name = "maxQueueSize", value = "321") + }) + private Object fallbackOverridesDefaultProperties() { + return null; + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java new file mode 100644 index 000000000..b7680b7a4 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/fallback/FallbackDefaultPropertiesTest.java @@ -0,0 +1,31 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.fallback; + +import com.netflix.hystrix.contrib.javanica.test.common.configuration.fallback.BasicFallbackDefaultPropertiesTest; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +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; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, FallbackDefaultPropertiesTest.Config.class}) +public class FallbackDefaultPropertiesTest extends BasicFallbackDefaultPropertiesTest { + + @Autowired + private Service service; + + @Override + protected Service createService() { + return service; + } + + @Configurable + public static class Config { + @Bean + public BasicFallbackDefaultPropertiesTest.Service service() { + return new BasicFallbackDefaultPropertiesTest.Service(); + } + } +} From 39d07aa4b6c2240c78145e66a338bdff7be9a704 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Fri, 1 Jul 2016 23:17:52 -0400 Subject: [PATCH 07/10] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index c35eeae06..d52678744 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -1,5 +1,7 @@ # hystrix-javanica +**Could you please spend 5 sec and answer [questionnaire](https://docs.google.com/forms/d/1NEeWxtL_PleX0H9GqTvKxxHqwUryJ9L048j8D3T45fs/viewform). Thank you !** + Java language has a great advantages over other languages such as reflection and annotations. All modern frameworks such as Spring, Hibernate, myBatis and etc. seek to use this advantages to the maximum. The idea of introduction annotations in Hystrix is obvious solution for improvement. Currently using Hystrix involves writing a lot of code that is a barrier to rapid development. You likely be spending a lot of time on writing a Hystrix commands. Idea of the Javanica project is make easier using of Hystrix by the introduction of support annotations. From 155d4e97a2d019238c939fd074feb9443e09ab68 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Fri, 1 Jul 2016 23:18:40 -0400 Subject: [PATCH 08/10] iss993: propagate original exception instead of HystrixRuntimeException --- .../javanica/aop/aspectj/HystrixCommandAspect.java | 13 +++++++++++++ .../javanica/command/AbstractHystrixCommand.java | 7 +++++-- .../error/BasicDefaultIgnoreExceptionsTest.java | 4 ++-- .../common/error/BasicErrorPropagationTest.java | 12 +++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) 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 496592577..271012ab5 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 @@ -26,10 +26,12 @@ import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import com.netflix.hystrix.contrib.javanica.utils.AopUtils; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.exception.HystrixRuntimeException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.aspectj.lang.JoinPoint; @@ -93,10 +95,21 @@ public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinP result = CommandExecutor.execute(invokable, executionType, metaHolder); } catch (HystrixBadRequestException e) { throw e.getCause(); + } catch (HystrixRuntimeException e) { + throw getCauseOrDefault(e, e); } return result; } + private Throwable getCauseOrDefault(RuntimeException e, RuntimeException defaultException) { + if (e.getCause() == null) return defaultException; + if (e.getCause() instanceof CommandActionExecutionException) { + CommandActionExecutionException commandActionExecutionException = (CommandActionExecutionException) e.getCause(); + return Optional.fromNullable(commandActionExecutionException.getCause()).or(defaultException); + } + return e.getCause(); + } + /** * A factory to create MetaHolder depending on {@link HystrixPointcutType}. */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java index eb014f1f4..6568cf778 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -150,10 +150,13 @@ Object process(Action action) throws Exception { if (isIgnorable(cause)) { throw new HystrixBadRequestException(cause.getMessage(), cause); } - if (cause instanceof Exception) { + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Exception) { throw (Exception) cause; } else { - throw Throwables.propagate(cause); + // instance of Throwable + throw new CommandActionExecutionException(cause); } } return result; diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java index d02286914..3347c1b4d 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicDefaultIgnoreExceptionsTest.java @@ -33,7 +33,7 @@ public void testCommandOverridesDefaultIgnoreExceptions() { service.commandOverridesDefaultIgnoreExceptions(SpecificException.class); } - @Test(expected = HystrixRuntimeException.class) + @Test(expected = BadRequestException.class) public void testCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { // method throws BadRequestException that isn't ignored service.commandOverridesDefaultIgnoreExceptions(BadRequestException.class); @@ -51,7 +51,7 @@ public void testFallbackCommandOverridesDefaultIgnoreExceptions() { service.commandWithFallbackOverridesDefaultIgnoreExceptions(SpecificException.class); } - @Test(expected = HystrixRuntimeException.class) + @Test(expected = SpecificException.class) public void testFallbackCommandOverridesDefaultIgnoreExceptions_nonIgnoreExceptionShouldBePropagated() { service.commandWithFallbackOverridesDefaultIgnoreExceptions(BadRequestException.class); } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java index beba54c38..8f5941268 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/error/BasicErrorPropagationTest.java @@ -110,7 +110,7 @@ public void testActivateUser() throws NotFoundException, ActivationException { } } - @Test(expected = HystrixRuntimeException.class) + @Test(expected = OperationException.class) public void testBlockUser() throws NotFoundException, ActivationException, OperationException { try { userService.blockUser("1"); // this method always throws ActivationException @@ -123,6 +123,11 @@ public void testBlockUser() throws NotFoundException, ActivationException, Opera } } + @Test(expected = NotFoundException.class) + public void testPropagateCauseException() throws NotFoundException { + userService.deleteUser(""); + } + public static class UserService { private FailoverService failoverService; @@ -131,6 +136,11 @@ public void setFailoverService(FailoverService failoverService) { this.failoverService = failoverService; } + @HystrixCommand + public Object deleteUser(String id) throws NotFoundException { + throw new NotFoundException(""); + } + @HystrixCommand( commandKey = COMMAND_KEY, ignoreExceptions = { From a43e231ac2a015933ab94852e8e51d5e388ff709 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 2 Jul 2016 19:08:46 -0400 Subject: [PATCH 09/10] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 45 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index d52678744..5df468f50 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -326,7 +326,32 @@ Based on [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagat } ``` -If `userResource.getUserById(id);` throws an exception which type is _BadRequestException_ then this exception will be thrown without triggering fallback logic. +If `userResource.getUserById(id);` throws an exception that type is _BadRequestException_ then this exception will be wrapped in ``HystrixBadRequestException`` and re-thrown without triggering fallback logic. You don't need to do it manually, javanica will do it for you under the hood. It is worth noting that a caller will get root cause exception, i.e. user ``BadRequestException``. A caller always gets root cause exception, never ``HystrixBadRequestException`` or ``HystrixRuntimeException`` except the case when executed code explicitly throws those exceptions. + +*Note*: If command has a fallback then only first exception that trigers fallback logic will be propagated to caller. Example: + +```java +class Service { + @HystrixCommand(fallbackMethod = "fallback") + Object command(Object o) throws CommandException { + throw new CommandException(); + } + + @HystrixCommand + Object fallback(Object o) throws FallbackException { + throw new FallbackException(); + } +} + +// in client code +{ + try { + service.command(null); + } catch (Exception e) { + assert CommandException.class.equals(e.getClass()) + } +} +``` ## Request Cache @@ -531,6 +556,24 @@ ThreadPoolProperties can be set using @HystrixCommand's 'threadPoolProperties' l } ``` +### DefaultProperties +``@DefaultProperties`` is class (type) level annotation that allows to default commands properties such as ``groupKey``, ``threadPoolKey``, ``commandProperties``, ``threadPoolProperties`` and ``ignoreExceptions``. Properties specified using this annotation will be used by default for each hystrix command defined within annotated class unless a command specifies those properties explicitly using corresponding ``@HystrixCommand`` parameters. +Example: + +```java +@DefaultProperties(groupKey = "DefaultGroupKey") +class Service { + @HystrixCommand // hystrix command group key is 'DefaultGroupKey' + public Object commandInheritsDefaultProperties() { + return null; + } + @HystrixCommand(groupKey = "SpecificGroupKey") // command overrides default group key + public Object commandOverridesGroupKey() { + return null; + } +} +``` + ## Hystrix collapser Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use ```@HystrixCollapser``` annotation. From 584deb0e3a667375fef3072abe0f8fbef290a15d Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 2 Jul 2016 19:34:03 -0400 Subject: [PATCH 10/10] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 5df468f50..37392b5a1 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -1,6 +1,6 @@ # hystrix-javanica -**Could you please spend 5 sec and answer [questionnaire](https://docs.google.com/forms/d/1NEeWxtL_PleX0H9GqTvKxxHqwUryJ9L048j8D3T45fs/viewform). Thank you !** +**Could you please spend 5 sec and answer the [questionnaire](https://docs.google.com/forms/d/1NEeWxtL_PleX0H9GqTvKxxHqwUryJ9L048j8D3T45fs/viewform). Thank you !** Java language has a great advantages over other languages such as reflection and annotations. All modern frameworks such as Spring, Hibernate, myBatis and etc. seek to use this advantages to the maximum. @@ -709,4 +709,4 @@ Please create an issue if you need a feature or you detected some bugs. Thanks **Note**: Javanica 1.4.+ is updated more frequently than 1.3.+ hence 1.4+ is more stable. -**It's recommended to use Javaniva 1.4.+** +**It's recommended to use Javanica 1.4.+**