diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 0f305c8b7..bd2c7ffcb 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -105,22 +105,35 @@ The return type of command method should be Future that indicates that a command ## Reactive Execution -To performe "Reactive Execution" you should return an instance of `ObservableResult` in your command method as in the exapmple below: +To performe "Reactive Execution" you should return an instance of `Observable` in your command method as in the exapmple below: ```java @HystrixCommand public Observable getUserById(final String id) { - return new ObservableResult() { - @Override - public User invoke() { - return userResource.getUserById(id); - } - }; + return Observable.create(new Observable.OnSubscribe() { + @Override + public void call(Subscriber observer) { + try { + if (!observer.isUnsubscribed()) { + observer.onNext(new User(id, name + id)); + observer.onCompleted(); + } + } catch (Exception e) { + observer.onError(e); + } + } + }); } ``` The return type of command method should be `Observable`. +HystrixObservable interface provides two methods: ```observe()``` - eagerly starts execution of the command the same as ``` HystrixCommand#queue()``` and ```HystrixCommand#execute()```; ```toObservable()``` - lazily starts execution of the command only once the Observable is subscribed to. To control this behaviour and swith between two modes ```@HystrixCommand``` provides specific parameter called ```observableExecutionMode```. +```@HystrixCommand(observableExecutionMode = EAGER)``` indicates that ```observe()``` method should be used to execute observable command +```@HystrixCommand(observableExecutionMode = LAZY)``` indicates that ```toObservable()``` should be used to execute observable command + +**NOTE: EAGER mode is used by default** + ## Fallback Graceful degradation can be achieved by declaring name of fallback method in `@HystrixCommand` like below: @@ -218,13 +231,13 @@ A fallback can be async or sync, at certain cases it depends on command executio case 1: sync command, sync fallback ```java - @HystrixCommand(fallbackMethod = "fallbackAsync") + @HystrixCommand(fallbackMethod = "fallback") User getUserById(String id) { throw new RuntimeException("getUserById command failed"); } @HystrixCommand - User fallbackAsync(String id) { + User fallback(String id) { return new User("def", "def"); } ``` @@ -232,13 +245,13 @@ case 1: sync command, sync fallback case 2: async command, sync fallback ```java - @HystrixCommand(fallbackMethod = "fallbackAsync") + @HystrixCommand(fallbackMethod = "fallback") Future getUserById(String id) { throw new RuntimeException("getUserById command failed"); } @HystrixCommand - User fallbackAsync(String id) { + User fallback(String id) { return new User("def", "def"); } ``` @@ -263,7 +276,7 @@ case 3: async command, async fallback **Unsupported(prohibited)** -case 1: sync command, async fallback. This case isn't supported because in the essence a caller does not get a future buy calling ```getUserById``` command and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete ```fallbackAsync``` before a caller gets a result, having said it turns out there is no benefits of async fallback execution. Such case makes async fallback useless. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case. +case 1: sync command, async fallback command. This case isn't supported because in the essence a caller does not get a future buy calling ```getUserById``` and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete ```fallbackAsync``` before a caller gets a result, having said it turns out there is no benefits of async fallback execution. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case. ```java @HystrixCommand(fallbackMethod = "fallbackAsync") @@ -281,8 +294,25 @@ case 1: sync command, async fallback. This case isn't supported because in the e }; } ``` +case 2: sync command, async fallback. This case isn't supported for the same reason as for the case 1. + +```java + @HystrixCommand(fallbackMethod = "fallbackAsync") + User getUserById(String id) { + throw new RuntimeException("getUserById command failed"); + } + Future fallbackAsync(String id) { + return new AsyncResult() { + @Override + public User invoke() { + return new User("def", "def"); + } + }; + } +``` +Same restrictions are imposed on using observable feature in javanica. ## Error Propagation Based on [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation) description, `@HystrixCommand` has an ability to specify exceptions types which should be ignored and wrapped to throw in `HystrixBadRequestException`. diff --git a/hystrix-contrib/hystrix-javanica/build.gradle b/hystrix-contrib/hystrix-javanica/build.gradle index f6e0eb319..e27ba0380 100644 --- a/hystrix-contrib/hystrix-javanica/build.gradle +++ b/hystrix-contrib/hystrix-javanica/build.gradle @@ -113,4 +113,5 @@ dependencies { testCompile 'org.mockito:mockito-all:1.9.5' testCompile 'log4j:log4j:1.2.17' testCompile 'org.slf4j:slf4j-log4j12:1.7.7' + testCompile 'com.tngtech.java:junit-dataprovider:1.10.2' } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java index f045d8ed6..fcc7f765a 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java @@ -105,5 +105,13 @@ * @return exceptions to ignore */ Class[] ignoreExceptions() default {}; + + /** + * Specifies the mode that should be used to execute hystrix observable command. + * For more information see {@link ObservableExecutionMode}. + * + * @return observable execution mode + */ + ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java new file mode 100644 index 000000000..a8dad2657 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/ObservableExecutionMode.java @@ -0,0 +1,42 @@ +/** + * Copyright 2015 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 com.netflix.hystrix.HystrixObservable; +import rx.Observable; + +/** + * Hystrix observable command can be executed in two different ways: + * eager - {@link HystrixObservable#observe()}, + * lazy - {@link HystrixObservable#toObservable()}. + *

+ * This enum is used to specify desire execution mode. + *

+ * Created by dmgcodevil. + */ +public enum ObservableExecutionMode { + + /** + * This mode lazily starts execution of the command only once the {@link Observable} is subscribed to. + */ + LAZY, + + /** + * This mode eagerly starts execution of the command the same as {@link com.netflix.hystrix.HystrixCommand#queue()} + * and {@link com.netflix.hystrix.HystrixCommand#execute()}. + */ + EAGER +} 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 b9ba3ee95..b74159ec6 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 @@ -18,12 +18,12 @@ 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.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.collapser.CommandCollapser; import com.netflix.hystrix.contrib.javanica.command.CommandExecutor; import com.netflix.hystrix.contrib.javanica.command.ExecutionType; -import com.netflix.hystrix.contrib.javanica.command.GenericHystrixCommandFactory; +import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; @@ -43,7 +43,6 @@ import java.util.Map; import java.util.concurrent.Future; - import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod; import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving; @@ -83,17 +82,12 @@ public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinP } MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method)); MetaHolder metaHolder = metaHolderFactory.create(joinPoint); - HystrixExecutable executable; - ExecutionType executionType = metaHolder.isCollapser() ? + HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder); + ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ? metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType(); - if (metaHolder.isCollapser()) { - executable = new CommandCollapser(metaHolder); - } else { - executable = GenericHystrixCommandFactory.getInstance().create(metaHolder, null); - } Object result; try { - result = CommandExecutor.execute(executable, executionType, metaHolder); + result = CommandExecutor.execute(invokable, executionType, metaHolder); } catch (HystrixBadRequestException e) { throw e.getCause(); } @@ -210,15 +204,18 @@ private static class CommandMetaHolderFactory extends MetaHolderFactory { @Override public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) { HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); + ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint); - builder.defaultCommandKey(method.getName()); - builder.hystrixCommand(hystrixCommand); - builder.executionType(ExecutionType.getExecutionType(method.getReturnType())); - return builder.build(); + return builder.defaultCommandKey(method.getName()) + .hystrixCommand(hystrixCommand) + .observableExecutionMode(hystrixCommand.observableExecutionMode()) + .executionType(executionType) + .observable(ExecutionType.OBSERVABLE == executionType) + .build(); } } - private static enum HystrixPointcutType { + private enum HystrixPointcutType { COMMAND, COLLAPSER; diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java index 37a518feb..4e3ab1a46 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CommandCollapser.java @@ -16,18 +16,14 @@ package com.netflix.hystrix.contrib.javanica.collapser; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.HystrixCollapserKey; -import com.netflix.hystrix.HystrixCollapserProperties; import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; -import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommandFactory; +import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.HystrixCommandBuilderFactory; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; -import org.apache.commons.lang3.StringUtils; import java.util.Collection; import java.util.List; -import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.initializeCollapserProperties; import static org.slf4j.helpers.MessageFormatter.arrayFormat; /** @@ -49,11 +45,7 @@ public class CommandCollapser extends HystrixCollapser, Object, Obj * @param metaHolder the {@link MetaHolder} */ public CommandCollapser(MetaHolder metaHolder) { - super(new CollapserSetterBuilder() - .collapserKey(metaHolder.getHystrixCollapser().collapserKey(), metaHolder.getDefaultCollapserKey()) - .scope(metaHolder.getHystrixCollapser().scope()) - .properties(metaHolder.getHystrixCollapser().collapserProperties()) - .build()); + super(HystrixCommandBuilderFactory.getInstance().create(metaHolder).getSetterBuilder().buildCollapserCommandSetter()); this.metaHolder = metaHolder; } @@ -71,7 +63,7 @@ public Object getRequestArgument() { @Override protected HystrixCommand> createCommand( Collection> collapsedRequests) { - return BatchHystrixCommandFactory.getInstance().create(metaHolder, collapsedRequests); + return new BatchHystrixCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder, collapsedRequests)); } /** @@ -89,39 +81,6 @@ protected void mapResponseToRequests(List batchResponse, } } - /** - * Builder for {@link Setter}. - */ - private static final class CollapserSetterBuilder { - - private String collapserKey; - - private Scope scope; - - private HystrixProperty[] properties; - - private CollapserSetterBuilder collapserKey(String pCollapserKey, String def) { - this.collapserKey = StringUtils.isNotEmpty(pCollapserKey) ? pCollapserKey : def; - return this; - } - - private CollapserSetterBuilder scope(Scope pScope) { - this.scope = pScope; - return this; - } - - private CollapserSetterBuilder properties(HystrixProperty[] properties) { - this.properties = properties; - return this; - } - - public Setter build() { - HystrixCollapserProperties.Setter propSetter = initializeCollapserProperties(properties); - return Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey(collapserKey)).andScope(scope) - .andCollapserPropertiesDefaults(propSetter); - } - } - private String createMessage(Collection> requests, List response) { return ERROR_MSG + arrayFormat(ERROR_MSF_TEMPLATE, new Object[]{getCollapserKey().name(), 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 ab35f08b0..426a9849a 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 @@ -30,7 +30,7 @@ import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; -import java.util.Map; +import java.util.List; /** * Base class for hystrix commands. @@ -40,19 +40,17 @@ @ThreadSafe public abstract class AbstractHystrixCommand extends com.netflix.hystrix.HystrixCommand { - private CommandActions commandActions; - private final Map commandProperties; - private CacheInvocationContext cacheResultInvocationContext; - private CacheInvocationContext cacheRemoveInvocationContext; + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; private final Collection> collapsedRequests; - private final Class[] ignoreExceptions; + private final List> ignoreExceptions; private final ExecutionType executionType; - private HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + private final HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); protected AbstractHystrixCommand(HystrixCommandBuilder builder) { super(builder.getSetterBuilder().build()); this.commandActions = builder.getCommandActions(); - this.commandProperties = builder.getCommandProperties(); this.collapsedRequests = builder.getCollapsedRequests(); this.cacheResultInvocationContext = builder.getCacheResultInvocationContext(); this.cacheRemoveInvocationContext = builder.getCacheRemoveInvocationContext(); @@ -65,7 +63,7 @@ protected AbstractHystrixCommand(HystrixCommandBuilder builder) { * * @return command action */ - CommandAction getCommandAction() { + protected CommandAction getCommandAction() { return commandActions.getCommandAction(); } @@ -74,26 +72,16 @@ CommandAction getCommandAction() { * * @return fallback action */ - CommandAction getFallbackAction() { + protected CommandAction getFallbackAction() { return commandActions.getFallbackAction(); } - - /** - * Gets command properties. - * - * @return command properties - */ - Map getCommandProperties() { - return commandProperties; - } - /** * Gets collapsed requests. * * @return collapsed requests */ - Collection> getCollapsedRequests() { + protected Collection> getCollapsedRequests() { return collapsedRequests; } @@ -102,11 +90,11 @@ Collection> getCollapsedReques * * @return exceptions types */ - Class[] getIgnoreExceptions() { + protected List> getIgnoreExceptions() { return ignoreExceptions; } - public ExecutionType getExecutionType() { + protected ExecutionType getExecutionType() { return executionType; } @@ -131,7 +119,7 @@ protected String getCacheKey() { * @return true if exception is ignorable, otherwise - false */ boolean isIgnorable(Throwable throwable) { - if (ignoreExceptions == null || ignoreExceptions.length == 0) { + if (ignoreExceptions == null || ignoreExceptions.isEmpty()) { return false; } for (Class ignoreException : ignoreExceptions) { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java index 1201127df..39529e4ba 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -18,16 +18,16 @@ import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; -import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.ThreadSafe; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; + /** * This command is used in collapser. */ @@ -36,7 +36,7 @@ public class BatchHystrixCommand extends AbstractHystrixCommand> { private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class); - protected BatchHystrixCommand(HystrixCommandBuilder builder) { + public BatchHystrixCommand(HystrixCommandBuilder builder) { super(builder); } @@ -70,19 +70,9 @@ protected List getFallback() { return (List) process(new Action() { @Override Object execute() { - Object[] args = toArgs(getCollapsedRequests()); - if (commandAction.getMetaHolder().isExtendedFallback()) { - if (commandAction.getMetaHolder().isExtendedParentFallback()) { - args[args.length - 1] = getFailedExecutionException(); - } else { - args = Arrays.copyOf(args, args.length + 1); - args[args.length - 1] = getFailedExecutionException(); - } - } else { - if (commandAction.getMetaHolder().isExtendedParentFallback()) { - args = ArrayUtils.remove(args, args.length - 1); - } - } + MetaHolder metaHolder = commandAction.getMetaHolder(); + Object[] args = toArgs(getCollapsedRequests()); + args = createArgsForFallback(args, metaHolder, getFailedExecutionException()); return commandAction.executeWithArgs(commandAction.getMetaHolder().getFallbackExecutionType(), args); } }); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java deleted file mode 100644 index 51f5b6d20..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2012 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.command; - -/** - * Specific implementation of {@link HystrixCommandFactory} interface to create {@link BatchHystrixCommand} instances. - */ -public class BatchHystrixCommandFactory extends AbstractHystrixCommandFactory - implements HystrixCommandFactory { - - private static final HystrixCommandFactory COMMAND_FACTORY = new BatchHystrixCommandFactory(); - - public static HystrixCommandFactory getInstance() { - return COMMAND_FACTORY; - } - - - @Override - BatchHystrixCommand create(HystrixCommandBuilder hystrixCommandBuilder) { - return new BatchHystrixCommand(hystrixCommandBuilder); - } -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java index 2566a71b5..06c34dd56 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandAction.java @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -20,9 +20,9 @@ /** * Simple action to encapsulate some logic to process it in a Hystrix command. */ -public abstract class CommandAction { +public interface CommandAction { - public abstract MetaHolder getMetaHolder(); + MetaHolder getMetaHolder(); /** * Executes action in accordance with the given execution type. @@ -31,7 +31,7 @@ public abstract class CommandAction { * @return result of execution * @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException */ - public abstract Object execute(ExecutionType executionType) throws CommandActionExecutionException; + Object execute(ExecutionType executionType) throws CommandActionExecutionException; /** * Executes action with parameters in accordance with the given execution ty @@ -41,13 +41,13 @@ public abstract class CommandAction { * @return result of execution * @throws com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException */ - public abstract Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException; + Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException; /** * Gets action name. Useful for debugging. * * @return the action name */ - public abstract String getActionName(); + String getActionName(); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java index d4621a656..668663255 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java @@ -42,6 +42,10 @@ public CommandAction getFallbackAction() { return fallbackAction; } + public boolean hasFallbackAction() { + return fallbackAction != null; + } + public static class Builder { private CommandAction commandAction; private CommandAction fallbackAction; diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java index bf9b6dc39..0cc55c8c7 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutionAction.java @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -15,30 +15,28 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; /** * Action to execute a Hystrix command. */ -public class CommandExecutionAction extends CommandAction { +public class CommandExecutionAction implements CommandAction { - private AbstractHystrixCommand hystrixCommand; - private MetaHolder metaHolder; + private HystrixInvokable hystrixCommand; + private MetaHolder metaHolder; /** * Constructor with parameters. * * @param hystrixCommand the hystrix command to execute. */ - public CommandExecutionAction(AbstractHystrixCommand hystrixCommand, MetaHolder metaHolder) { + public CommandExecutionAction(HystrixInvokable hystrixCommand, MetaHolder metaHolder) { this.hystrixCommand = hystrixCommand; this.metaHolder = metaHolder; } - public AbstractHystrixCommand getHystrixCommand() { - return hystrixCommand; - } - @Override public MetaHolder getMetaHolder() { return metaHolder; @@ -59,8 +57,9 @@ public Object executeWithArgs(ExecutionType executionType, Object[] args) throws */ @Override public String getActionName() { - if (hystrixCommand != null) { - return hystrixCommand.getCommandKey().name(); + if (hystrixCommand != null && hystrixCommand instanceof HystrixInvokableInfo) { + HystrixInvokableInfo info = (HystrixInvokableInfo) hystrixCommand; + return info.getCommandKey().name(); } return ""; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java index 605ba01c8..9c8bc8a59 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -16,47 +16,70 @@ package com.netflix.hystrix.contrib.javanica.command; import com.netflix.hystrix.HystrixExecutable; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.HystrixObservable; +import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; import com.netflix.hystrix.contrib.javanica.utils.FutureDecorator; import org.apache.commons.lang3.Validate; /** - * Invokes necessary method of {@link HystrixExecutable} for specified execution type: + * Invokes necessary method of {@link HystrixExecutable} or {@link HystrixObservable} for specified execution type: *

* {@link ExecutionType#SYNCHRONOUS} -> {@link com.netflix.hystrix.HystrixExecutable#execute()} + *

* {@link ExecutionType#ASYNCHRONOUS} -> {@link com.netflix.hystrix.HystrixExecutable#queue()} - * {@link ExecutionType#OBSERVABLE} -> {@link com.netflix.hystrix.HystrixExecutable#observe()}. + *

+ * {@link ExecutionType#OBSERVABLE} -> depends on specify observable execution mode: + * {@link ObservableExecutionMode#EAGER} - {@link HystrixObservable#observe()}, + * {@link ObservableExecutionMode#LAZY} - {@link HystrixObservable#toObservable()}. */ public class CommandExecutor { /** * Calls a method of {@link HystrixExecutable} in accordance with specified execution type. * - * @param executable {@link HystrixExecutable} + * @param invokable {@link HystrixInvokable} * @param metaHolder {@link MetaHolder} * @return the result of invocation of specific method. * @throws RuntimeException */ - public static Object execute(HystrixExecutable executable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException { - Validate.notNull(executable); + public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException { + Validate.notNull(invokable); Validate.notNull(metaHolder); switch (executionType) { case SYNCHRONOUS: { - return executable.execute(); + return castToExecutable(invokable, executionType).execute(); } case ASYNCHRONOUS: { - if(metaHolder.hasFallbackMethodCommand() - && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()){ + HystrixExecutable executable = castToExecutable(invokable, executionType); + if (metaHolder.hasFallbackMethodCommand() + && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) { return new FutureDecorator(executable.queue()); } return executable.queue(); } case OBSERVABLE: { - return executable.observe(); + HystrixObservable observable = castToObservable(invokable); + return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable(); } default: throw new RuntimeException("unsupported execution type: " + executionType); } } + private static HystrixExecutable castToExecutable(HystrixInvokable invokable, ExecutionType executionType) { + if (invokable instanceof HystrixExecutable) { + return (HystrixExecutable) invokable; + } + throw new RuntimeException("Command should implement " + HystrixExecutable.class.getCanonicalName() + " interface to execute in: " + executionType + " mode"); + } + + private static HystrixObservable castToObservable(HystrixInvokable invokable) { + if (invokable instanceof HystrixObservable) { + return (HystrixObservable) invokable; + } + throw new RuntimeException("Command should implement " + HystrixObservable.class.getCanonicalName() + " interface to execute in observable mode"); + } + } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandSetterBuilder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandSetterBuilder.java deleted file mode 100644 index 3893a8a27..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandSetterBuilder.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright 2012 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.command; - -import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.HystrixCommandGroupKey; -import com.netflix.hystrix.HystrixCommandKey; -import com.netflix.hystrix.HystrixThreadPoolKey; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; -import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; -import com.netflix.hystrix.contrib.javanica.exception.HystrixPropertyException; -import org.apache.commons.lang3.StringUtils; - -/** - * Builder for {@link HystrixCommand.Setter}. - */ -public class CommandSetterBuilder { - - private String groupKey; - private String commandKey; - private String threadPoolKey; - private HystrixProperty[] commandProperties = EMPTY; - private HystrixProperty[] threadPoolProperties = EMPTY; - private static final HystrixProperty[] EMPTY = new HystrixProperty[0]; - - public CommandSetterBuilder groupKey(String pGroupKey) { - this.groupKey = pGroupKey; - return this; - } - - public CommandSetterBuilder groupKey(String pGroupKey, String def) { - this.groupKey = StringUtils.isNotEmpty(pGroupKey) ? pGroupKey : def; - return this; - } - - public CommandSetterBuilder commandKey(String pCommandKey) { - this.commandKey = pCommandKey; - return this; - } - - public CommandSetterBuilder commandKey(String pCommandKey, String def) { - this.commandKey = StringUtils.isNotEmpty(pCommandKey) ? pCommandKey : def; - return this; - } - - public CommandSetterBuilder commandProperties(HystrixProperty[] properties) { - commandProperties = properties; - return this; - } - - public CommandSetterBuilder threadPoolProperties(HystrixProperty[] properties) { - threadPoolProperties = properties; - return this; - } - - public CommandSetterBuilder threadPoolKey(String pThreadPoolKey) { - this.threadPoolKey = pThreadPoolKey; - return this; - } - - /** - * Creates instance of {@link HystrixCommand.Setter}. - * - * @return the instance of {@link HystrixCommand.Setter} - */ - public HystrixCommand.Setter build() throws HystrixPropertyException { - HystrixCommand.Setter setter = HystrixCommand.Setter - .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) - .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); - if (StringUtils.isNotBlank(threadPoolKey)) { - setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); - } - try { - setter.andThreadPoolPropertiesDefaults(HystrixPropertiesManager.initializeThreadPoolProperties(threadPoolProperties)); - } catch (IllegalArgumentException e) { - throw new HystrixPropertyException("Failed to set Thread Pool properties. " + getInfo(), e); - } - try { - setter.andCommandPropertiesDefaults(HystrixPropertiesManager.initializeCommandProperties(commandProperties)); - } catch (IllegalArgumentException e) { - throw new HystrixPropertyException("Failed to set Command properties. " + getInfo(), e); - } - return setter; - } - - private String getInfo() { - return "groupKey: '" + groupKey + "', commandKey: '" + commandKey + "', threadPoolKey: '" + threadPoolKey + "'"; - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java index b52c1cff2..1db909b9a 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java @@ -16,12 +16,12 @@ package com.netflix.hystrix.contrib.javanica.command; import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; -import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.ThreadSafe; -import java.util.Arrays; + +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; /** * Implementation of AbstractHystrixCommand which returns an Object as result. @@ -70,19 +70,8 @@ protected Object getFallback() { return process(new Action() { @Override Object execute() { - Object[] args = commandAction.getMetaHolder().getArgs(); - if (commandAction.getMetaHolder().isExtendedFallback()) { - if (commandAction.getMetaHolder().isExtendedParentFallback()) { - args[args.length - 1] = getFailedExecutionException(); - } else { - args = Arrays.copyOf(args, args.length + 1); - args[args.length - 1] = getFailedExecutionException(); - } - } else { - if (commandAction.getMetaHolder().isExtendedParentFallback()) { - args = ArrayUtils.remove(args, args.length - 1); - } - } + MetaHolder metaHolder = commandAction.getMetaHolder(); + Object[] args = createArgsForFallback(metaHolder, getFailedExecutionException()); return commandAction.executeWithArgs(commandAction.getMetaHolder().getFallbackExecutionType(), args); } }); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java deleted file mode 100644 index 9891cfe95..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2012 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.command; - -/** - * Specific implementation of {@link HystrixCommandFactory} interface to create {@link GenericCommand} instances. - */ -public class GenericHystrixCommandFactory extends AbstractHystrixCommandFactory - implements HystrixCommandFactory { - - private static final HystrixCommandFactory COMMAND_FACTORY = new GenericHystrixCommandFactory(); - - public static HystrixCommandFactory getInstance() { - return COMMAND_FACTORY; - } - - @Override - GenericCommand create(HystrixCommandBuilder hystrixCommandBuilder) { - return new GenericCommand(hystrixCommandBuilder); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java new file mode 100644 index 000000000..15ab1d90f --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericObservableCommand.java @@ -0,0 +1,150 @@ +/** + * Copyright 2015 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.command; + + +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; +import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; + +import javax.annotation.concurrent.ThreadSafe; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.utils.CommonUtils.createArgsForFallback; + +/** + * Generic class for all observable commands executed within javanica context. + */ +@ThreadSafe +public class GenericObservableCommand extends HystrixObservableCommand { + + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; + private final List> ignoreExceptions; + private final ExecutionType executionType; + private final HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + + private static final Logger LOGGER = LoggerFactory.getLogger(GenericObservableCommand.class); + + public GenericObservableCommand(HystrixCommandBuilder builder) { + super(builder.getSetterBuilder().buildObservableCommandSetter()); + this.commandActions = builder.getCommandActions(); + this.cacheResultInvocationContext = builder.getCacheResultInvocationContext(); + this.cacheRemoveInvocationContext = builder.getCacheRemoveInvocationContext(); + this.ignoreExceptions = builder.getIgnoreExceptions(); + this.executionType = builder.getExecutionType(); + } + + /** + *{@inheritDoc}. + */ + @Override + protected Observable construct() { + Observable result; + try { + result = (Observable) commandActions.getCommandAction().execute(executionType); + flushCache(); + } catch (CommandActionExecutionException throwable) { + Throwable cause = throwable.getCause(); + if (isIgnorable(cause)) { + throw new HystrixBadRequestException(cause.getMessage(), cause); + } + throw throwable; + } + return result; + } + + /** + *{@inheritDoc}. + */ + @Override + protected Observable resumeWithFallback() { + if (commandActions.hasFallbackAction()) { + MetaHolder metaHolder = commandActions.getFallbackAction().getMetaHolder(); + Throwable cause = getFailedExecutionException(); + if (cause instanceof CommandActionExecutionException) { + cause = cause.getCause(); + } + + Object[] args = createArgsForFallback(metaHolder, cause); + try { + Object res = commandActions.getFallbackAction().executeWithArgs(executionType, args); + if (res instanceof Observable) { + return (Observable) res; + } else { + return Observable.just(res); + } + } catch (Exception e) { + LOGGER.error(AbstractHystrixCommand.FallbackErrorMessageBuilder.create() + .append(commandActions.getFallbackAction(), e).build()); + throw new FallbackInvocationException(e.getCause()); + } + } + return super.resumeWithFallback(); + } + + /** + * {@inheritDoc}. + */ + @Override + protected String getCacheKey() { + String key = null; + if (cacheResultInvocationContext != null) { + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(cacheResultInvocationContext); + key = hystrixGeneratedCacheKey.getCacheKey(); + } + return key; + } + + /** + * Clears cache for the specified hystrix command. + */ + protected void flushCache() { + if (cacheRemoveInvocationContext != null) { + HystrixRequestCacheManager.getInstance().clearCache(cacheRemoveInvocationContext); + } + } + + /** + * Check whether triggered exception is ignorable. + * + * @param throwable the exception occurred during a command execution + * @return true if exception is ignorable, otherwise - false + */ + boolean isIgnorable(Throwable throwable) { + if (ignoreExceptions == null || ignoreExceptions.isEmpty()) { + return false; + } + for (Class ignoreException : ignoreExceptions) { + if (ignoreException.isAssignableFrom(throwable.getClass())) { + return true; + } + } + return false; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java new file mode 100644 index 000000000..494415b71 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericSetterBuilder.java @@ -0,0 +1,188 @@ +/** + * Copyright 2012 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.command; + +import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixCollapserKey; +import com.netflix.hystrix.HystrixCollapserProperties; +import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.HystrixCommandGroupKey; +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixObservableCommand; +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; +import com.netflix.hystrix.contrib.javanica.exception.HystrixPropertyException; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.concurrent.Immutable; +import java.util.Collections; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.initializeCollapserProperties; + +/** + * Builder for Hystrix Setters: {@link HystrixCommand.Setter}, {@link HystrixObservableCommand.Setter}, {@link HystrixCollapser.Setter}. + */ +@Immutable +public class GenericSetterBuilder { + + private String groupKey; + private String commandKey; + private String threadPoolKey; + private String collapserKey; + private HystrixCollapser.Scope scope; + private List commandProperties = Collections.emptyList(); + private List collapserProperties = Collections.emptyList(); + private List threadPoolProperties = Collections.emptyList(); + + public GenericSetterBuilder(Builder builder) { + this.groupKey = builder.groupKey; + this.commandKey = builder.commandKey; + this.threadPoolKey = builder.threadPoolKey; + this.collapserKey = builder.collapserKey; + this.scope = builder.scope; + this.commandProperties = builder.commandProperties; + this.collapserProperties = builder.collapserProperties; + this.threadPoolProperties = builder.threadPoolProperties; + } + + public static Builder builder(){ + return new Builder(); + } + + + /** + * Creates instance of {@link HystrixCommand.Setter}. + * + * @return the instance of {@link HystrixCommand.Setter} + */ + public HystrixCommand.Setter build() throws HystrixPropertyException { + HystrixCommand.Setter setter = HystrixCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) + .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); + if (StringUtils.isNotBlank(threadPoolKey)) { + setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); + } + try { + setter.andThreadPoolPropertiesDefaults(HystrixPropertiesManager.initializeThreadPoolProperties(threadPoolProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Thread Pool properties. " + getInfo(), e); + } + try { + setter.andCommandPropertiesDefaults(HystrixPropertiesManager.initializeCommandProperties(commandProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Command properties. " + getInfo(), e); + } + return setter; + } + + // todo dmgcodevil: it would be better to reuse the code from build() method + public HystrixObservableCommand.Setter buildObservableCommandSetter() { + HystrixObservableCommand.Setter setter = HystrixObservableCommand.Setter + .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) + .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); + try { + setter.andCommandPropertiesDefaults(HystrixPropertiesManager.initializeCommandProperties(commandProperties)); + } catch (IllegalArgumentException e) { + throw new HystrixPropertyException("Failed to set Command properties. " + getInfo(), e); + } + return setter; + } + + public HystrixCollapser.Setter buildCollapserCommandSetter(){ + HystrixCollapserProperties.Setter propSetter = initializeCollapserProperties(collapserProperties); + return HystrixCollapser.Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey(collapserKey)).andScope(scope) + .andCollapserPropertiesDefaults(propSetter); + } + + private String getInfo() { + return "groupKey: '" + groupKey + "', commandKey: '" + commandKey + "', threadPoolKey: '" + threadPoolKey + "'"; + } + + + public static class Builder { + private String groupKey; + private String commandKey; + private String threadPoolKey; + private String collapserKey; + private HystrixCollapser.Scope scope; + private List commandProperties = Collections.emptyList(); + private List collapserProperties = Collections.emptyList(); + private List threadPoolProperties = Collections.emptyList(); + + public Builder groupKey(String pGroupKey) { + this.groupKey = pGroupKey; + return this; + } + + public Builder groupKey(String pGroupKey, String def) { + this.groupKey = StringUtils.isNotEmpty(pGroupKey) ? pGroupKey : def; + return this; + } + + public Builder commandKey(String pCommandKey) { + this.commandKey = pCommandKey; + return this; + } + + @Deprecated + public Builder commandKey(String pCommandKey, String def) { + this.commandKey = StringUtils.isNotEmpty(pCommandKey) ? pCommandKey : def; + return this; + } + + public Builder collapserKey(String pCollapserKey) { + this.collapserKey = pCollapserKey; + return this; + } + + public Builder scope(HystrixCollapser.Scope pScope) { + this.scope = pScope; + return this; + } + + public Builder collapserProperties(List properties) { + collapserProperties = properties; + return this; + } + + public Builder commandProperties(List properties) { + commandProperties = properties; + return this; + } + + + public Builder threadPoolProperties(List properties) { + threadPoolProperties = properties; + return this; + } + + public Builder threadPoolKey(String pThreadPoolKey) { + this.threadPoolKey = pThreadPoolKey; + return this; + } + + public GenericSetterBuilder build(){ + return new GenericSetterBuilder(this); + } + + } + + + + +} 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 04458c7ba..4f9a7e49a 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 @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -15,32 +15,48 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.google.common.collect.ImmutableList; import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; - +import javax.annotation.concurrent.Immutable; import java.util.Collection; -import java.util.Map; +import java.util.Collections; +import java.util.List; /** * Builder contains all necessary information required to create specific hystrix command. * * @author dmgcodevil */ +@Immutable public class HystrixCommandBuilder { - private CommandSetterBuilder setterBuilder; - private CommandActions commandActions; - private Map commandProperties; - private CacheInvocationContext cacheResultInvocationContext; - private CacheInvocationContext cacheRemoveInvocationContext; - private Collection> collapsedRequests; - private Class[] ignoreExceptions; - private ExecutionType executionType; + private final GenericSetterBuilder setterBuilder; + private final CommandActions commandActions; + private final CacheInvocationContext cacheResultInvocationContext; + private final CacheInvocationContext cacheRemoveInvocationContext; + private final Collection> collapsedRequests; + private final List> ignoreExceptions; + private final ExecutionType executionType; + + public HystrixCommandBuilder(Builder builder) { + this.setterBuilder = builder.setterBuilder; + this.commandActions = builder.commandActions; + this.cacheResultInvocationContext = builder.cacheResultInvocationContext; + this.cacheRemoveInvocationContext = builder.cacheRemoveInvocationContext; + this.collapsedRequests = builder.collapsedRequests; + this.ignoreExceptions = builder.ignoreExceptions; + this.executionType = builder.executionType; + } + + public static Builder builder() { + return new Builder(); + } - public CommandSetterBuilder getSetterBuilder() { + public GenericSetterBuilder getSetterBuilder() { return setterBuilder; } @@ -48,10 +64,6 @@ public CommandActions getCommandActions() { return commandActions; } - public Map getCommandProperties() { - return commandProperties; - } - public CacheInvocationContext getCacheResultInvocationContext() { return cacheResultInvocationContext; } @@ -64,7 +76,7 @@ public Collection> getCollapse return collapsedRequests; } - public Class[] getIgnoreExceptions() { + public List> getIgnoreExceptions() { return ignoreExceptions; } @@ -72,92 +84,101 @@ public ExecutionType getExecutionType() { return executionType; } - /** - * Sets the builder to create {@link com.netflix.hystrix.HystrixCommand.Setter}. - * - * @param pSetterBuilder the builder to create {@link com.netflix.hystrix.HystrixCommand.Setter} - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder setterBuilder(CommandSetterBuilder pSetterBuilder) { - this.setterBuilder = pSetterBuilder; - return this; - } - - /** - * Sets command actions {@link CommandActions}. - * - * @param pCommandActions the command actions - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder commandActions(CommandActions pCommandActions) { - this.commandActions = pCommandActions; - return this; - } - - /** - * Sets command properties. - * - * @param pCommandProperties the command properties - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder commandProperties(Map pCommandProperties) { - this.commandProperties = pCommandProperties; - return this; - } - - /** - * Sets CacheResult invocation context, see {@link CacheInvocationContext} and {@link CacheResult}. - * - * @param pCacheResultInvocationContext the CacheResult invocation context - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder cacheResultInvocationContext(CacheInvocationContext pCacheResultInvocationContext) { - this.cacheResultInvocationContext = pCacheResultInvocationContext; - return this; - } - - /** - * Sets CacheRemove invocation context, see {@link CacheInvocationContext} and {@link CacheRemove}. - * - * @param pCacheRemoveInvocationContext the CacheRemove invocation context - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder cacheRemoveInvocationContext(CacheInvocationContext pCacheRemoveInvocationContext) { - this.cacheRemoveInvocationContext = pCacheRemoveInvocationContext; - return this; - } - - /** - * Sets collapsed requests. - * - * @param pCollapsedRequests the collapsed requests - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder collapsedRequests(Collection> pCollapsedRequests) { - this.collapsedRequests = pCollapsedRequests; - return this; - } - - /** - * Sets exceptions that should be ignored and wrapped to throw in {@link com.netflix.hystrix.exception.HystrixBadRequestException}. - * - * @param pIgnoreExceptions the exceptions to be ignored - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder ignoreExceptions(Class[] pIgnoreExceptions) { - this.ignoreExceptions = pIgnoreExceptions; - return this; - } - /** - * Sets execution type, see {@link ExecutionType}. - * - * @param pExecutionType the execution type - * @return this {@link HystrixCommandBuilder} - */ - public HystrixCommandBuilder executionType(ExecutionType pExecutionType) { - this.executionType = pExecutionType; - return this; + public static class Builder { + private GenericSetterBuilder setterBuilder; + private CommandActions commandActions; + private CacheInvocationContext cacheResultInvocationContext; + private CacheInvocationContext cacheRemoveInvocationContext; + private Collection> collapsedRequests = Collections.emptyList(); + private List> ignoreExceptions = Collections.emptyList(); + private ExecutionType executionType = ExecutionType.SYNCHRONOUS; + + /** + * Sets the builder to create specific Hystrix setter, for instance HystrixCommand.Setter + * + * @param pSetterBuilder the builder to create specific Hystrix setter + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder setterBuilder(GenericSetterBuilder pSetterBuilder) { + this.setterBuilder = pSetterBuilder; + return this; + } + + /** + * Sets command actions {@link CommandActions}. + * + * @param pCommandActions the command actions + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder commandActions(CommandActions pCommandActions) { + this.commandActions = pCommandActions; + return this; + } + + /** + * Sets CacheResult invocation context, see {@link CacheInvocationContext} and {@link CacheResult}. + * + * @param pCacheResultInvocationContext the CacheResult invocation context + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder cacheResultInvocationContext(CacheInvocationContext pCacheResultInvocationContext) { + this.cacheResultInvocationContext = pCacheResultInvocationContext; + return this; + } + + /** + * Sets CacheRemove invocation context, see {@link CacheInvocationContext} and {@link CacheRemove}. + * + * @param pCacheRemoveInvocationContext the CacheRemove invocation context + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder cacheRemoveInvocationContext(CacheInvocationContext pCacheRemoveInvocationContext) { + this.cacheRemoveInvocationContext = pCacheRemoveInvocationContext; + return this; + } + + /** + * Sets collapsed requests. + * + * @param pCollapsedRequests the collapsed requests + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder collapsedRequests(Collection> pCollapsedRequests) { + this.collapsedRequests = pCollapsedRequests; + return this; + } + + /** + * Sets exceptions that should be ignored and wrapped to throw in {@link com.netflix.hystrix.exception.HystrixBadRequestException}. + * + * @param pIgnoreExceptions the exceptions to be ignored + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder ignoreExceptions(Class[] pIgnoreExceptions) { + this.ignoreExceptions = ImmutableList.copyOf(pIgnoreExceptions); + return this; + } + + /** + * Sets execution type, see {@link ExecutionType}. + * + * @param pExecutionType the execution type + * @return this {@link HystrixCommandBuilder.Builder} + */ + public Builder executionType(ExecutionType pExecutionType) { + this.executionType = pExecutionType; + return this; + } + + /** + * Creates new {@link HystrixCommandBuilder} instance. + * + * @return new {@link HystrixCommandBuilder} instance + */ + public HystrixCommandBuilder build() { + return new HystrixCommandBuilder(this); + } } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java similarity index 51% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java index 8980071f1..2523e4c21 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilderFactory.java @@ -1,12 +1,12 @@ /** - * Copyright 2012 Netflix, Inc. - * + * Copyright 2015 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. @@ -15,11 +15,8 @@ */ package com.netflix.hystrix.contrib.javanica.command; - -import com.google.common.collect.Maps; import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; import org.apache.commons.lang3.StringUtils; @@ -28,7 +25,6 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; -import java.util.Map; import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheRemoveInvocationContext; import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheResultInvocationContext; @@ -36,55 +32,100 @@ import static com.netflix.hystrix.contrib.javanica.utils.ajc.AjcUtils.getAjcMethodAroundAdvice; /** - * Base implementation of {@link HystrixCommandFactory} interface. - * - * @param the type of Hystrix command + * Created by dmgcodevil. */ -public abstract class AbstractHystrixCommandFactory - implements HystrixCommandFactory { - - /** - * {@inheritDoc} - */ - @Override - public T create(MetaHolder metaHolder, - Collection> collapsedRequests) { - Validate.notNull(metaHolder.getHystrixCommand(), "hystrixCommand cannot be null"); - String groupKey = StringUtils.isNotEmpty(metaHolder.getHystrixCommand().groupKey()) ? - metaHolder.getHystrixCommand().groupKey() - : metaHolder.getDefaultGroupKey(); - String commandKey = StringUtils.isNotEmpty(metaHolder.getHystrixCommand().commandKey()) ? - metaHolder.getHystrixCommand().commandKey() - : metaHolder.getDefaultCommandKey(); - - CommandSetterBuilder setterBuilder = new CommandSetterBuilder(); - setterBuilder.commandKey(commandKey); - setterBuilder.groupKey(groupKey); - setterBuilder.threadPoolKey(metaHolder.getHystrixCommand().threadPoolKey()); - setterBuilder.threadPoolProperties(metaHolder.getHystrixCommand().threadPoolProperties()); - setterBuilder.commandProperties(metaHolder.getHystrixCommand().commandProperties()); - - Map commandProperties = getCommandProperties(metaHolder.getHystrixCommand()); - CommandAction commandAction = new MethodExecutionAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs(), metaHolder); - CommandAction fallbackAction = createFallbackAction(metaHolder, collapsedRequests); - CommandActions commandActions = CommandActions.builder().commandAction(commandAction) - .fallbackAction(fallbackAction).build(); +public class HystrixCommandBuilderFactory { + + // todo Add Cache + + private static final HystrixCommandBuilderFactory INSTANCE = new HystrixCommandBuilderFactory(); + + public static HystrixCommandBuilderFactory getInstance() { + return INSTANCE; + } + + private HystrixCommandBuilderFactory() { + + } + + public HystrixCommandBuilder create(MetaHolder metaHolder) { + return create(metaHolder, Collections.>emptyList()); + } - HystrixCommandBuilder hystrixCommandBuilder = new HystrixCommandBuilder().setterBuilder(setterBuilder) - .commandActions(commandActions) - .commandProperties(commandProperties) + public HystrixCommandBuilder create(MetaHolder metaHolder, Collection> collapsedRequests) { + validateMetaHolder(metaHolder); + + return HystrixCommandBuilder.builder() + .setterBuilder(createGenericSetterBuilder(metaHolder)) + .commandActions(createCommandActions(metaHolder)) .collapsedRequests(collapsedRequests) .cacheResultInvocationContext(createCacheResultInvocationContext(metaHolder)) .cacheRemoveInvocationContext(createCacheRemoveInvocationContext(metaHolder)) .ignoreExceptions(metaHolder.getHystrixCommand().ignoreExceptions()) - .executionType(metaHolder.getExecutionType()); - return create(hystrixCommandBuilder); + .executionType(metaHolder.getExecutionType()) + .build(); + } + + private void validateMetaHolder(MetaHolder metaHolder) { + Validate.notNull(metaHolder, "metaHolder is required parameter and cannot be null"); + Validate.isTrue(metaHolder.isCommandAnnotationPresent(), "hystrixCommand annotation is absent"); + } + + private GenericSetterBuilder createGenericSetterBuilder(MetaHolder metaHolder) { + GenericSetterBuilder.Builder setterBuilder = GenericSetterBuilder.builder() + .groupKey(createGroupKey(metaHolder)) + .threadPoolKey(createThreadPoolKey(metaHolder)) + .commandKey(createCommandKey(metaHolder)) + .collapserKey(createCollapserKey(metaHolder)) + .commandProperties(metaHolder.getCommandProperties()) + .threadPoolProperties(metaHolder.getThreadPoolProperties()) + .collapserProperties(metaHolder.getCollapserProperties()); + if (metaHolder.isCollapserAnnotationPresent()) { + setterBuilder.scope(metaHolder.getHystrixCollapser().scope()); + } + 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); + return CommandActions.builder().commandAction(commandAction) + .fallbackAction(fallbackAction).build(); } - CommandAction createFallbackAction(MetaHolder metaHolder, - Collection> collapsedRequests) { + private CommandAction createCommandAction(MetaHolder metaHolder) { + return new MethodExecutionAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs(), metaHolder); + } + + private CommandAction createFallbackAction(MetaHolder metaHolder) { - FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(metaHolder.getObj().getClass(), metaHolder.getMethod(), metaHolder.isExtendedFallback()); + FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(metaHolder.getObj().getClass(), + metaHolder.getMethod(), metaHolder.isExtendedFallback()); fallbackMethod.validateReturnType(metaHolder.getMethod()); CommandAction fallbackAction = null; if (fallbackMethod.isPresent()) { @@ -92,6 +133,7 @@ CommandAction createFallbackAction(MetaHolder metaHolder, Method fMethod = fallbackMethod.getMethod(); if (fallbackMethod.isCommand()) { fMethod.setAccessible(true); + HystrixCommand hystrixCommand = fMethod.getAnnotation(HystrixCommand.class); MetaHolder fmMetaHolder = MetaHolder.builder() .obj(metaHolder.getObj()) .method(fMethod) @@ -103,11 +145,13 @@ CommandAction createFallbackAction(MetaHolder metaHolder, .extendedFallback(fallbackMethod.isExtended()) .fallbackExecutionType(fallbackMethod.getExecutionType()) .extendedParentFallback(metaHolder.isExtendedFallback()) + .observable(ExecutionType.OBSERVABLE == fallbackMethod.getExecutionType()) .defaultCommandKey(fMethod.getName()) .defaultGroupKey(metaHolder.getDefaultGroupKey()) .hystrixCollapser(metaHolder.getHystrixCollapser()) - .hystrixCommand(fMethod.getAnnotation(HystrixCommand.class)).build(); - fallbackAction = new LazyCommandExecutionAction(GenericHystrixCommandFactory.getInstance(), fmMetaHolder, collapsedRequests); + .observableExecutionMode(hystrixCommand.observableExecutionMode()) + .hystrixCommand(hystrixCommand).build(); + fallbackAction = new LazyCommandExecutionAction(fmMetaHolder); } else { MetaHolder fmMetaHolder = MetaHolder.builder() .obj(metaHolder.getObj()) @@ -125,9 +169,6 @@ CommandAction createFallbackAction(MetaHolder metaHolder, return fallbackAction; } - abstract T create(HystrixCommandBuilder hystrixCommandBuilder); - - private Method getAjcMethod(Object target, Method fallback) { if (isCompileWeaving()) { return getAjcMethodAroundAdvice(target.getClass(), fallback); @@ -135,23 +176,4 @@ private Method getAjcMethod(Object target, Method fallback) { return null; } - private CommandAction createCacheKeyAction(MetaHolder metaHolder) { - CommandAction cacheKeyAction = null; - if (metaHolder.getCacheKeyMethod() != null) { - cacheKeyAction = new MethodExecutionAction(metaHolder.getObj(), metaHolder.getCacheKeyMethod(), metaHolder.getArgs(), metaHolder); - } - return cacheKeyAction; - } - - private Map getCommandProperties(HystrixCommand hystrixCommand) { - if (hystrixCommand.commandProperties() == null || hystrixCommand.commandProperties().length == 0) { - return Collections.emptyMap(); - } - Map commandProperties = Maps.newHashMap(); - for (HystrixProperty commandProperty : hystrixCommand.commandProperties()) { - commandProperties.put(commandProperty.name(), commandProperty.value()); - } - return commandProperties; - } - } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java index 845b1182b..216642403 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandFactory.java @@ -1,12 +1,12 @@ /** - * Copyright 2012 Netflix, Inc. - * + * Copyright 2015 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. @@ -15,24 +15,44 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.HystrixCollapser; - -import java.util.Collection; +import com.netflix.hystrix.HystrixExecutable; +import com.netflix.hystrix.HystrixInvokable; +import com.netflix.hystrix.contrib.javanica.collapser.CommandCollapser; /** - * Base factory interface for Hystrix commands. - * - * @param the type of Hystrix command + * Created by dmgcodevil. */ -public interface HystrixCommandFactory { - - /** - * Creates a Hystrix command. - * - * @param metaHolder the {@link MetaHolder} - * @param collapsedRequests the collapsed requests - * @return a Hystrix command - */ - T create(MetaHolder metaHolder, - Collection> collapsedRequests); +public class HystrixCommandFactory { + + private static final HystrixCommandFactory INSTANCE = new HystrixCommandFactory(); + + private HystrixCommandFactory() { + + } + + public static HystrixCommandFactory getInstance() { + return INSTANCE; + } + + public HystrixInvokable create(MetaHolder metaHolder) { + HystrixInvokable executable; + if (metaHolder.isCollapserAnnotationPresent()) { + executable = new CommandCollapser(metaHolder); + } else if (metaHolder.isObservable()) { + executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } else { + executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } + return executable; + } + + public HystrixInvokable createDelayed(MetaHolder metaHolder) { + HystrixInvokable executable; + if (metaHolder.isObservable()) { + executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } else { + executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder)); + } + return executable; + } } 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 edcd30d6c..893f082a4 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 @@ -1,12 +1,12 @@ /** * Copyright 2012 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. @@ -16,34 +16,25 @@ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.HystrixInvokable; import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import org.apache.commons.lang3.StringUtils; -import java.util.Collection; - /** * This action creates related hystrix commands on demand when command creation can be postponed. */ -public class LazyCommandExecutionAction extends CommandAction { - - private MetaHolder metaHolder; +public class LazyCommandExecutionAction implements CommandAction { - private Collection> collapsedRequests; + private MetaHolder originalMetaHolder; - private HystrixCommandFactory commandFactory; - public LazyCommandExecutionAction(HystrixCommandFactory commandFactory, - MetaHolder metaHolder, - Collection> collapsedRequests) { - this.commandFactory = commandFactory; - this.metaHolder = metaHolder; - this.collapsedRequests = collapsedRequests; + public LazyCommandExecutionAction(MetaHolder metaHolder) { + this.originalMetaHolder = metaHolder; } @Override public MetaHolder getMetaHolder() { - return metaHolder; + return originalMetaHolder; } /** @@ -51,8 +42,8 @@ public MetaHolder getMetaHolder() { */ @Override public Object execute(ExecutionType executionType) throws CommandActionExecutionException { - AbstractHystrixCommand abstractHystrixCommand = commandFactory.create(createHolder(executionType), collapsedRequests); - return new CommandExecutionAction(abstractHystrixCommand, metaHolder).execute(executionType); + HystrixInvokable command = HystrixCommandFactory.getInstance().createDelayed(createCopy(originalMetaHolder, executionType)); + return new CommandExecutionAction(command, originalMetaHolder).execute(executionType); } /** @@ -60,8 +51,8 @@ public Object execute(ExecutionType executionType) throws CommandActionExecution */ @Override public Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException { - AbstractHystrixCommand abstractHystrixCommand = commandFactory.create(createHolder(executionType, args), collapsedRequests); - return new CommandExecutionAction(abstractHystrixCommand, metaHolder).execute(executionType); + HystrixInvokable command = HystrixCommandFactory.getInstance().createDelayed(createCopy(originalMetaHolder, executionType, args)); + return new CommandExecutionAction(command, originalMetaHolder).execute(executionType); } /** @@ -69,43 +60,48 @@ public Object executeWithArgs(ExecutionType executionType, Object[] args) throws */ @Override public String getActionName() { - return StringUtils.isNotEmpty(metaHolder.getHystrixCommand().commandKey()) ? - metaHolder.getHystrixCommand().commandKey() - : metaHolder.getDefaultCommandKey(); + return StringUtils.isNotEmpty(originalMetaHolder.getHystrixCommand().commandKey()) ? + originalMetaHolder.getHystrixCommand().commandKey() + : originalMetaHolder.getDefaultCommandKey(); } - private MetaHolder createHolder(ExecutionType executionType) { + // todo dmgcodevil: move it to MetaHolder class ? + private MetaHolder createCopy(MetaHolder source, ExecutionType executionType) { return MetaHolder.builder() - .obj(metaHolder.getObj()) - .method(metaHolder.getMethod()) - .ajcMethod(metaHolder.getAjcMethod()) - .fallbackExecutionType(metaHolder.getFallbackExecutionType()) - .extendedFallback(metaHolder.isExtendedFallback()) - .extendedParentFallback(metaHolder.isExtendedParentFallback()) + .obj(source.getObj()) + .method(source.getMethod()) + .ajcMethod(source.getAjcMethod()) + .fallbackExecutionType(source.getFallbackExecutionType()) + .extendedFallback(source.isExtendedFallback()) + .extendedParentFallback(source.isExtendedParentFallback()) .executionType(executionType) - .args(metaHolder.getArgs()) - .defaultCollapserKey(metaHolder.getDefaultCollapserKey()) - .defaultCommandKey(metaHolder.getDefaultCommandKey()) - .defaultGroupKey(metaHolder.getDefaultGroupKey()) - .hystrixCollapser(metaHolder.getHystrixCollapser()) - .hystrixCommand(metaHolder.getHystrixCommand()).build(); + .args(source.getArgs()) + .observable(source.isObservable()) + .observableExecutionMode(source.getObservableExecutionMode()) + .defaultCollapserKey(source.getDefaultCollapserKey()) + .defaultCommandKey(source.getDefaultCommandKey()) + .defaultGroupKey(source.getDefaultGroupKey()) + .hystrixCollapser(source.getHystrixCollapser()) + .hystrixCommand(source.getHystrixCommand()).build(); } - private MetaHolder createHolder(ExecutionType executionType, Object[] args) { + private MetaHolder createCopy(MetaHolder source, ExecutionType executionType, Object[] args) { return MetaHolder.builder() - .obj(metaHolder.getObj()) - .method(metaHolder.getMethod()) + .obj(source.getObj()) + .method(source.getMethod()) .executionType(executionType) - .ajcMethod(metaHolder.getAjcMethod()) - .fallbackExecutionType(metaHolder.getFallbackExecutionType()) - .extendedParentFallback(metaHolder.isExtendedParentFallback()) - .extendedFallback(metaHolder.isExtendedFallback()) + .ajcMethod(source.getAjcMethod()) + .fallbackExecutionType(source.getFallbackExecutionType()) + .extendedParentFallback(source.isExtendedParentFallback()) + .extendedFallback(source.isExtendedFallback()) .args(args) - .defaultCollapserKey(metaHolder.getDefaultCollapserKey()) - .defaultCommandKey(metaHolder.getDefaultCommandKey()) - .defaultGroupKey(metaHolder.getDefaultGroupKey()) - .hystrixCollapser(metaHolder.getHystrixCollapser()) - .hystrixCommand(metaHolder.getHystrixCommand()).build(); + .observable(source.isObservable()) + .observableExecutionMode(source.getObservableExecutionMode()) + .defaultCollapserKey(source.getDefaultCollapserKey()) + .defaultCommandKey(source.getDefaultCommandKey()) + .defaultGroupKey(source.getDefaultGroupKey()) + .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 1d0505b76..e3fdf1015 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,15 +15,19 @@ */ package com.netflix.hystrix.contrib.javanica.command; +import com.google.common.collect.ImmutableList; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.aop.aspectj.WeavingMode; +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.aspectj.lang.JoinPoint; +import javax.annotation.concurrent.Immutable; import java.lang.reflect.Method; import java.util.Arrays; -import javax.annotation.concurrent.Immutable; +import java.util.Collections; +import java.util.List; /** * Simple immutable holder to keep all necessary information about current method to build Hystrix command. @@ -52,6 +56,8 @@ public class MetaHolder { private final boolean fallback; private boolean extendedParentFallback; private final JoinPoint joinPoint; + private final boolean observable; + private final ObservableExecutionMode observableExecutionMode; private MetaHolder(Builder builder) { this.hystrixCommand = builder.hystrixCommand; @@ -74,6 +80,8 @@ private MetaHolder(Builder builder) { this.extendedFallback = builder.extendedFallback; this.fallback = builder.fallback; this.extendedParentFallback = builder.extendedParentFallback; + this.observable = builder.observable; + this.observableExecutionMode = builder.observableExecutionMode; } public static Builder builder() { @@ -140,8 +148,12 @@ public Class[] getParameterTypes() { return method.getParameterTypes(); } - public boolean isCollapser(){ - return hystrixCollapser!=null; + public boolean isCollapserAnnotationPresent() { + return hystrixCollapser != null; + } + + public boolean isCommandAnnotationPresent() { + return hystrixCommand != null; } public JoinPoint getJoinPoint() { @@ -176,6 +188,26 @@ public ExecutionType getFallbackExecutionType() { return fallbackExecutionType; } + public List getCommandProperties() { + return isCommandAnnotationPresent() ? ImmutableList.copyOf(hystrixCommand.commandProperties()) : Collections.emptyList(); + } + + public List getCollapserProperties() { + return isCollapserAnnotationPresent() ? ImmutableList.copyOf(hystrixCollapser.collapserProperties()) : Collections.emptyList(); + } + + public List getThreadPoolProperties() { + return isCommandAnnotationPresent() ? ImmutableList.copyOf(hystrixCommand.threadPoolProperties()) : Collections.emptyList(); + } + + public boolean isObservable() { + return observable; + } + + public ObservableExecutionMode getObservableExecutionMode() { + return observableExecutionMode; + } + public static final class Builder { private HystrixCollapser hystrixCollapser; @@ -197,8 +229,9 @@ public static final class Builder { private boolean extendedFallback; private boolean fallback; private boolean extendedParentFallback; + private boolean observable; private JoinPoint joinPoint; - + private ObservableExecutionMode observableExecutionMode; public Builder hystrixCollapser(HystrixCollapser hystrixCollapser) { this.hystrixCollapser = hystrixCollapser; @@ -300,6 +333,16 @@ public Builder extendedFallback(boolean extendedFallback) { return this; } + public Builder observable(boolean observable) { + this.observable = observable; + return this; + } + + public Builder observableExecutionMode(ObservableExecutionMode observableExecutionMode) { + this.observableExecutionMode = observableExecutionMode; + return this; + } + public MetaHolder build() { return new MetaHolder(this); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java index e6a132960..2adaae94e 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java @@ -16,8 +16,8 @@ package com.netflix.hystrix.contrib.javanica.command; +import com.netflix.hystrix.contrib.javanica.command.closure.AsyncClosureFactory; import com.netflix.hystrix.contrib.javanica.command.closure.Closure; -import com.netflix.hystrix.contrib.javanica.command.closure.ClosureFactoryRegistry; import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import com.netflix.hystrix.contrib.javanica.exception.ExceptionUtils; @@ -32,7 +32,7 @@ * If {@link Method#invoke(Object, Object...)} throws exception then this exception is wrapped to {@link CommandActionExecutionException} * for further unwrapping and processing. */ -public class MethodExecutionAction extends CommandAction { +public class MethodExecutionAction implements CommandAction { private static final Object[] EMPTY_ARGS = new Object[]{}; @@ -85,12 +85,12 @@ public Object execute(ExecutionType executionType) throws CommandActionExecution */ @Override public Object executeWithArgs(ExecutionType executionType, Object[] args) throws CommandActionExecutionException { - if (ExecutionType.SYNCHRONOUS.equals(executionType)) { - return execute(object, method, args); - } else { - Closure closure = ClosureFactoryRegistry.getFactory(executionType).createClosure(metaHolder, method, object, args); + if(ExecutionType.ASYNCHRONOUS == executionType){ + Closure closure = AsyncClosureFactory.getInstance().createClosure(metaHolder, method, object, args); return executeClj(closure.getClosureObj(), closure.getClosureMethod()); } + + return execute(object, method, args); } /** diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ObservableResult.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ObservableResult.java deleted file mode 100644 index ee4cb43f9..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ObservableResult.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2012 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.command; - -import rx.Observable; -import rx.Subscriber; - -/** - * Fake implementation of Observable. - * Provides abstract invoke method to process reactive execution (asynchronous callback). - * Can be used for method signatures - * which are declared with a Observable return type for reactive execution. - * - * @param command result type - */ -public abstract class ObservableResult extends Observable implements ClosureCommand { - - public ObservableResult() { - super(new OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - // do nothing - } - }); - } - - /** - * {@inheritDoc}. - */ - @Override - public abstract T invoke(); - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java index 0f73c4bde..23735a1ee 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java @@ -34,21 +34,6 @@ public abstract class AbstractClosureFactory implements ClosureFactory { static final String ERROR_TYPE_MESSAGE = "return type of '{}' method should be {}."; static final String INVOKE_METHOD = "invoke"; - /** - * {@inheritDoc}. - */ - @Override - public Closure createClosure(final Method method, final Object o, final Object... args) { - try { - Object closureObj = method.invoke(o, args); // creates instance of an anonymous class - return createClosure(method.getName(), closureObj); - } catch (InvocationTargetException e) { - throw Throwables.propagate(e.getCause()); - } catch (Exception e) { - throw Throwables.propagate(e); - } - } - @Override public Closure createClosure(MetaHolder metaHolder, Method method, Object o, Object... args) { try { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java index decfec809..c9502545a 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java @@ -23,6 +23,15 @@ */ public class AsyncClosureFactory extends AbstractClosureFactory { + private static final AsyncClosureFactory INSTANCE = new AsyncClosureFactory(); + + private AsyncClosureFactory() { + } + + public static AsyncClosureFactory getInstance() { + return INSTANCE; + } + /** * {@inheritDoc}. */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java index d27a64e62..bae427f17 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java @@ -27,13 +27,11 @@ public interface ClosureFactory { /** * Creates closure in accordance with method return type. * + * @param metaHolder the meta holder * @param method the external method within which closure is created * @param o the object the underlying method is invoked from * @param args the arguments used for the method call * @return new {@link Closure} instance */ - @Deprecated - Closure createClosure(final Method method, final Object o, final Object... args); - Closure createClosure(final MetaHolder metaHolder, final Method method, final Object o, final Object... args); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactoryRegistry.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactoryRegistry.java deleted file mode 100644 index c1f975b25..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactoryRegistry.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2012 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.command.closure; - - -import com.google.common.collect.ImmutableMap; -import com.netflix.hystrix.contrib.javanica.command.ExecutionType; -import com.netflix.hystrix.contrib.javanica.command.MetaHolder; - -import java.lang.reflect.Method; -import java.util.Map; - -/** - * Registry of {@link ClosureFactory} instances. - */ -public class ClosureFactoryRegistry { - - private static final Map CLOSURE_FACTORY_MAP = ImmutableMap - .builder() - .put(ExecutionType.ASYNCHRONOUS, new AsyncClosureFactory()) - .put(ExecutionType.OBSERVABLE, new ObservableClosureFactory()) - .put(ExecutionType.SYNCHRONOUS, new ClosureFactory() { - @Override - public Closure createClosure(Method method, Object o, Object... args) { - return null; - } - - @Override - public Closure createClosure(MetaHolder metaHolder, Method method, Object o, Object... args) { - return null; - } - }) - .build(); - - /** - * Gets factory for specified execution type. - * - * @param executionType the execution type {@link ExecutionType} - * @return an instance of {@link ClosureFactory} - */ - public static ClosureFactory getFactory(ExecutionType executionType) { - return CLOSURE_FACTORY_MAP.get(executionType); - } -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ObservableClosureFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ObservableClosureFactory.java deleted file mode 100644 index 57fbbc91a..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ObservableClosureFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2012 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.command.closure; - -import com.netflix.hystrix.contrib.javanica.command.ClosureCommand; -import com.netflix.hystrix.contrib.javanica.command.ObservableResult; - -/** - * Specific implementation of {@link ClosureFactory}. - */ -public class ObservableClosureFactory extends AbstractClosureFactory { - - /** - * {@inheritDoc}. - */ - @Override - boolean isClosureCommand(Object closureObj) { - return closureObj instanceof ObservableResult; - } - - /** - * {@inheritDoc}. - */ - @Override - Class getClosureCommandType() { - return ObservableResult.class; - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java index 2b3baf2bb..b77fa0d37 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/conf/HystrixPropertiesManager.java @@ -23,6 +23,7 @@ import org.apache.commons.lang3.Validate; import java.util.Arrays; +import java.util.List; import java.util.Map; /** @@ -94,7 +95,7 @@ private HystrixPropertiesManager() { * * @param properties the collapser properties */ - public static HystrixCommandProperties.Setter initializeCommandProperties(HystrixProperty[] properties) throws IllegalArgumentException { + public static HystrixCommandProperties.Setter initializeCommandProperties(List properties) throws IllegalArgumentException { return initializeProperties(HystrixCommandProperties.Setter(), properties, CMD_PROP_MAP, "command"); } @@ -103,7 +104,7 @@ public static HystrixCommandProperties.Setter initializeCommandProperties(Hystri * * @param properties the collapser properties */ - public static HystrixThreadPoolProperties.Setter initializeThreadPoolProperties(HystrixProperty[] properties) throws IllegalArgumentException { + public static HystrixThreadPoolProperties.Setter initializeThreadPoolProperties(List properties) throws IllegalArgumentException { return initializeProperties(HystrixThreadPoolProperties.Setter(), properties, TP_PROP_MAP, "thread pool"); } @@ -112,12 +113,12 @@ public static HystrixThreadPoolProperties.Setter initializeThreadPoolProperties( * * @param properties the collapser properties */ - public static HystrixCollapserProperties.Setter initializeCollapserProperties(HystrixProperty[] properties) { + public static HystrixCollapserProperties.Setter initializeCollapserProperties(List properties) { return initializeProperties(HystrixCollapserProperties.Setter(), properties, COLLAPSER_PROP_MAP, "collapser"); } - private static S initializeProperties(S setter, HystrixProperty[] properties, Map> propMap, String type) { - if (properties != null && properties.length > 0) { + private static S initializeProperties(S setter, List properties, Map> propMap, String type) { + if (properties != null && properties.size() > 0) { for (HystrixProperty property : properties) { validate(property); if (!propMap.containsKey(property.name())) { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java new file mode 100644 index 000000000..dafd729a7 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/CommonUtils.java @@ -0,0 +1,51 @@ +/** + * Copyright 2015 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.utils; + +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.Arrays; + +/** + * Created by dmgcodevil. + */ +public final class CommonUtils { + + private CommonUtils(){ + + } + + public static Object[] createArgsForFallback(MetaHolder metaHolder, Throwable exception) { + return createArgsForFallback(metaHolder.getArgs(), metaHolder, exception); + } + + public static Object[] createArgsForFallback(Object[] args, MetaHolder metaHolder, Throwable exception) { + if (metaHolder.isExtendedFallback()) { + if (metaHolder.isExtendedParentFallback()) { + args[args.length - 1] = exception; + } else { + args = Arrays.copyOf(args, args.length + 1); + args[args.length - 1] = exception; + } + } else { + if (metaHolder.isExtendedParentFallback()) { + args = ArrayUtils.remove(args, args.length - 1); + } + } + return args; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java index 12ccadde7..da471371d 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/FallbackMethod.java @@ -4,18 +4,31 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.getAllParameterizedTypes; +import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isReturnTypeParametrized; public class FallbackMethod { + + private final Method method; private final boolean extended; private ExecutionType executionType; public static final FallbackMethod ABSENT = new FallbackMethod(null, false); + public FallbackMethod(Method method) { + this(method, false); + } + public FallbackMethod(Method method, boolean extended) { this.method = method; this.extended = extended; @@ -46,50 +59,115 @@ public boolean isExtended() { public void validateReturnType(Method commandMethod) { if (isPresent()) { - Class returnType = commandMethod.getReturnType(); - if(ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(returnType)){ - // todo javanica support for observable must be reimplemented therefore it doesn't make sense to validate this type - return; - } - if (ExecutionType.ASYNCHRONOUS == ExecutionType.getExecutionType(returnType)) { - Class commandActualReturnType = getGenReturnType(commandMethod); - if (commandActualReturnType != null) { - Class fallbackActualReturnType; - if (isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) { - fallbackActualReturnType = getGenReturnType(method); - if (fallbackActualReturnType != null && - !commandActualReturnType.isAssignableFrom(fallbackActualReturnType)) { - throw new FallbackDefinitionException("fallback method is async and must be parametrized with: " + commandActualReturnType); - } - } - if (isCommand() && ExecutionType.SYNCHRONOUS == getExecutionType()) { - if (!commandActualReturnType.isAssignableFrom(method.getReturnType())) { - throw new FallbackDefinitionException("fallback method '" + method + "' must return : " + commandActualReturnType + " or it's subclass"); - } - } - if (!isCommand()) { - if (!commandActualReturnType.isAssignableFrom(method.getReturnType())) { - throw new FallbackDefinitionException("fallback method '" + method + "' must return : " + commandActualReturnType + " or it's subclass"); - } + Class commandReturnType = commandMethod.getReturnType(); + if (ExecutionType.OBSERVABLE == ExecutionType.getExecutionType(commandReturnType)) { + if (ExecutionType.OBSERVABLE != getExecutionType()) { + Type commandParametrizedType = commandMethod.getGenericReturnType(); + if (isReturnTypeParametrized(commandMethod)) { + commandParametrizedType = getFirstParametrizedType(commandMethod); } + validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method); + } else { + validateReturnType(commandMethod, method); } + + } else if (ExecutionType.ASYNCHRONOUS == ExecutionType.getExecutionType(commandReturnType)) { + if (isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) { + validateReturnType(commandMethod, method); + } + if (ExecutionType.ASYNCHRONOUS != getExecutionType()) { + Type commandParametrizedType = commandMethod.getGenericReturnType(); + if (isReturnTypeParametrized(commandMethod)) { + commandParametrizedType = getFirstParametrizedType(commandMethod); + } + validateParametrizedType(commandParametrizedType, method.getGenericReturnType(), commandMethod, method); + } + if (!isCommand() && ExecutionType.ASYNCHRONOUS == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if the fallback isn't command when the command is async.")); + } } else { - if (!commandMethod.getReturnType().isAssignableFrom(method.getReturnType())) { - throw new FallbackDefinitionException("fallback method '" + method + "' must return : " + commandMethod.getReturnType() + " or it's subclass"); + if (ExecutionType.ASYNCHRONOUS == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Future if command isn't asynchronous.")); } + if (ExecutionType.OBSERVABLE == getExecutionType()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, "fallback cannot return Observable if command isn't observable.")); + } + validateReturnType(commandMethod, method); } } - } - private Class getGenReturnType(Method m) { - Type type = m.getGenericReturnType(); - if (type != null) { - ParameterizedType pType = (ParameterizedType) type; - return (Class) pType.getActualTypeArguments()[0]; + private Type getFirstParametrizedType(Method m) { + Type gtype = m.getGenericReturnType(); + if (gtype instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) gtype; + return pType.getActualTypeArguments()[0]; } return null; } + + private void validateReturnType(Method commandMethod, Method fallbackMethod) { + if (isReturnTypeParametrized(commandMethod)) { + List commandParametrizedTypes = getParametrizedTypes(commandMethod); + List fallbackParametrizedTypes = getParametrizedTypes(fallbackMethod); + List msg = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes); + if (!msg.isEmpty()) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, ", "))); + } + } + validatePlainReturnType(commandMethod, fallbackMethod); + } + + private void validatePlainReturnType(Method commandMethod, Method fallbackMethod) { + validatePlainReturnType(commandMethod.getReturnType(), fallbackMethod.getReturnType(), commandMethod, fallbackMethod); + } + + private void validatePlainReturnType(Class commandReturnType, Class fallbackReturnType, Method commandMethod, Method fallbackMethod) { + if (!commandReturnType.isAssignableFrom(fallbackReturnType)) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, "Fallback method '" + + fallbackMethod + "' must return: " + commandReturnType + " or it's subclass")); + } + } + + private void validateParametrizedType(Type commandReturnType, Type fallbackReturnType, Method commandMethod, Method fallbackMethod) { + if (!commandReturnType.equals(fallbackReturnType)) { + throw new FallbackDefinitionException(createErrorMsg(commandMethod, fallbackMethod, "Fallback method '" + + fallbackMethod + "' must return: " + commandReturnType + " or it's subclass")); + } + } + + private String createErrorMsg(Method commandMethod, Method fallbackMethod, String hint) { + return "Incompatible return types. Command method: " + commandMethod + ", fallback method: " + fallbackMethod + ". " + + (StringUtils.isNotBlank(hint) ? "Hint: " : ""); + } + + private List getParametrizedTypes(Method m) { + return getAllParameterizedTypes(m.getGenericReturnType()); + } + + private List equalsParametrizedTypes(List commandParametrizedTypes, List fallbackParametrizedTypes) { + List msg = Collections.emptyList(); + if (commandParametrizedTypes.size() != fallbackParametrizedTypes.size()) { + return Collections.singletonList("a different set of parametrized types, command: " + commandParametrizedTypes.size() + + " fallback: " + fallbackParametrizedTypes.size()); + } + + for (int i = 0; i < commandParametrizedTypes.size(); i++) { + Type commandParametrizedType = commandParametrizedTypes.get(i); + Type fallbackParametrizedType = fallbackParametrizedTypes.get(i); + if (!commandParametrizedType.equals(fallbackParametrizedType)) { + if (Collections.emptyList() == msg) { + msg = new ArrayList(); + } + msg.add("wrong parametrized type. Expected: '" + commandParametrizedType + "' but in fallback '" + + fallbackParametrizedType + "', position: " + i); + return msg; + } + } + + return msg; + } + } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java index 4e9101a06..8ba2c34b4 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/MethodProvider.java @@ -55,6 +55,15 @@ public FallbackMethod getFallbackMethod(Class type, Method commandMethod) { return getFallbackMethod(type, commandMethod, false); } + /** + * Gets fallback method for command method. + * + * @param type type + * @param commandMethod the command method. in the essence it can be a fallback + * method annotated with HystrixCommand annotation that has a fallback as well. + * @param extended true if the given commandMethod was derived using additional parameter, otherwise - false + * @return new instance of {@link FallbackMethod} or {@link FallbackMethod#ABSENT} if there is no suitable fallback method for the given command + */ public FallbackMethod getFallbackMethod(Class type, Method commandMethod, boolean extended) { if (commandMethod.isAnnotationPresent(HystrixCommand.class)) { HystrixCommand hystrixCommand = commandMethod.getAnnotation(HystrixCommand.class); @@ -86,6 +95,16 @@ public Optional getMethod(Class type, String name, Class... parame } } + /** + * Finds generic method for the given bridge method. + * + * @param bridgeMethod the bridge method + * @param aClass the type where the bridge method is declared + * @return generic method + * @throws IOException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ public Method unbride(final Method bridgeMethod, Class aClass) throws IOException, NoSuchMethodException, ClassNotFoundException { if (bridgeMethod.isBridge() && bridgeMethod.isSynthetic()) { if (cache.containsKey(bridgeMethod)) { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java new file mode 100644 index 000000000..3b117ee21 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/TypeHelper.java @@ -0,0 +1,86 @@ +/** + * Copyright 2015 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.utils; + +import com.google.common.collect.TreeTraverser; +import org.apache.commons.lang3.Validate; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Helper class that provides convenient methods to work with java types. + *

+ * Created by dmgcodevil. + */ +public final class TypeHelper { + private TypeHelper() { + } + + + /** + * Check whether return type of the given method is parametrized or not. + * + * @param method the method + * @return true - if return type is {@link ParameterizedType}, otherwise - false + */ + public static boolean isReturnTypeParametrized(Method method) { + return method.getGenericReturnType() instanceof ParameterizedType; + } + + + /** + * Unwinds parametrized type into plain list that contains all parameters for the given type including nested parameterized types, + * for example calling the method for the following type + * + * GType>, Parent>>> + * + * will return list of 8 elements: + * + * [GType, GType, GDoubleType, GType, GDoubleType, Parent, Parent, Parent] + * + * if the given type is not parametrized then returns list with one element which is given type passed into method. + * + * @param type the parameterized type + * @return list of {@link Type} + */ + @ParametersAreNonnullByDefault + public static List getAllParameterizedTypes(Type type) { + Validate.notNull(type, "type cannot be null"); + List types = new ArrayList(); + TreeTraverser typeTraverser = new TreeTraverser() { + @Override + public Iterable children(Type root) { + if (root instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) root; + return Arrays.asList(pType.getActualTypeArguments()); + + } + return Collections.emptyList(); + } + }; + for (Type t : typeTraverser.breadthFirstTraversal(type)) { + types.add(t); + } + return types; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java index 476d5f594..6c72a17c6 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/observable/BasicObservableTest.java @@ -3,15 +3,17 @@ import com.netflix.hystrix.HystrixEventType; import com.netflix.hystrix.HystrixRequestLog; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.command.ObservableResult; +import com.netflix.hystrix.contrib.javanica.annotation.ObservableExecutionMode; import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; import com.netflix.hystrix.contrib.javanica.test.common.domain.User; -import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; import rx.Observable; import rx.Observer; +import rx.Subscriber; import rx.functions.Action1; +import rx.subjects.ReplaySubject; import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; import static org.junit.Assert.assertEquals; @@ -34,9 +36,11 @@ public void setUp() throws Exception { } @Test - public void testGetUserByIdObservable() { + public void testGetUserByIdSuccess() { // blocking - assertEquals("name: 1", userService.getUser("1", "name: ").toBlocking().single().getName()); + Observable observable = userService.getUser("1", "name: "); + assertObservableExecutionMode(observable, ObservableExecutionMode.EAGER); + assertEquals("name: 1", observable.toBlocking().single().getName()); // non-blocking // - this is a verbose anonymous inner-class approach and doesn't do assertions @@ -73,40 +77,140 @@ public void call(User user) { assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } + + @Test + public void testGetUserWithRegularFallback() { + final User exUser = new User("def", "def"); + Observable userObservable = userService.getUserRegularFallback(" ", ""); + assertObservableExecutionMode(userObservable, ObservableExecutionMode.LAZY); + // blocking + assertEquals(exUser, userObservable.toBlocking().single()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserRegularFallback"); + // confirm that command has failed + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } + @Test - public void testGetUserWithFallback() { + public void testGetUserWithRxFallback() { final User exUser = new User("def", "def"); // blocking - assertEquals(exUser, userService.getUser(" ", "").toBlocking().single()); + assertEquals(exUser, userService.getUserRxFallback(" ", "").toBlocking().single()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); - com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUser"); + com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("getUserRxFallback"); // confirm that command has failed assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that fallback was successful assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } + @Test + public void testGetUserWithRxCommandFallback() { + final User exUser = new User("def", "def"); + + // blocking + Observable userObservable = userService.getUserRxCommandFallback(" ", ""); + assertObservableExecutionMode(userObservable, ObservableExecutionMode.LAZY); + assertEquals(exUser, userObservable.toBlocking().single()); + assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); + com.netflix.hystrix.HystrixInvokableInfo getUserRxCommandFallback = getHystrixCommandByKey("getUserRxCommandFallback"); + com.netflix.hystrix.HystrixInvokableInfo rxCommandFallback = getHystrixCommandByKey("rxCommandFallback"); + // confirm that command has failed + assertTrue(getUserRxCommandFallback.getExecutionEvents().contains(HystrixEventType.FAILURE)); + assertTrue(getUserRxCommandFallback.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + // and that fallback command was successful + assertTrue(rxCommandFallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } + + + private static void assertObservableExecutionMode(Observable observable, ObservableExecutionMode mode) { + // todo find better way to figure it out + boolean eager = observable instanceof ReplaySubject; + if (ObservableExecutionMode.EAGER == mode) { + if (!eager) { + throw new AssertionError("observable must be instance of ReplaySubject"); + } + } else { + if (eager) { + throw new AssertionError("observable must not be instance of ReplaySubject"); + } + } + } + public static class UserService { - @HystrixCommand(fallbackMethod = "staticFallback") + private User regularFallback(String id, String name) { + return new User("def", "def"); + } + + private Observable rxFallback(String id, String name) { + return Observable.just(new User("def", "def")); + } + + @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER) + private Observable rxCommandFallback(String id, String name, Throwable throwable) { + if (throwable instanceof GetUserException && "getUserRxCommandFallback has failed".equals(throwable.getMessage())) { + return Observable.just(new User("def", "def")); + } else { + throw new IllegalStateException(); + } + + } + + @HystrixCommand public Observable getUser(final String id, final String name) { - return new ObservableResult() { + validate(id, name, "getUser has failed"); + return createObservable(id, name); + } + + @HystrixCommand(fallbackMethod = "regularFallback", observableExecutionMode = ObservableExecutionMode.LAZY) + public Observable getUserRegularFallback(final String id, final String name) { + validate(id, name, "getUser has failed"); + return createObservable(id, name); + } + + @HystrixCommand(fallbackMethod = "rxFallback") + public Observable getUserRxFallback(final String id, final String name) { + validate(id, name, "getUserRxFallback has failed"); + return createObservable(id, name); + } + + @HystrixCommand(fallbackMethod = "rxCommandFallback", observableExecutionMode = ObservableExecutionMode.LAZY) + public Observable getUserRxCommandFallback(final String id, final String name) { + validate(id, name, "getUserRxCommandFallback has failed"); + return createObservable(id, name); + } + + + private Observable createObservable(final String id, final String name) { + return Observable.create(new Observable.OnSubscribe() { @Override - public User invoke() { - validate(id, name); - return new User(id, name + id); + public void call(Subscriber observer) { + try { + if (!observer.isUnsubscribed()) { + observer.onNext(new User(id, name + id)); + observer.onCompleted(); + } + } catch (Exception e) { + observer.onError(e); + } } - }; + }); } - private User staticFallback(String id, String name) { - return new User("def", "def"); + private void validate(String id, String name, String errorMsg) { + if (StringUtils.isBlank(id) || StringUtils.isBlank(name)) { + throw new GetUserException(errorMsg); + } } - private void validate(String id, String name) { - Validate.notBlank(id); - Validate.notBlank(name); + private static final class GetUserException extends RuntimeException { + public GetUserException(String message) { + super(message); + } } } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java new file mode 100644 index 000000000..f26bd2b78 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodTest.java @@ -0,0 +1,103 @@ +package com.netflix.hystrix.contrib.javanica.util; + +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Method; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Created by dmgcodevil. + */ +@RunWith(DataProviderRunner.class) +public class FallbackMethodTest { + + @Test + public void testGetExtendedFallback() throws NoSuchMethodException { + // given + Method command = Service.class.getDeclaredMethod("command", String.class, Integer.class); + // when + Method extFallback = MethodProvider.getInstance().getFallbackMethod(Service.class, command).getMethod(); + // then + assertParamsTypes(extFallback, String.class, Integer.class, Throwable.class); + } + + @Test + @DataProvider({"true", "false"}) + public void testGetFallbackForExtendedCommand(boolean extended) throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommand", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, extended).getMethod(); + // then + assertParamsTypes(fallback, String.class, Integer.class, Throwable.class); + } + + public void testGetFallbackForExtendedCommandV2() throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommandV2", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, true).getMethod(); + // then + assertParamsTypes(fallback, String.class, Integer.class); + } + + public void testGetFallbackForExtendedCommandV2_extendedParameterFalse() throws NoSuchMethodException { + // given + Method extFallback = Service.class.getDeclaredMethod("extCommandV2", String.class, Integer.class, Throwable.class); + // when + Method fallback = MethodProvider.getInstance().getFallbackMethod(Service.class, extFallback, false).getMethod(); + // then + assertNull(fallback); + } + + + private static void assertParamsTypes(Method method, Class... expected) { + assertEquals(expected.length, method.getParameterTypes().length); + Class[] actual = method.getParameterTypes(); + assertArrayEquals(expected, actual); + } + + private static class Common { + private String fallback(String s, Integer i) { + return null; + } + + private String fallbackV2(String s, Integer i) { + return null; + } + } + + private static class Service extends Common{ + + @HystrixCommand(fallbackMethod = "fallback") + public String command(String s, Integer i) { + return null; + } + + @HystrixCommand(fallbackMethod = "fallback") + public String extCommand(String s, Integer i, Throwable throwable) { + return null; + } + + + @HystrixCommand(fallbackMethod = "fallbackV2") + public String extCommandV2(String s, Integer i, Throwable throwable) { + return null; + } + + + public String fallback(String s, Integer i, Throwable throwable) { + return null; + } + + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java new file mode 100644 index 000000000..513475fba --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/FallbackMethodValidationTest.java @@ -0,0 +1,166 @@ +package com.netflix.hystrix.contrib.javanica.util; + +import com.google.common.base.Throwables; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; +import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import rx.Observable; + +import java.lang.reflect.Method; +import java.util.concurrent.Future; + +/** + * Created by dmgcodevil. + */ +@RunWith(DataProviderRunner.class) +public class FallbackMethodValidationTest { + + + @DataProvider + public static Object[][] fail() { + // @formatter:off + return new Object[][]{ + // sync execution + {getMethod("commandReturnPlainTypeLong"), getMethod("fallbackReturnPlainTypeString")}, + {getMethod("commandReturnPlainTypeChild"), getMethod("fallbackReturnPlainTypeParent")}, + {getMethod("commandReturnGenericTypeParent"), getMethod("fallbackReturnGenericTypeChild")}, + {getMethod("commandReturnGenericTypeChild"), getMethod("fallbackReturnGenericTypeParent")}, + {getMethod("commandReturnGenericTypeChildParent"), getMethod("fallbackReturnGenericTypeParentChild")}, + {getMethod("commandReturnGenericTypeParentChild"), getMethod("fallbackReturnGenericTypeChildParent")}, + {getMethod("commandReturnGenericNestedTypeParentChildParent"), getMethod("commandReturnGenericNestedTypeParentParentParent")}, + + // async execution + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnFutureChild")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnFutureParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnFutureParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnFutureParent")}, + + // observable execution + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackReturnObservableChild")}, + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackCommandReturnObservableChild")}, + {getMethod("fallbackReturnObservableParent"), getMethod("fallbackReturnChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnObservableParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnObservableParent")}, + {getMethod("commandReturnParent"), getMethod("fallbackReturnObservableChild")}, + {getMethod("commandReturnParent"), getMethod("fallbackCommandReturnObservableChild")}, + }; + // @formatter:on + } + + @DataProvider + public static Object[][] success() { + // @formatter:off + return new Object[][]{ + // sync execution + {getMethod("commandReturnPlainTypeLong"), getMethod("fallbackReturnPlainTypeLong")}, + {getMethod("commandReturnPlainTypeParent"), getMethod("fallbackReturnPlainTypeChild")}, + {getMethod("commandReturnPlainTypeParent"), getMethod("fallbackReturnPlainTypeParent")}, + {getMethod("commandReturnGenericTypeChild"), getMethod("fallbackReturnGenericTypeChild")}, + {getMethod("commandReturnGenericNestedTypeParentChildParent"), getMethod("fallbackReturnGenericNestedTypeParentChildParent")}, + + + // async execution + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnFutureParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackCommandReturnParent")}, + {getMethod("commandReturnFutureParent"), getMethod("fallbackReturnParent")}, + + // observable execution + {getMethod("commandReturnObservableParent"), getMethod("fallbackReturnObservableParent")}, + {getMethod("commandReturnObservableParent"), getMethod("fallbackCommandReturnObservableParent")}, + {getMethod("commandReturnObservableParent"), getMethod("fallbackReturnParent")}, + + }; + // @formatter:on + } + + @Test(expected = FallbackDefinitionException.class) + @UseDataProvider("fail") + public void testValidateBadFallbackReturnType(Method commandMethod, Method fallbackMethod) { + new FallbackMethod(fallbackMethod).validateReturnType(commandMethod); + } + + @UseDataProvider("success") + public void testValidateCorrectFallbackReturnType(Method commandMethod, Method fallbackMethod) { + new FallbackMethod(fallbackMethod).validateReturnType(commandMethod); + } + + private static Method getMethod(String name) { + try { + return Service.class.getDeclaredMethod(name); + } catch (NoSuchMethodException e) { + throw Throwables.propagate(e); + } + } + + // @formatter:off + private static class Service { + // Sync execution + public Parent commandReturnPlainTypeParent() {return null;} + public Child commandReturnPlainTypeChild() {return null;} + public Parent fallbackReturnPlainTypeParent() {return null;} + public Child fallbackReturnPlainTypeChild() {return null;} + public Long commandReturnPlainTypeLong() {return null;} + public Long fallbackReturnPlainTypeLong() {return null;} + public String fallbackReturnPlainTypeString() {return null;} + public GType commandReturnGenericTypeParent() {return null;} + public GType commandReturnGenericTypeChild() {return null;} + public GType fallbackReturnGenericTypeParent() {return null;} + public GType fallbackReturnGenericTypeChild() {return null;} + public GDoubleType commandReturnGenericTypeParentChild() {return null;} + public GDoubleType commandReturnGenericTypeChildParent() {return null;} + public GDoubleType fallbackReturnGenericTypeParentChild() {return null;} + public GDoubleType fallbackReturnGenericTypeChildParent() {return null;} + public GType>, Parent>>> commandReturnGenericNestedTypeParentChildParent() {return null;} + public GType>, Parent>>> commandReturnGenericNestedTypeParentParentParent() {return null;} + public GType>, Parent>>> fallbackReturnGenericNestedTypeParentChildParent() {return null;} + + // Async execution + Future commandReturnFutureParent() {return null;} + Parent commandReturnParent() {return null;} + + Parent fallbackReturnParent() {return null;} + Child fallbackReturnChild() {return null;} + Future fallbackReturnFutureParent() {return null;} + Future fallbackReturnFutureChild() {return null;} + + @HystrixCommand Parent fallbackCommandReturnParent() {return null;} + @HystrixCommand Child fallbackCommandReturnChild() {return null;} + @HystrixCommand Future fallbackCommandReturnFutureParent() {return null;} + @HystrixCommand Future fallbackCommandReturnFutureChild() {return null;} + + // Observable execution + Observable commandReturnObservableParent() {return null;} + + Observable fallbackReturnObservableParent() {return null;} + Observable fallbackReturnObservableChild() {return null;} + + @HystrixCommand Observable fallbackCommandReturnObservableParent() {return null;} + @HystrixCommand Observable fallbackCommandReturnObservableChild() {return null;} + } + // @formatter:on + + + + + private interface GType { + } + + private interface GDoubleType { + + } + + private static class Parent { + + } + + private static class Child extends Parent { + + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java new file mode 100644 index 000000000..9f6dc142b --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Child.java @@ -0,0 +1,7 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil. + */ +public class Child extends Parent { +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java new file mode 100644 index 000000000..2f911b16a --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterface.java @@ -0,0 +1,10 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil + */ +public interface GenericInterface { + + + R foo(P1 p1); +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java new file mode 100644 index 000000000..0d50fb3c2 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/GenericInterfaceImpl.java @@ -0,0 +1,22 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil + */ +public class GenericInterfaceImpl implements GenericInterface { + + + public Child foo(SubChild c) { + return null; + } + + @Override + public Child foo(Child c) { + return null; + } + + public Child foo(Parent c) { + return null; + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java new file mode 100644 index 000000000..979dd04a1 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/Parent.java @@ -0,0 +1,5 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + + +public class Parent { +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java new file mode 100644 index 000000000..6d5b2a494 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/SubChild.java @@ -0,0 +1,7 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + +/** + * Created by dmgcodevil. + */ +public class SubChild extends Child { +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java new file mode 100644 index 000000000..d10c3a8d2 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/util/bridge/UnbridgeMethodTest.java @@ -0,0 +1,49 @@ +package com.netflix.hystrix.contrib.javanica.util.bridge; + +import com.netflix.hystrix.contrib.javanica.utils.MethodProvider; +import org.junit.Test; + +import java.io.IOException; +import java.lang.reflect.Method; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Created by dmgcodevil + */ +public class UnbridgeMethodTest { + + @Test + public void testUnbridgeFoo() throws NoSuchMethodException, IOException, ClassNotFoundException { + // given + Method bridgeMethod = getBridgeMethod(GenericInterfaceImpl.class, "foo"); + assertNotNull(bridgeMethod); + // when + Method genMethod = MethodProvider.getInstance().unbride(bridgeMethod, GenericInterfaceImpl.class); + // then + assertNotNull(bridgeMethod); + assertReturnType(Child.class, genMethod); + assertParamsTypes(genMethod, Child.class); + } + + private static Method getBridgeMethod(Class type, String methodName) { + for (Method method : type.getDeclaredMethods()) { + if (method.isBridge() && method.getName().equals(methodName)) { + return method; + } + } + return null; + } + + private static void assertReturnType(Class expected, Method method) { + assertEquals(expected, method.getReturnType()); + } + + private static void assertParamsTypes(Method method, Class... expected) { + assertEquals(expected.length, method.getParameterTypes().length); + Class[] actual = method.getParameterTypes(); + assertArrayEquals(expected, actual); + } +}