diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 2906ceb64..494adcdd5 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -40,8 +40,69 @@ More about Spring AOP + AspectJ read [here] (http://docs.spring.io/spring/docs/3 # How to use ## Hystrix command +### Synchronous Execution + +To run method as Hystrix command synchronously you need to annotate method with `@HystrixCommand` annotation, for example +```java +public class UserService { +... + @HystrixCommand + public User getUserById(String id) { + return userResource.getUserById(id); + } +} +... +``` +In example above the `getUserById` method will be processed [synchronously](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution) within new Hystrix command. +By default the name of **command key** is command method name: `getUserById`, default **group key** name is class name of annotated method: `UserService`. You can change it using necessary `@HystrixCommand` properties: + +```java + @HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand") + public User getUserById(String id) { + return userResource.getUserById(id); + } +``` +To set threadPoolKey use ```@HystrixCommand#threadPoolKey()``` + +### Asynchronous Execution + +To process Hystrix command asynchronously you should return an instance of `AsyncResult` in your command method as in the exapmple below: +```java + @HystrixCommand + public Future getUserByIdAsync(final String id) { + return new AsyncResult() { + @Override + public User invoke() { + return userResource.getUserById(id); + } + }; + } +``` + +The return type of command method should be Future that indicates that a command should be executed [asynchronously] (https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Asynchronous-Execution). + +## Reactive Execution + +To performe "Reactive Execution" you should return an instance of `ObservableResult` 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); + } + }; + } +``` + +The return type of command method should be `Observable`. + +## Fallback + +Graceful degradation can be achieved by declaring name of fallback method in `@HystrixCommand` like below: -To run method as Hystrix command you need to annotate method with @HystrixCommand annotation, for example ```java @HystrixCommand(fallbackMethod = "defaultUser") public User getUserById(String id) { @@ -49,10 +110,13 @@ To run method as Hystrix command you need to annotate method with @HystrixComman } private User defaultUser(String id) { - return new User(); + return new User("def", "def"); } ``` -In example above the 'getUserById' method will be processed [synchronously](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution) as Hystrix command. Fallback method can be private or public. Method 'defaultUser' will be used to process fallback logic in a case of any errors. If you need to run 'defaultUser' as command then you can annotate it with HystrixCommand annotation as below: + +**_Its important to remember that Hystrix command and fallback should be placed in the same class and have same method signature_**. + +Fallback method can have any access modifier. Method `defaultUser` will be used to process fallback logic in a case of any errors. If you need to run fallback method `defaultUser` as separate Hystrix command then you need to annotate it with `HystrixCommand` annotation as below: ```java @HystrixCommand(fallbackMethod = "defaultUser") public User getUserById(String id) { @@ -65,7 +129,7 @@ In example above the 'getUserById' method will be processed [synchronously](http } ``` -If fallback method was marked with @HystrixCommand then this method also can has own fallback method, as in the example below: +If fallback method was marked with `@HystrixCommand` then this fallback method (_defaultUser_) also can has own fallback method, as in the example below: ```java @HystrixCommand(fallbackMethod = "defaultUser") public User getUserById(String id) { @@ -83,28 +147,43 @@ If fallback method was marked with @HystrixCommand then this method also can has } ``` -Its important to remember that Hystrix command and fallback should be placed in the same class. +## 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`. -To process Hystrix command asynchronously you should return an instance of AsyncCommand in your command method as in the exapmple below: ```java - @HystrixCommand - public Future getUserByIdAsync(final String id) { - return new AsyncCommand() { - @Override - public User invoke() { - return userResource.getUserById(id); - } - }; + @HystrixCommand(ignoreExceptions = {BadRequestException.class}) + public User getUserById(String id) { + return userResource.getUserById(id); } ``` -The return type of command method should be Future that indicates that a command should be executed [asynchronously] (https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Asynchronous-Execution). +If `userResource.getUserById(id);` throws an exception which type is _BadRequestException_ then this exception will be wrapped in `HystrixBadRequestException` and will not affect metrics and will not trigger fallback logic. -Command properties can be set using commandProperties field like below: +## Request Cache + +Request caching is enabled by defining the _get cache key_ method like in example below: ```java + @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") + public User getUserById(String id) { + return userResource.getUserById(id); + } + + private String getUserIdCacheKey(String id){ + return id; + } +``` + +If "getUserIdCacheKey" returns `null` then the cache key will be "null" (string). + +**_Its important to remember that Hystrix command and "get cache key" method should be placed in the same class and have same method signature_**. - @HystrixCommand(fallbackMethod = "defaultUser", - commandProperties = { +## Configuration +### Command Properties + +Command properties can be set using @HystrixCommand's 'commandProperties' like below: + +```java + @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500") }) public User getUserById(String id) { @@ -112,58 +191,98 @@ Command properties can be set using commandProperties field like below: } ``` - -Javanica sets command properties using Hystrix ConfigurationManager. +Javanica dynamically sets properties using Hystrix ConfigurationManager. For the example above Javanica behind the scenes performs next action: ```java ConfigurationManager.getConfigInstance().setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500"); ``` - More about Hystrix command properties [command](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandExecution) and [fallback](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandFallback) ## Hystrix collapser -Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use HystrixCollapser annotation. +Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use ```@HystrixCollapser``` annotation. -Hystrix command: +**Asynchronous call** ```java - @HystrixCommand - public User getUserById(String id) { - return userResource.getUserById(id); - } + @HystrixCommand + @HystrixCollapser + public Future getUserAsync(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + return new User(id, name + id); // there should be a network call + } + }; + } ``` -Asynchronous call: -```java - @HystrixCollapser(commandMethod = "getUserById", - collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) - public Future getUserByIdCollapserAsync(String id) { - return CollapserResult.async(); - } -``` +**Synchronous call** +_Note_ : Request collapsing on a single thread makes no sense, because if a single thread is invoking ```execute()``` or ```queue().get()``` synchronously it will block and wait for a response and thus not submit any further requests that will be collapsed inside the window. Doing requests on a separate threads (such as user requests with collapsing scope set to GLOBAL) would make sense: -Synchronous call: ```java - @HystrixCollapser(commandMethod = "getUserById", - collapserProperties = {@HystrixProperty(name = "maxRequestsInBatch", value = "3")}) - @Override - public User getUserByIdCollapser(String id) { - return CollapserResult.sync(); - } + @HystrixCommand + @HystrixCollapser(scope = GLOBAL) + public User getUserSync(String id, String name) { + return new User(id, name + id); // there should be a network call + } ``` -Lines: +To set collapser [properties](https://github.com/Netflix/Hystrix/wiki/Configuration#Collapser) use `@HystrixCollapser#collapserProperties` + +Read more about Hystrix request collapsing [here] (https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-RequestCollapsing) + +**Collapser error processing** + +All commands are collapsed are instances of ```BatchHystrixCommand```. In BatchHystrixCommand the ```getFallback()``` isn't implemented, even if a collapsed command has fallback ``` @HystrixCommand(fallbackMethod = "fallbackCommand")``` then it never be processed in ```getFallback()```, below this moment is consecrated in detail. + +If the processing of a request fails then other requests will not be processed within this collapser and ```setException``` method will be automatically called on requests instances. Generally this is an all or nothing affair. For example, a timeout or rejection of the HystrixCommand would cause failure on all of the batched calls. Thus existence of fallback method does not eliminate the break of all requests and the collapser as a whole. Thus there are two scenarios that you can change using ```@HystrixCollapser#fallbackEnabled``` annotation property. + +When one of the requests has failed: + +**Scenario #1** (```@HystrixCollapser(fallbackEnabled = false)```): + +All requests that were processed successfully will be returned to an user and will be available from Future, other requests including one that failed will not. The fallback of collapsed command will not be executed in a case of any errors. +Short example: ```java - return CollapserResult.async(); - return CollapserResult.sync(); + + @HystrixCommand(fallbackMethod = "fallback") + @HystrixCollapser(fallbackEnabled = false) + public Future getUserAsync(final String id, final String name) { + // some logic here + }; + + // ....... + + + Future f1 = userService.getUserAsync("1", "name: "); + Future f2 = userService.getUserAsync("2", "name: "); + Future f3 = userService.getUserAsync("not found", "name"); // not found, exception here + Future f4 = userService.getUserAsync("4", "name: "); // will not be processed + Future f5 = userService.getUserAsync("5", "name: "); // will not be processed + System.out.println(f1.get().getName()); // this line will be executed + System.out.println(f2.get().getName()); // this line will be executed + System.out.println(f3.get().getName()); // this line will not be executed + System.out.println(f4.get().getName()); // this line will not be executed + System.out.println(f5.get().getName()); // this line will not be executed ``` -In examples above don't affect the result of a collapser call. This just to avoid ```return null;``` statement in code. -It's important to remember that Hystrix command and сollapser should be placed in the same class. -Read more about Hystrix request collapsing [here] (https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-RequestCollapsing) +**Scenario #2** (```@HystrixCollapser(fallbackEnabled = true)```): + +All requests that were processed successfully or failed will be returned to an user and will be available from a Future instance. For failed requests the collapsed command's fallback method will be executed, of course if the name of fallback method was defined in ```@HystrixCommand```. The fallback of collapsed command will be used to process any exceptions during batch processing. If the fallback method hasn't ```@HystrixCommand``` annotation then this method will be processed in the a single thread with the BatchHystrixCommand in method _run()_, otherwise fallback will be process as Hystrix command in separate thread if fallback method was annotated with ```@HystrixCommand```. This is important point that you need to pay attention if you want to use fallback for your collapsed command. Thus fallback method will be processed in ```BatchHystrixCommand#run()``` not in ```BatchHystrixCommand#getFallback()```, but fallback can be process as Hystrix command therefore set the ```fallbackEnabled``` as true can be useful in some situations to avoid crash of whole collapser and continue to process requests, but I recommend to use ```@HystrixCollapser(fallbackEnabled = false)``` and give preference to the first scenario as this is the correct behavior in most cases. + #Development Status and Future +Todo list: + +1. Add new annotation that can be applied for types in order to define default command properties for all commands that are specified in a class. For example specify default group key or default properties for all commands. +2.Add Cache annotation to mark some parameters which should participate in building cache key, for example: ```java -todo -``` \ No newline at end of file +@HystrixCommand +User getUserByParams(@Cache String id, String name, @Cache Integer age) +``` +`toString()` method will be invoked for parameters: id and age to build cache key: +Pseudo code +``` +String cacheKey = id.toString()+age.toString() +``` diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java index 6a0085d9c..aee478488 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCollapser.java @@ -24,7 +24,8 @@ import java.lang.annotation.Target; /** - * This annotation is used to mark some methods to collapse some commands into a single backend dependency call. + * This annotation is used to collapse some commands into a single backend dependency call. + * This annotation should be used together with {@link HystrixCommand} annotation. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -50,17 +51,19 @@ Scope scope() default Scope.REQUEST; /** - * Specifies name of a method which has @HystrixCommand annotation and should be collapsed. + * Specifies collapser properties. * - * @return method name. + * @return collapser properties */ - String commandMethod(); + HystrixProperty[] collapserProperties() default {}; /** - * Specifies collapser properties. + * Used to enabled fallback logic for a batch hystrix command. + *

+ * default => the false * - * @return collapser properties + * @return true if processing of a fallback is allowed */ - HystrixProperty[] collapserProperties() default {}; + boolean fallbackEnabled() default false; } 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 b1239882e..83386b9d2 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 @@ -83,11 +83,28 @@ */ String fallbackMethod() default ""; + /** + * Method name to be used to get a key for request caching. + * The command and get cache key method should be placed in the same class. + *

+ * By default this returns empty string which means "do not cache". + * + * @return method name or empty string + */ + String cacheKeyMethod() default ""; + /** * Specifies command properties. * * @return command properties */ HystrixProperty[] commandProperties() default {}; + + /** + * Defines exceptions which should be ignored and wrapped to throw in HystrixBadRequestException. + * + * @return exceptions to ignore + */ + Class[] ignoreExceptions() default {}; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCollapserAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCollapserAspect.java deleted file mode 100644 index e536adc06..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCollapserAspect.java +++ /dev/null @@ -1,77 +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.aop.aspectj; - -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.MetaHolder; - -import java.lang.reflect.Method; -import java.util.concurrent.Future; - -import org.apache.commons.lang3.Validate; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; - -import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; - - -/** - * AspectJ aspect to process methods which annotated with {@link HystrixCollapser} annotation. - */ -@Aspect -public class HystrixCollapserAspect { - - @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)") - public void hystrixCollapserAnnotationPointcut() { - } - - @Around("hystrixCollapserAnnotationPointcut()") - public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable { - HystrixCollapser hystrixCollapser; - Method collapserMethod = getMethodFromTarget(joinPoint); - Object obj = joinPoint.getTarget(); - Object[] args = joinPoint.getArgs(); - - Validate.notNull(collapserMethod, "failed to get collapser method from joinPoint: %s", joinPoint); - hystrixCollapser = collapserMethod.getAnnotation(HystrixCollapser.class); - - - Method commandMethod = getMethodFromTarget(joinPoint, hystrixCollapser.commandMethod()); - - HystrixCommand hystrixCommand = commandMethod.getAnnotation(HystrixCommand.class); - Validate.notNull(hystrixCommand, "collapser should refer to method which was annotated with @HystrixCommand"); - - MetaHolder metaHolder = MetaHolder.builder() - .args(args) - .method(commandMethod) - .obj(obj) - .hystrixCollapser(hystrixCollapser) - .hystrixCommand(hystrixCommand) - .defaultCommandKey(commandMethod.getName()) - .defaultCollapserKey(collapserMethod.getName()) - .defaultGroupKey(obj.getClass().getSimpleName()).build(); - if (collapserMethod.getReturnType().isAssignableFrom(Future.class)) { - return new CommandCollapser(metaHolder).queue(); - } else { - return new CommandCollapser(metaHolder).execute(); - } - } - -} 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 2031a5cde..4d9b82112 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java @@ -15,12 +15,10 @@ */ package com.netflix.hystrix.contrib.javanica.aop.aspectj; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.command.AsyncCommand; -import com.netflix.hystrix.contrib.javanica.command.GenericCommand; -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.collapser.CommandCollapser; +import com.netflix.hystrix.contrib.javanica.command.*; import org.apache.commons.lang3.Validate; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -28,10 +26,8 @@ import org.aspectj.lang.annotation.Pointcut; import java.lang.reflect.Method; -import java.util.concurrent.Future; import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; -import static org.slf4j.helpers.MessageFormatter.format; /** * AspectJ aspect to process methods which annotated with {@link HystrixCommand} annotation. @@ -39,9 +35,6 @@ @Aspect public class HystrixCommandAspect { - private static final String ERROR_TYPE_MESSAGE = "return statement of '{}' method should returns instance of AsyncCommand class"; - private static final String INVOKE_METHOD = "invoke"; - @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") public void hystrixCommandAnnotationPointcut() { } @@ -49,46 +42,28 @@ public void hystrixCommandAnnotationPointcut() { @Around("hystrixCommandAnnotationPointcut()") public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable { - HystrixCommand hystrixCommand; Method method = getMethodFromTarget(joinPoint); Object obj = joinPoint.getTarget(); Object[] args = joinPoint.getArgs(); Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); - hystrixCommand = method.getAnnotation(HystrixCommand.class); - - Object asyncObj = null; - Method asyncMethod = null; - boolean async = false; - if (method.getReturnType().isAssignableFrom(Future.class)) { - asyncObj = method.invoke(obj, args); // creates instance - if (!isAsyncCommand(asyncObj)) { - throw new RuntimeException(format(ERROR_TYPE_MESSAGE, method.getName()).getMessage()); - } - asyncMethod = asyncObj.getClass().getMethod(INVOKE_METHOD); - async = true; - } - - HystrixCommandFactory genericCommandHystrixCommandFactory = new GenericHystrixCommandFactory(); + HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); + HystrixCollapser hystrixCollapser = method.getAnnotation(HystrixCollapser.class); + ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); + Method cacheKeyMethod = getMethodFromTarget(joinPoint, hystrixCommand.cacheKeyMethod()); MetaHolder metaHolder = MetaHolder.builder() - .async(async) - .args(args) - .method(method) - .asyncMethod(asyncMethod) - .asyncObj(asyncObj) - .obj(obj) - .hystrixCommand(hystrixCommand) + .args(args).method(method).obj(obj).proxyObj(joinPoint.getThis()) + .cacheKeyMethod(cacheKeyMethod).executionType(executionType) + .hystrixCommand(hystrixCommand).hystrixCollapser(hystrixCollapser) .defaultCommandKey(method.getName()) + .defaultCollapserKey(method.getName()) .defaultGroupKey(obj.getClass().getSimpleName()).build(); - GenericCommand genericCommand = genericCommandHystrixCommandFactory.create(metaHolder, null); - if (async) { - return genericCommand.queue(); + if (hystrixCollapser != null) { + CommandCollapser commandCollapser = new CommandCollapser(metaHolder); + return CommandExecutor.execute(commandCollapser, executionType); } else { - return genericCommand.execute(); + GenericCommand genericCommand = GenericHystrixCommandFactory.getInstance().create(metaHolder, null); + return CommandExecutor.execute(genericCommand, executionType); } } - private boolean isAsyncCommand(Object instance) { - return instance instanceof AsyncCommand; - } - } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CollapserResult.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CollapserResult.java deleted file mode 100644 index 23a9a6d2f..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/collapser/CollapserResult.java +++ /dev/null @@ -1,72 +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.collapser; - -import org.apache.commons.lang3.concurrent.ConcurrentUtils; - -import java.util.concurrent.Future; - -/** - * Used as stub in return statement of a collapser method. It doesn't affect the result of a collapser call. - * This stub is used just to avoid return null statement in code. - * Example (asynchronous): - *

- * - * @HystrixCollapser(commandMethod = "commandMethod") - * public Future asynchronous(String param) { return CollapserResult.async(); } - * - *

- * Example (synchronous): - *

- * - * @HystrixCollapser(commandMethod = "commandMethod") - * public Result synchronous(String param) { return CollapserResult.sync(); } - * - *

- * Hystrix command: - *

- * - * @HystrixCommand - * public Result commandMethod(String param) { return new Result(param); } - * - */ -public final class CollapserResult { - - private CollapserResult() { - throw new UnsupportedOperationException("It's prohibited to create instances of the class."); - } - - /** - * This method is used in synchronous calls. - * - * @param the result type - * @return null - */ - public static T sync() { - return null; - } - - /** - * This method is used in asynchronous calls. - * - * @param the result type - * @return fake instance of Future - */ - public static Future async() { - return ConcurrentUtils.constantFuture(null); - } - -} \ No newline at end of file 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 b46058e6c..c37a9fb98 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 @@ -18,15 +18,17 @@ import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.HystrixCollapserKey; import com.netflix.hystrix.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommand; import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommandFactory; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; import java.util.Collection; import java.util.List; +import static org.slf4j.helpers.MessageFormatter.arrayFormat; + /** * Collapses multiple requests into a single {@link HystrixCommand} execution based * on a time window and optionally a max batch size. @@ -35,6 +37,11 @@ public class CommandCollapser extends HystrixCollapser, Object, Obj private MetaHolder metaHolder; + private static final String ERROR_MSG = "Failed to map all collapsed requests to response. " + + "The expected contract has not been respected. "; + + private static final String ERROR_MSF_TEMPLATE = "Collapser key: '{}', requests size: '{}', response size: '{}'"; + /** * Constructor with parameters. * @@ -64,7 +71,9 @@ public Object getRequestArgument() { @Override protected HystrixCommand> createCommand( Collection> collapsedRequests) { - return BatchHystrixCommandFactory.getInstance().create(metaHolder, collapsedRequests); + BatchHystrixCommand command = BatchHystrixCommandFactory.getInstance().create(metaHolder, collapsedRequests); + command.setFallbackEnabled(metaHolder.getHystrixCollapser().fallbackEnabled()); + return command; } /** @@ -73,9 +82,9 @@ protected HystrixCommand> createCommand( @Override protected void mapResponseToRequests(List batchResponse, Collection> collapsedRequests) { - Validate.notNull(batchResponse, "batchResponse cannot be null"); - Validate.isTrue(batchResponse.size() >= collapsedRequests.size(), - "size of batch response size should be gth or eq collapsed requests size"); + if (batchResponse.size() < collapsedRequests.size()) { + throw new RuntimeException(createMessage(collapsedRequests, batchResponse)); + } int count = 0; for (CollapsedRequest request : collapsedRequests) { request.setResponse(batchResponse.get(count++)); @@ -106,4 +115,9 @@ public Setter build() { } } + private String createMessage(Collection> requests, + List response) { + return ERROR_MSG + arrayFormat(ERROR_MSF_TEMPLATE, new Object[]{getCollapserKey().name(), requests.size(), response.size()}).getMessage(); + } + } 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 b6f1bb117..002daf696 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 @@ -16,13 +16,14 @@ package com.netflix.hystrix.contrib.javanica.command; -import com.google.common.collect.Maps; +import com.google.common.base.Throwables; import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; import java.util.Map; -import javax.annotation.concurrent.ThreadSafe; /** * Base class for hystrix commands. @@ -32,30 +33,34 @@ @ThreadSafe public abstract class AbstractHystrixCommand extends com.netflix.hystrix.HystrixCommand { - private CommandAction commandAction; - private CommandAction fallbackAction; - private Map commandProperties = Maps.newHashMap(); - private Collection> collapsedRequests; + private CommandActions commandActions; + private final Map commandProperties; + private final Collection> collapsedRequests; + private final Class[] ignoreExceptions; + private final ExecutionType executionType; /** * Constructor with parameters. * * @param setterBuilder the builder to build {@link com.netflix.hystrix.HystrixCommand.Setter} - * @param commandAction the command action - * @param fallbackAction the fallback action + * @param commandActions the command actions {@link CommandActions} * @param commandProperties the command properties * @param collapsedRequests the collapsed requests + * @param ignoreExceptions the exceptions which should be ignored and wrapped to throw in {@link HystrixBadRequestException} + * @param executionType the execution type {@link ExecutionType} */ protected AbstractHystrixCommand(CommandSetterBuilder setterBuilder, - CommandAction commandAction, - CommandAction fallbackAction, + CommandActions commandActions, Map commandProperties, - Collection> collapsedRequests) { + Collection> collapsedRequests, + final Class[] ignoreExceptions, + ExecutionType executionType) { super(setterBuilder.build()); + this.commandActions = commandActions; this.commandProperties = commandProperties; this.collapsedRequests = collapsedRequests; - this.commandAction = commandAction; - this.fallbackAction = fallbackAction; + this.ignoreExceptions = ignoreExceptions; + this.executionType = executionType; HystrixPropertiesManager.setCommandProperties(commandProperties, getCommandKey().name()); } @@ -65,7 +70,7 @@ protected AbstractHystrixCommand(CommandSetterBuilder setterBuilder, * @return command action */ CommandAction getCommandAction() { - return commandAction; + return commandActions.getCommandAction(); } /** @@ -74,7 +79,16 @@ CommandAction getCommandAction() { * @return fallback action */ CommandAction getFallbackAction() { - return fallbackAction; + return commandActions.getFallbackAction(); + } + + /** + * Gets key action. + * + * @return key action + */ + CommandAction getCacheKeyAction() { + return commandActions.getCacheKeyAction(); } /** @@ -95,6 +109,65 @@ Collection> getCollapsedReques return collapsedRequests; } + /** + * Gets exceptions types which should be ignored. + * + * @return exceptions types + */ + Class[] getIgnoreExceptions() { + return ignoreExceptions; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + /** + * {@inheritDoc}. + */ + @Override + protected String getCacheKey() { + String key; + if (commandActions.getCacheKeyAction() != null) { + key = String.valueOf(commandActions.getCacheKeyAction().execute(executionType)); + } else { + key = super.getCacheKey(); + } + return key; + } + + boolean isIgnorable(Throwable throwable) { + if (ignoreExceptions == null || ignoreExceptions.length == 0) { + return false; + } + for (Class ignoreException : ignoreExceptions) { + if (throwable.getClass().isAssignableFrom(ignoreException)) { + return true; + } + } + return false; + } + + /** + * Executes an action. If an action has failed and an exception is ignorable then propagate it as HystrixBadRequestException + * otherwise propagate it as RuntimeException to trigger fallback method. + * + * @param action the action + * @return result of command action execution + */ + Object process(Action action) throws RuntimeException { + Object result; + try { + result = action.execute(); + } catch (Throwable throwable) { + if (isIgnorable(throwable)) { + throw new HystrixBadRequestException(throwable.getMessage(), throwable); + } + throw Throwables.propagate(throwable); + } + return result; + } + /** * {@inheritDoc}. */ @@ -109,4 +182,11 @@ protected T getFallback() { throw new RuntimeException("No fallback available.", getFailedExecutionException()); } + /** + * Common action. + */ + abstract class Action { + abstract Object execute(); + } + } 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/AbstractHystrixCommandFactory.java index 4edb9f0e0..d1413901e 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/AbstractHystrixCommandFactory.java @@ -26,6 +26,7 @@ import java.lang.reflect.Method; import java.util.Collection; +import java.util.Collections; import java.util.Map; /** @@ -54,15 +55,14 @@ public T create(MetaHolder metaHolder, setterBuilder.commandKey(commandKey); setterBuilder.groupKey(groupKey); setterBuilder.threadPoolKey(metaHolder.getHystrixCommand().threadPoolKey()); - CommandAction action; - if (metaHolder.isAsync()) { - action = new CommandAction(metaHolder.getAsyncObj(), metaHolder.getAsyncMethod()); - } else { - action = new CommandAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs()); - } Map commandProperties = getCommandProperties(metaHolder.getHystrixCommand()); + CommandAction commandAction = new MethodExecutionAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs()); CommandAction fallbackAction = createFallbackAction(metaHolder, collapsedRequests); - return create(setterBuilder, commandProperties, action, fallbackAction, collapsedRequests); + CommandAction cacheKeyAction = createCacheKeyAction(metaHolder); + CommandActions commandActions = CommandActions.builder().commandAction(commandAction) + .fallbackAction(fallbackAction).cacheKeyAction(cacheKeyAction).build(); + return create(setterBuilder, commandActions, commandProperties, collapsedRequests, + metaHolder.getHystrixCommand().ignoreExceptions(), metaHolder.getExecutionType()); } CommandAction createFallbackAction(MetaHolder metaHolder, @@ -84,22 +84,37 @@ CommandAction createFallbackAction(MetaHolder metaHolder, .defaultGroupKey(metaHolder.getDefaultGroupKey()) .hystrixCollapser(metaHolder.getHystrixCollapser()) .hystrixCommand(fallbackMethod.getAnnotation(HystrixCommand.class)).build(); - fallbackAction = new CommandExecutionAction(create(fmMetaHolder, collapsedRequests)); + fallbackAction = new LazyCommandExecutionAction(GenericHystrixCommandFactory.getInstance(), fmMetaHolder, collapsedRequests); } else { - fallbackAction = new CommandAction(metaHolder.getObj(), fallbackMethod, metaHolder.getArgs()); + fallbackAction = new MethodExecutionAction(metaHolder.getObj(), fallbackMethod, metaHolder.getArgs()); } } catch (NoSuchMethodException e) { - Throwables.propagate(e); + throw Throwables.propagate(e); } } return fallbackAction; } - abstract T create(CommandSetterBuilder setterBuilder, Map commandProperties, CommandAction action, - CommandAction fallbackAction, - Collection> collapsedRequests); + abstract T create(CommandSetterBuilder setterBuilder, + CommandActions commandActions, + Map commandProperties, + Collection> collapsedRequests, + Class[] ignoreExceptions, + ExecutionType executionType); + + + private CommandAction createCacheKeyAction(MetaHolder metaHolder) { + CommandAction cacheKeyAction = null; + if (metaHolder.getCacheKeyMethod() != null) { + cacheKeyAction = new MethodExecutionAction(metaHolder.getObj(), metaHolder.getCacheKeyMethod(), metaHolder.getArgs()); + } + 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()); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java similarity index 57% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncCommand.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java index 9f295e0f7..632a6bc94 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AsyncResult.java @@ -15,45 +15,49 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** - * Fake implementation of Future. Provides abstract invoke method to process an asynchronous call. + * Fake implementation of {@link Future}. Can be used for method signatures + * which are declared with a Future return type for asynchronous execution. + * Provides abstract {@link #invoke()} method to wrap some logic for an asynchronous call. * * @param the type of result */ -public abstract class AsyncCommand implements Future { +public abstract class AsyncResult implements Future, ClosureCommand { - private static final String ERROR_MSG = "AsyncCommand is just a stab and cannot be used as complete implementation of Future"; + private static final String ERROR_MSG = "AsyncResult is just a stab and cannot be used as complete implementation of Future"; @Override - public boolean cancel(boolean mayInterruptIfRunning) { + public boolean cancel(boolean mayInterruptIfRunning) throws UnsupportedOperationException { throw new UnsupportedOperationException(ERROR_MSG); } @Override - public boolean isCancelled() { + public boolean isCancelled() throws UnsupportedOperationException { throw new UnsupportedOperationException(ERROR_MSG); } @Override - public boolean isDone() { + public boolean isDone() throws UnsupportedOperationException { throw new UnsupportedOperationException(ERROR_MSG); } @Override - public T get() throws InterruptedException, ExecutionException { + public T get() throws UnsupportedOperationException { throw new UnsupportedOperationException(ERROR_MSG); } @Override - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + public T get(long timeout, TimeUnit unit) throws UnsupportedOperationException { throw new UnsupportedOperationException(ERROR_MSG); } + /** + * {@inheritDoc}. + */ + @Override public abstract T invoke(); } 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 0cc8698ed..9a22638e9 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 @@ -18,6 +18,7 @@ import com.google.common.collect.Lists; import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.exception.HystrixBadRequestException; import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; @@ -30,15 +31,32 @@ @ThreadSafe public class BatchHystrixCommand extends AbstractHystrixCommand> { - private List response = Lists.newCopyOnWriteArrayList(); + /** + * If some error occurs during the processing in run() then if {@link #fallbackEnabled} is true then the {@link #processWithFallback} + * will be invoked. If {@link #getFallbackAction()} doesn't process fallback logic as Hystrix command then + * command fallback will be processed in the single thread with BatchHystrixCommand, + * because the {@link #processWithFallback} is called from run(); + */ + private boolean fallbackEnabled; /** - * {@inheritDoc} + * {@inheritDoc}. */ - protected BatchHystrixCommand(CommandSetterBuilder setterBuilder, CommandAction commandAction, - CommandAction fallbackAction, Map commandProperties, - Collection> collapsedRequests) { - super(setterBuilder, commandAction, fallbackAction, commandProperties, collapsedRequests); + protected BatchHystrixCommand(CommandSetterBuilder setterBuilder, CommandActions commandActions, + Map commandProperties, + Collection> collapsedRequests, + Class[] ignoreExceptions, + ExecutionType executionType) { + super(setterBuilder, commandActions, commandProperties, collapsedRequests, + ignoreExceptions, executionType); + } + + public boolean isFallbackEnabled() { + return fallbackEnabled; + } + + public void setFallbackEnabled(boolean fallbackEnabled) { + this.fallbackEnabled = fallbackEnabled; } /** @@ -46,24 +64,62 @@ protected BatchHystrixCommand(CommandSetterBuilder setterBuilder, CommandAction */ @Override protected List run() throws Exception { + List response = Lists.newArrayList(); for (HystrixCollapser.CollapsedRequest request : getCollapsedRequests()) { - Object[] args = (Object[]) request.getArgument(); - response.add(getCommandAction().execute(args)); + final Object[] args = (Object[]) request.getArgument(); + try { + response.add(fallbackEnabled ? processWithFallback(args) : process(args)); + } catch (RuntimeException ex) { + request.setException(ex); + response.add(null); + } + } return response; } - /** - * {@inheritDoc} - */ - @Override - protected List getFallback() { - if (getFallbackAction() != null) { - response.add(getFallbackAction().execute()); - return response; - } else { - return super.getFallback(); + private Object process(final Object[] args) { + return process(new Action() { + @Override + Object execute() { + return getCommandAction().executeWithArgs(getExecutionType(), args); + } + }); + } + + private Object processWithFallback(final Object[] args) { + Object result = null; + try { + result = process(args); + } catch (RuntimeException ex) { + if (ex instanceof HystrixBadRequestException || getFallbackAction() == null) { + throw ex; + } else { + if (getFallbackAction() != null) { + result = processFallback(args); + } + } } + return result; } + private Object processFallback(final Object[] args) { + return process(new Action() { + @Override + Object execute() { + return getFallbackAction().executeWithArgs(ExecutionType.SYNCHRONOUS, args); + } + }); + } + + +/* @Override + protected List getFallback() { + Calling this method for Batch commands makes no sense in general because if the processing of a request fails + then other requests will not be processed within this collapser and setException method will be automatically + called on requests instances. Generally this is an all or nothing affair. For example, a timeout or rejection of + the HystrixCommand would cause failure on all of the batched calls. Thus existence of fallback method does not + eliminate the break of all requests and the collapser as a whole. + }*/ + } 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 index 36565c93b..d6a35d6f8 100644 --- 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 @@ -24,7 +24,7 @@ * Specific implementation of {@link HystrixCommandFactory} interface to create {@link BatchHystrixCommand} instances. */ public class BatchHystrixCommandFactory extends AbstractHystrixCommandFactory - implements HystrixCommandFactory { + implements HystrixCommandFactory { private static final HystrixCommandFactory COMMAND_FACTORY = new BatchHystrixCommandFactory(); @@ -34,11 +34,13 @@ public static HystrixCommandFactory getInstance() { @Override BatchHystrixCommand create(CommandSetterBuilder setterBuilder, - Map commandProperties, CommandAction action, - CommandAction fallbackAction, - Collection> collapsedRequests) { - BatchHystrixCommand batchHystrixCommand = new BatchHystrixCommand(setterBuilder, action, fallbackAction, - commandProperties, collapsedRequests); + CommandActions commandActions, + Map commandProperties, + Collection> collapsedRequests, + Class[] ignoreExceptions, + ExecutionType executionType) { + BatchHystrixCommand batchHystrixCommand = new BatchHystrixCommand(setterBuilder, commandActions, + commandProperties, collapsedRequests, ignoreExceptions, executionType); return batchHystrixCommand; } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/ProxyType.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java similarity index 64% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/ProxyType.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java index d138419e6..3b53109f5 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/ProxyType.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ClosureCommand.java @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.hystrix.contrib.javanica.aop; +package com.netflix.hystrix.contrib.javanica.command; /** - * Represents enumeration of proxies types. + * This interface is used to perform command logic within an anonymous class and basically used as wrapper for some logic. + * + * @param command result type */ -public enum ProxyType { - - JDK, - - CGLIB, +public interface ClosureCommand { - UNKNOWN + /** + * Process logic. + * + * @return result + */ + T invoke(); } 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 e60b75c83..5b90249fa 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 @@ -15,56 +15,13 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** - * Simple wrapper to invoke a method using Java reflection API. + * Simple action to encapsulate some logic to process it in a Hystrix command. */ -public class CommandAction { - - private static final Object[] EMPTY_ARGS = new Object[]{}; - private Object object; - private Method method; - private Object[] _args; - - protected CommandAction() { - } - - protected CommandAction(Object object, Method method) { - this.object = object; - this.method = method; - this._args = EMPTY_ARGS; - } - - protected CommandAction(Object object, Method method, Object[] args) { - this.object = object; - this.method = method; - this._args = args; - } +public abstract class CommandAction { - public Object execute() { - return execute(_args); - } + public abstract Object execute(ExecutionType executionType); - /** - * Invokes the method. Also private method also can be invoked. - * - * @return result of execution - */ - public Object execute(Object[] args) { - Object result = null; - try { - method.setAccessible(true); // suppress Java language access - result = method.invoke(object, args); - } catch (IllegalAccessException e) { - throw new CommandActionExecutionException(e); - } catch (InvocationTargetException e) { - throw new CommandActionExecutionException(e); - } - return result; - } + public abstract Object executeWithArgs(ExecutionType executionType, Object[] args); } 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 new file mode 100644 index 000000000..83d1af0e4 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java @@ -0,0 +1,72 @@ +/** + * 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; + + +public class CommandActions { + + private final CommandAction commandAction; + private final CommandAction fallbackAction; + private final CommandAction cacheKeyAction; + + public CommandActions(Builder builder) { + this.commandAction = builder.commandAction; + this.fallbackAction = builder.fallbackAction; + this.cacheKeyAction = builder.cacheKeyAction; + } + + public static Builder builder() { + return new Builder(); + } + + public CommandAction getCommandAction() { + return commandAction; + } + + public CommandAction getFallbackAction() { + return fallbackAction; + } + + public CommandAction getCacheKeyAction() { + return cacheKeyAction; + } + + public static class Builder { + private CommandAction commandAction; + private CommandAction fallbackAction; + private CommandAction cacheKeyAction; + + public Builder commandAction(CommandAction pCommandAction) { + this.commandAction = pCommandAction; + return this; + } + + public Builder fallbackAction(CommandAction pFallbackAction) { + this.fallbackAction = pFallbackAction; + return this; + } + + public Builder cacheKeyAction(CommandAction pCacheKeyAction) { + this.cacheKeyAction = pCacheKeyAction; + return this; + } + + public CommandActions build() { + return new CommandActions(this); + } + } + +} 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 c226230f8..658ed89b9 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 @@ -24,6 +24,7 @@ public class CommandExecutionAction extends CommandAction { /** * Constructor with parameters. + * * @param hystrixCommand the hystrix command to execute. */ public CommandExecutionAction(AbstractHystrixCommand hystrixCommand) { @@ -31,8 +32,13 @@ public CommandExecutionAction(AbstractHystrixCommand hystrixCommand) { } @Override - public Object execute() { - return hystrixCommand.execute(); + public Object execute(ExecutionType executionType) { + return CommandExecutor.execute(hystrixCommand, executionType); + } + + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) { + return CommandExecutor.execute(hystrixCommand, executionType); } } 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 new file mode 100644 index 000000000..5583a40a1 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandExecutor.java @@ -0,0 +1,56 @@ +/** + * 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.HystrixExecutable; +import org.apache.commons.lang3.Validate; + +/** + * Invokes necessary method of {@link HystrixExecutable} 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()}. + */ +public class CommandExecutor { + + /** + * Calls a method of {@link HystrixExecutable} in accordance with specified execution type. + * + * @param executable {@link HystrixExecutable} + * @param executionType {@link ExecutionType} + * @return the result of invocation of specific method. + * @throws RuntimeException + */ + public static Object execute(HystrixExecutable executable, ExecutionType executionType) throws RuntimeException { + Validate.notNull(executable); + Validate.notNull(executionType); + switch (executionType) { + case SYNCHRONOUS: { + return executable.execute(); + } + case ASYNCHRONOUS: { + return executable.queue(); + } + case OBSERVABLE: { + return executable.observe(); + } + default: + throw new RuntimeException("unsupported execution type: " + executionType); + } + } + +} 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 index 210122ee8..d7b49f46b 100644 --- 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 @@ -64,7 +64,7 @@ public HystrixCommand.Setter build() { HystrixCommand.Setter setter = HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); - if (StringUtils.isNotEmpty(threadPoolKey)) { + if (StringUtils.isNotBlank(threadPoolKey)) { setter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); } return setter; diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java new file mode 100644 index 000000000..57638c2c8 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ExecutionType.java @@ -0,0 +1,58 @@ +/** + * 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 java.util.concurrent.Future; + +/** + * Specifies executions types. + */ +public enum ExecutionType { + + /** + * Used for asynchronous execution of command. + */ + ASYNCHRONOUS, + + /** + * Used for synchronous execution of command. + */ + SYNCHRONOUS, + + /** + * Reactive execution (asynchronous callback). + */ + OBSERVABLE; + + + /** + * Gets execution type for specified class type. + * @param type the type + * @return the execution type {@link ExecutionType} + */ + public static ExecutionType getExecutionType(Class type) { + if (type.isAssignableFrom(Future.class)) { + return ExecutionType.ASYNCHRONOUS; + } else if (type.isAssignableFrom(Observable.class)) { + return ExecutionType.OBSERVABLE; + } else { + return ExecutionType.SYNCHRONOUS; + } + } + +} 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 55a3587d0..ac8432948 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,31 +16,32 @@ package com.netflix.hystrix.contrib.javanica.command; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; import java.util.Map; /** - * This command used to execute {@link CommandAction} as hystrix command. - * Basically any logic can be executed within {@link CommandAction} - * such as method invocation and etc. + * Implementation of AbstractHystrixCommand which returns an Object as result. */ +@ThreadSafe public class GenericCommand extends AbstractHystrixCommand { private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class); - private static final String EXECUTION_ERROR_MSG = "failed to process command action"; - /** * {@inheritDoc} */ - protected GenericCommand(CommandSetterBuilder setterBuilder, CommandAction commandAction, - CommandAction fallbackAction, Map commandProperties, - Collection> collapsedRequests) { - super(setterBuilder, commandAction, fallbackAction, commandProperties, collapsedRequests); + protected GenericCommand(CommandSetterBuilder setterBuilder, + CommandActions commandActions, + Map commandProperties, + Collection> collapsedRequests, + Class[] ignoreExceptions, + ExecutionType executionType) { + super(setterBuilder, commandActions, commandProperties, collapsedRequests, + ignoreExceptions, executionType); } /** @@ -49,7 +50,12 @@ protected GenericCommand(CommandSetterBuilder setterBuilder, CommandAction comma @Override protected Object run() throws Exception { LOGGER.debug("execute command: {}", getCommandKey().name()); - return process(getCommandAction()); + return process(new Action() { + @Override + Object execute() { + return getCommandAction().execute(getExecutionType()); + } + }); } /** @@ -57,29 +63,23 @@ protected Object run() throws Exception { * Also a fallback method will be invoked within separate command in the case if fallback method was annotated with * HystrixCommand annotation, otherwise current implementation throws RuntimeException and leaves the caller to deal with it * (see {@link super#getFallback()}). + * The getFallback() is always processed synchronously. * * @return result of invocation of fallback method or RuntimeException */ @Override protected Object getFallback() { - return getFallbackAction() != null ? process(getFallbackAction()) : super.getFallback(); - } + if (getFallbackAction() != null) { + return process(new Action() { + @Override + Object execute() { + return getFallbackAction().execute(ExecutionType.SYNCHRONOUS); + } + }); - /** - * Executes action and in the case of any exceptions propagates it as {@link CommandActionExecutionException} - * runtime exception. - * - * @param action the command action - * @return result of command action execution - */ - private Object process(CommandAction action) { - Object result; - try { - result = action.execute(); - } catch (Throwable throwable) { - throw new CommandActionExecutionException(EXECUTION_ERROR_MSG, throwable); + } else { + return super.getFallback(); } - return result; } } 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 index 946b5d139..bf3efdad9 100644 --- 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 @@ -24,7 +24,7 @@ * Specific implementation of {@link HystrixCommandFactory} interface to create {@link GenericCommand} instances. */ public class GenericHystrixCommandFactory extends AbstractHystrixCommandFactory - implements HystrixCommandFactory { + implements HystrixCommandFactory { private static final HystrixCommandFactory COMMAND_FACTORY = new GenericHystrixCommandFactory(); @@ -33,11 +33,14 @@ public static HystrixCommandFactory getInstance() { } @Override - GenericCommand create(CommandSetterBuilder setterBuilder, Map commandProperties, - CommandAction action, CommandAction fallbackAction, - Collection> collapsedRequests) { - GenericCommand genericCommand = new GenericCommand(setterBuilder, action, fallbackAction, commandProperties, - collapsedRequests); + GenericCommand create(CommandSetterBuilder setterBuilder, + CommandActions commandActions, + Map commandProperties, + Collection> collapsedRequests, + Class[] ignoreExceptions, + ExecutionType executionType) { + GenericCommand genericCommand = new GenericCommand(setterBuilder, commandActions, commandProperties, + collapsedRequests, ignoreExceptions, executionType); return genericCommand; } 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 new file mode 100644 index 000000000..5dc8ebbf8 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/LazyCommandExecutionAction.java @@ -0,0 +1,77 @@ +/** + * 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 java.util.Collection; + +public class LazyCommandExecutionAction extends CommandAction { + + private MetaHolder metaHolder; + + private Collection> collapsedRequests; + + private HystrixCommandFactory commandFactory; + + public LazyCommandExecutionAction(HystrixCommandFactory commandFactory, + MetaHolder metaHolder, + Collection> collapsedRequests) { + this.commandFactory = commandFactory; + this.metaHolder = metaHolder; + this.collapsedRequests = collapsedRequests; + } + + @Override + public Object execute(ExecutionType executionType) { + AbstractHystrixCommand abstractHystrixCommand = commandFactory.create(createHolder(executionType), collapsedRequests); + return new CommandExecutionAction(abstractHystrixCommand).execute(executionType); + } + + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) { + AbstractHystrixCommand abstractHystrixCommand = commandFactory.create(createHolder(executionType, args), collapsedRequests); + return new CommandExecutionAction(abstractHystrixCommand).execute(executionType); + } + + private MetaHolder createHolder(ExecutionType executionType) { + return MetaHolder.builder() + .obj(metaHolder.getObj()) + .method(metaHolder.getMethod()) + .executionType(executionType) + .args(metaHolder.getArgs()) + .defaultCollapserKey(metaHolder.getDefaultCollapserKey()) + .defaultCommandKey(metaHolder.getDefaultCommandKey()) + .defaultGroupKey(metaHolder.getDefaultGroupKey()) + .hystrixCollapser(metaHolder.getHystrixCollapser()) + .hystrixCommand(metaHolder.getHystrixCommand()).build(); + } + + private MetaHolder createHolder(ExecutionType executionType, Object[] args) { + return MetaHolder.builder() + .obj(metaHolder.getObj()) + .method(metaHolder.getMethod()) + .executionType(executionType) + .args(args) + .defaultCollapserKey(metaHolder.getDefaultCollapserKey()) + .defaultCommandKey(metaHolder.getDefaultCommandKey()) + .defaultGroupKey(metaHolder.getDefaultGroupKey()) + .hystrixCollapser(metaHolder.getHystrixCollapser()) + .hystrixCommand(metaHolder.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 d7f239be4..f9ef8ab69 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 @@ -17,42 +17,45 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.command.closure.Closure; + import java.lang.reflect.Method; import java.util.Arrays; import javax.annotation.concurrent.Immutable; /** - * Simple holder to keep necessary information. + * Simple immutable holder to keep all necessary information to build Hystrix command. */ @Immutable public class MetaHolder { - private HystrixCollapser hystrixCollapser; - private HystrixCommand hystrixCommand; - - private Method method; - private Method asyncMethod; - private Object obj; - private Object asyncObj; - private Object[] args; + private final HystrixCollapser hystrixCollapser; + private final HystrixCommand hystrixCommand; - private String defaultGroupKey; - private String defaultCommandKey; - private String defaultCollapserKey; - private boolean async; + private final Method method; + private final Method cacheKeyMethod; + private final Object obj; + private final Object proxyObj; + private final Object[] args; + private final Closure closure; + private final String defaultGroupKey; + private final String defaultCommandKey; + private final String defaultCollapserKey; + private final ExecutionType executionType; private MetaHolder(Builder builder) { this.hystrixCommand = builder.hystrixCommand; this.method = builder.method; - this.asyncMethod = builder.asyncMethod; + this.cacheKeyMethod = builder.cacheKeyMethod; this.obj = builder.obj; - this.asyncObj = builder.asyncObj; + this.proxyObj = builder.proxyObj; this.args = builder.args; + this.closure = builder.closure; this.defaultGroupKey = builder.defaultGroupKey; this.defaultCommandKey = builder.defaultCommandKey; this.defaultCollapserKey = builder.defaultCollapserKey; this.hystrixCollapser = builder.hystrixCollapser; - this.async = builder.async; + this.executionType = builder.executionType; } public static Builder builder() { @@ -68,27 +71,31 @@ public HystrixCommand getHystrixCommand() { } public Method getMethod() { - return method; //todo return a copy + return method; + } + + public Method getCacheKeyMethod() { + return cacheKeyMethod; } public Object getObj() { return obj; } - public Method getAsyncMethod() { - return asyncMethod; + public Object getProxyObj() { + return proxyObj; } - public Object getAsyncObj() { - return asyncObj; + public Closure getClosure() { + return closure; } - public boolean isAsync() { - return async; + public ExecutionType getExecutionType() { + return executionType; } public Object[] getArgs() { - return Arrays.copyOf(args, args.length); + return args != null ? Arrays.copyOf(args, args.length) : new Object[]{}; } public String getDefaultGroupKey() { @@ -112,14 +119,15 @@ public static final class Builder { private HystrixCollapser hystrixCollapser; private HystrixCommand hystrixCommand; private Method method; - private Method asyncMethod; + private Method cacheKeyMethod; private Object obj; - private Object asyncObj; + private Object proxyObj; + private Closure closure; private Object[] args; private String defaultGroupKey; private String defaultCommandKey; private String defaultCollapserKey; - private boolean async; + private ExecutionType executionType; public Builder hystrixCollapser(HystrixCollapser hystrixCollapser) { this.hystrixCollapser = hystrixCollapser; @@ -136,8 +144,8 @@ public Builder method(Method method) { return this; } - public Builder asyncMethod(Method asyncMethod) { - this.asyncMethod = asyncMethod; + public Builder cacheKeyMethod(Method cacheKeyMethod) { + this.cacheKeyMethod = cacheKeyMethod; return this; } @@ -146,8 +154,8 @@ public Builder obj(Object obj) { return this; } - public Builder asyncObj(Object asyncObj) { - this.asyncObj = asyncObj; + public Builder proxyObj(Object proxy) { + this.proxyObj = proxy; return this; } @@ -156,8 +164,13 @@ public Builder args(Object[] args) { return this; } - public Builder async(boolean async) { - this.async = async; + public Builder closure(Closure closure) { + this.closure = closure; + return this; + } + + public Builder executionType(ExecutionType executionType) { + this.executionType = executionType; return 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 new file mode 100644 index 000000000..466a0dddc --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MethodExecutionAction.java @@ -0,0 +1,85 @@ +package com.netflix.hystrix.contrib.javanica.command; + + +import com.google.common.base.Throwables; +import com.netflix.hystrix.contrib.javanica.command.closure.Closure; +import com.netflix.hystrix.contrib.javanica.command.closure.ClosureFactoryRegistry; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class MethodExecutionAction extends CommandAction { + + private static final Object[] EMPTY_ARGS = new Object[]{}; + + private final Object object; + private final Method method; + private final Object[] _args; + + public MethodExecutionAction(Object object, Method method) { + this.object = object; + this.method = method; + this._args = EMPTY_ARGS; + } + + public MethodExecutionAction(Object object, Method method, Object[] args) { + this.object = object; + this.method = method; + this._args = args; + } + + public Object getObject() { + return object; + } + + public Method getMethod() { + return method; + } + + public Object[] getArgs() { + return _args; + } + + @Override + public Object execute(ExecutionType executionType) { + return executeWithArgs(executionType, _args); + } + + /** + * Invokes the method. Also private method also can be invoked. + * + * @return result of execution + */ + @Override + public Object executeWithArgs(ExecutionType executionType, Object[] args) { + if (ExecutionType.SYNCHRONOUS.equals(executionType)) { + return execute(object, method, args); + } else { + Closure closure = ClosureFactoryRegistry.getFactory(executionType).createClosure(method, object, args); + return execute(closure.getClosureObj(), closure.getClosureMethod()); + } + } + + /** + * Invokes the method. + * + * @return result of execution + */ + private Object execute(Object o, Method m, Object... args) { + Object result = null; + try { + m.setAccessible(true); // suppress Java language access + result = m.invoke(o, args); + } catch (IllegalAccessException e) { + propagateCause(e); + } catch (InvocationTargetException e) { + propagateCause(e); + } + return result; + } + + private void propagateCause(Throwable throwable) { + throw Throwables.propagate(throwable.getCause()); + } + +} 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 new file mode 100644 index 000000000..ee4cb43f9 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/ObservableResult.java @@ -0,0 +1,46 @@ +/** + * 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 new file mode 100644 index 000000000..6ac43f02a --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AbstractClosureFactory.java @@ -0,0 +1,80 @@ +/** + * 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.base.Throwables; +import com.netflix.hystrix.contrib.javanica.command.ClosureCommand; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.slf4j.helpers.MessageFormatter.format; + +/** + * Abstract implementation of {@link ClosureFactory}. + */ +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); + } + } + + /** + * Creates closure. + * + * @param rootMethodName the name of external method within which closure is created. + * @param closureObj the instance of specific anonymous class + * @return new {@link Closure} instance + * @throws Exception + */ + Closure createClosure(String rootMethodName, final Object closureObj) throws Exception { + if (!isClosureCommand(closureObj)) { + throw new RuntimeException(format(ERROR_TYPE_MESSAGE, rootMethodName, + getClosureCommandType().getName()).getMessage()); + } + Method closureMethod = closureObj.getClass().getMethod(INVOKE_METHOD); + return new Closure(closureMethod, closureObj); + } + + /** + * Checks that closureObj is instance of necessary class. + * + * @param closureObj the instance of an anonymous class + * @return true of closureObj has expected type, otherwise - false + */ + abstract boolean isClosureCommand(final Object closureObj); + + /** + * Gets type of expected closure type. + * + * @return closure (anonymous class) type + */ + abstract Class getClosureCommandType(); +} 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 new file mode 100644 index 000000000..decfec809 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/AsyncClosureFactory.java @@ -0,0 +1,42 @@ +/** + * 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.AsyncResult; +import com.netflix.hystrix.contrib.javanica.command.ClosureCommand; + +/** + * Specific implementation of {@link ClosureFactory}. + */ +public class AsyncClosureFactory extends AbstractClosureFactory { + + /** + * {@inheritDoc}. + */ + @Override + boolean isClosureCommand(Object closureObj) { + return closureObj instanceof AsyncResult; + } + + /** + * {@inheritDoc}. + */ + @Override + Class getClosureCommandType() { + return AsyncResult.class; + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java new file mode 100644 index 000000000..e3b1fe753 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/Closure.java @@ -0,0 +1,47 @@ +/** + * 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 java.lang.reflect.Method; + +/** + * Contains method and instance of anonymous class which implements + * {@link com.netflix.hystrix.contrib.javanica.command.ClosureCommand} interface. + */ +public class Closure { + + private final Method closureMethod; + private final Object closureObj; + + public Closure() { + closureMethod = null; + closureObj = null; + } + + public Closure(Method closureMethod, Object closureObj) { + this.closureMethod = closureMethod; + this.closureObj = closureObj; + } + + public Method getClosureMethod() { + return closureMethod; + } + + public Object getClosureObj() { + return closureObj; + } + +} 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 new file mode 100644 index 000000000..19276782c --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactory.java @@ -0,0 +1,34 @@ +/** + * 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 java.lang.reflect.Method; + +/** + * Factory to create instances of {@link Closure} class. + */ +public interface ClosureFactory { + + /** + * Creates closure in accordance with method return type. + * + * @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 + */ + Closure createClosure(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 new file mode 100644 index 000000000..25d63852d --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ClosureFactoryRegistry.java @@ -0,0 +1,51 @@ +/** + * 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 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; + } + }) + .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 new file mode 100644 index 000000000..57fbbc91a --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/closure/ObservableClosureFactory.java @@ -0,0 +1,42 @@ +/** + * 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/exception/CommandActionExecutionException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/CommandActionExecutionException.java deleted file mode 100644 index ae73021a6..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/CommandActionExecutionException.java +++ /dev/null @@ -1,51 +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.exception; - -public class CommandActionExecutionException extends RuntimeException { - - public CommandActionExecutionException() { - } - - /** - * Constructor with message. - * - * @param message message text - */ - public CommandActionExecutionException(String message) { - super(message); - } - - /** - * Constructor with cause. - * - * @param cause the cause - */ - public CommandActionExecutionException(Throwable cause) { - super(cause); - } - - /** - * Constructor with message and cause. - * - * @param message message text - * @param cause the cause - */ - public CommandActionExecutionException(String message, Throwable cause) { - super(message, cause); - } - -} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java index a7198c06a..faf5b836e 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/utils/AopUtils.java @@ -15,13 +15,10 @@ */ package com.netflix.hystrix.contrib.javanica.utils; -import com.netflix.hystrix.contrib.javanica.aop.ProxyType; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; /** * Provides common methods to retrieve information from JoinPoint and not only. @@ -29,69 +26,59 @@ public final class AopUtils { private AopUtils() { + throw new UnsupportedOperationException("It's prohibited to create instances of the class."); } /** - * The CGLIB class separator character "$$" - */ - public static final String CGLIB_CLASS_SEPARATOR = "$$"; - - /** - * Gets method from source object class (not proxy class). + * Gets a {@link Method} object from target object (not proxy class). * * @param joinPoint the {@link JoinPoint} - * @return a method + * @return a {@link Method} object or null if method doesn't exist or if the signature at a join point + * isn't sub-type of {@link MethodSignature} */ public static Method getMethodFromTarget(JoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - return getDeclaredMethod(joinPoint.getTarget().getClass(), signature.getName(), - getParameterTypes(joinPoint)); + Method method = null; + if (joinPoint.getSignature() instanceof MethodSignature) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + method = getDeclaredMethod(joinPoint.getTarget().getClass(), signature.getName(), + getParameterTypes(joinPoint)); + } + return method; } + /** + * Gets a {@link Method} object from target object by specified method name. + * + * @param joinPoint the {@link JoinPoint} + * @param methodName the method name + * @return a {@link Method} object or null if method with specified methodName doesn't exist + */ public static Method getMethodFromTarget(JoinPoint joinPoint, String methodName) { return getDeclaredMethod(joinPoint.getTarget().getClass(), methodName, - getParameterTypes(joinPoint)); - } - - public static Method getMethodFromThis(JoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - return getDeclaredMethod(joinPoint.getThis().getClass(), signature.getName(), - getParameterTypes(joinPoint)); - } - - public static Method getMethodFromThis(JoinPoint joinPoint, String methodName) { - return getDeclaredMethod(joinPoint.getThis().getClass(), methodName, - getParameterTypes(joinPoint)); - } - - public static String getTargetClassName(JoinPoint joinPoint) { - return joinPoint.getTarget().getClass().getSimpleName(); - } - - public static String getThisClassName(JoinPoint joinPoint) { - return joinPoint.getThis().getClass().getSimpleName(); + getParameterTypes(joinPoint)); } + /** + * Gets parameter types of the join point. + * + * @param joinPoint the join point + * @return the parameter types for the method this object + * represents + */ public static Class[] getParameterTypes(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); return method.getParameterTypes(); } - public static Annotation getAnnotation(JoinPoint joinPoint, Class aClass, String methodName) { - Method method = null; - if (ProxyType.JDK.equals(getProxyType(joinPoint.getThis()))) { - method = getMethodFromTarget(joinPoint, methodName); - } else { - method = getMethodFromThis(joinPoint, methodName); - } - return method.getAnnotation(aClass); - } - - public static boolean isMethodExists(Class type, String methodName, Class... parameterTypes) { - return getDeclaredMethod(type, methodName, parameterTypes) != null; - } - + /** + * Gets declared method from specified type by mame and parameters types. + * + * @param type the type + * @param methodName the name of the method + * @param parameterTypes the parameter array + * @return a {@link Method} object or null if method doesn't exist + */ public static Method getDeclaredMethod(Class type, String methodName, Class... parameterTypes) { Method method = null; try { @@ -102,27 +89,4 @@ public static Method getDeclaredMethod(Class type, String methodName, Class getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + + user = userService.getUser("1", "name"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUser("1", "name"); + com.netflix.hystrix.HystrixCommand getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + + private com.netflix.hystrix.HystrixCommand getLastExecutedCommand() { + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getExecutedCommands(); + return Iterables.getLast(executedCommands); + } + + public static class UserService { + @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") + public User getUser(String id, String name) { + return new User(id, name + id); // it should be network call + } + + private String getUserIdCacheKey(String id, String name) { + return id + name; + } + } + + /** + * Spring configuration. + */ + @Configurable + public static class CacheTestConfig { + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java new file mode 100644 index 000000000..51235b86c --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/collapser/CollapserTest.java @@ -0,0 +1,275 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.collapser; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.command.AsyncResult; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * This test covers "Request Collapsing" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Collapsing + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CollapserTest.CollapserTestConfig.class}) +public class CollapserTest { + + @Autowired + private UserService userService; + + @Test + public void testCollapserAsync() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsync("1", "name: "); + Future f2 = userService.getUserAsync("2", "name: "); + Future f3 = userService.getUserAsync("3", "name: "); + Future f4 = userService.getUserAsync("4", "name: "); + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals("name: 3", f3.get().getName()); + assertEquals("name: 4", f4.get().getName()); + + // assert that the batch command 'GetUserCommand' was in fact + // executed and that it executed only once + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + // assert the command is the one we're expecting + assertEquals("GetUserCommand", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + /** + * This test covers situation when fallback isn't enabled in collapser. + */ + @Test(expected = ExecutionException.class) + public void testCollapserAsyncNotFound() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsync("1", "name: "); + Future f2 = userService.getUserAsync("2", "name: "); + Future f3 = userService.getUserAsync("not found", "name"); // not found, exception here + Future f4 = userService.getUserAsync("4", "name: "); // will not be processed + Future f5 = userService.getUserAsync("5", "name: "); // will not be processed + System.out.println(f1.get().getName()); // this line will be executed + System.out.println(f2.get().getName()); // this line will be executed + System.out.println(f3.get().getName()); // this line will not be executed + System.out.println(f4.get().getName()); // this line will not be executed + System.out.println(f5.get().getName()); // this line will not be executed + + } finally { + context.shutdown(); + } + } + + /** + * This test covers situation when fallback is enabled in collapser. + */ + @Test + public void testCollapserAsyncNotFoundWithFallbackEnabled() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsyncWithFallback("1", "name: "); + Future f2 = userService.getUserAsyncWithFallback("2", "name: "); + Future f3 = userService.getUserAsyncWithFallback("not found", "name"); // not found, exception here + Future f4 = userService.getUserAsyncWithFallback("4", "name: "); + Future f5 = userService.getUserAsyncWithFallback("5", "name: "); + + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals(UserService.DEFAULT_USER, f3.get()); // default value from fallback + assertEquals("name: 4", f4.get().getName()); + assertEquals("name: 5", f5.get().getName()); + + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserCommand = getHystrixCommandByKey("getUserAsyncWithFallback"); + + // confirm that it was a COLLAPSED command execution + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + } finally { + context.shutdown(); + } + } + + + /** + * This test covers situation when fallback is enabled in collapser. + */ + @Test + public void testCollapserAsyncNotFoundWithFallbackCommandEnabled() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsyncWithFallbackCommand("1", "name: "); + Future f2 = userService.getUserAsyncWithFallbackCommand("2", "name: "); + Future f3 = userService.getUserAsyncWithFallbackCommand("not found", "name"); // not found, exception here + Future f4 = userService.getUserAsyncWithFallbackCommand("4", "name: "); + Future f5 = userService.getUserAsyncWithFallbackCommand("5", "name: "); + + + assertEquals("name: 1", f1.get().getName()); + assertEquals("name: 2", f2.get().getName()); + assertEquals(UserService.DEFAULT_USER, f3.get()); // default value from fallback + assertEquals("name: 4", f4.get().getName()); + assertEquals("name: 5", f5.get().getName()); + + assertEquals(2, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserCommand = getHystrixCommandByKey("getUserAsyncWithFallbackCommand"); + + // confirm that it was a COLLAPSED command execution + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + + } finally { + context.shutdown(); + } + } + + + @Test + public void testCollapserSync() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserSync("1", "name: "); + User u2 = userService.getUserSync("2", "name: "); + User u3 = userService.getUserSync("3", "name: "); + User u4 = userService.getUserSync("4", "name: "); + + assertEquals("name: 1", u1.getName()); + assertEquals("name: 2", u2.getName()); + assertEquals("name: 3", u3.getName()); + assertEquals("name: 4", u4.getName()); + + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("GetUserCommand", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + @Test + public void testCollapserSyncWithFallbackCommand() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserSync("5", "name: "); + assertEquals("name: 5", u1.getName()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("GetUserCommand", command.getCommandKey().name()); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + } finally { + context.shutdown(); + } + } + + public static class UserService { + + public static final User DEFAULT_USER = new User("def", "def"); + + @HystrixCommand(commandKey = "GetUserCommand", fallbackMethod = "fallback") + @HystrixCollapser(collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserAsync(final String id, final String name) { + emulateNotFound(id); + return new AsyncResult() { + @Override + public User invoke() { + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "fallback") + @HystrixCollapser(fallbackEnabled = true, collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserAsyncWithFallback(final String id, final String name) { + return getUserAsync(id, name); + } + + /** + * Fallback for this command is also Hystrix command {@link #fallbackCommand(String, String)}. + */ + @HystrixCommand(fallbackMethod = "fallbackCommand") + @HystrixCollapser(fallbackEnabled = true, collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public Future getUserAsyncWithFallbackCommand(final String id, final String name) { + return getUserAsync(id, name); + } + + @HystrixCommand(commandKey = "GetUserCommand", fallbackMethod = "fallback") + @HystrixCollapser(fallbackEnabled = true, collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) + public User getUserSync(String id, String name) { + emulateNotFound(id); + return new User(id, name + id); // it should be network call + } + + /** + * Emulates situation when a server returns NOT_FOUND if user id = 5. + * + * @param id user id + */ + private void emulateNotFound(String id) throws RuntimeException { + if ("not found".equals(id)) { + throw new RuntimeException("not found"); + } + } + + /** + * Makes network call to a 'backup' server. + */ + private User fallback(String id, String name) { + return DEFAULT_USER; + } + + @HystrixCommand + private User fallbackCommand(String id, String name) { + return DEFAULT_USER; + } + + } + + /** + * Spring configuration. + */ + @Configurable + public static class CollapserTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java new file mode 100644 index 000000000..94aa954de --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/command/CommandTest.java @@ -0,0 +1,107 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.command; + + +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.AsyncResult; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * This test covers "Hystrix command" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Synchronous-Execution + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Asynchronous-Execution + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandTest.CommandTestConfig.class}) +public class CommandTest { + + @Autowired + private UserService userService; + + @Test + public void testGetUserAsync() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsync("1", "name: "); + + assertEquals("name: 1", f1.get().getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + // assert the command key name is the we're expecting + assertEquals("GetUserCommand", command.getCommandKey().name()); + // assert the command group key name is the we're expecting + assertEquals("UserService", command.getCommandGroup().name()); + // assert the command thread pool key name is the we're expecting + assertEquals("CommandTestAsync", command.getThreadPoolKey().name()); + // it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetUserSync() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserSync("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("getUserSync", command.getCommandKey().name()); + assertEquals("UserGroup", command.getCommandGroup().name()); + assertEquals("UserGroup", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + public static class UserService { + + @HystrixCommand(commandKey = "GetUserCommand", threadPoolKey = "CommandTestAsync") + public Future getUserAsync(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(groupKey = "UserGroup") + public User getUserSync(String id, String name) { + return new User(id, name + id); // it should be network call + } + + } + + @Configurable + public static class CommandTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java index bafc47c29..c3457a28d 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/conf/SpringApplicationContext.java @@ -1,6 +1,5 @@ package com.netflix.hystrix.contrib.javanica.test.spring.conf; -import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCollapserAspect; import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Bean; @@ -15,8 +14,4 @@ public HystrixCommandAspect hystrixAspect() { return new HystrixCommandAspect(); } - @Bean - public HystrixCollapserAspect hystrixCollapserAspect() { - return new HystrixCollapserAspect(); - } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java new file mode 100644 index 000000000..791cbfee9 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/collapser/CollapserPropertiesTest.java @@ -0,0 +1,87 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.collapser; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import com.netflix.hystrix.util.HystrixRollingNumberEvent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CollapserPropertiesTest.CollapserPropertiesTestConfig.class}) +public class CollapserPropertiesTest { + + + @Autowired + private UserService userService; + + @Test + public void testCollapser() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUser("1", "name: "); + User u2 = userService.getUser("2", "name: "); + User u3 = userService.getUser("3", "name: "); + User u4 = userService.getUser("4", "name: "); + + assertEquals("name: 1", u1.getName()); + assertEquals("name: 2", u2.getName()); + assertEquals("name: 3", u3.getName()); + assertEquals("name: 4", u4.getName()); + + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("getUser", command.getCommandKey().name()); + //When a command is fronted by an HystrixCollapser then this marks how many requests are collapsed into the single command execution. + assertEquals(4, command.getMetrics().getCumulativeCount(HystrixRollingNumberEvent.COLLAPSED)); + // confirm that it was a COLLAPSED command execution + assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); + // and that it was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + public static class UserService { + + @HystrixCommand + @HystrixCollapser(collapserKey = "GetUserCollapser", collapserProperties = { + @HystrixProperty(name = "maxRequestsInBatch", value = "1"), + @HystrixProperty(name = "timerDelayInMilliseconds", value = "200") + }) + public User getUser(String id, String name) { + return new User(id, name + id); + } + + @HystrixCommand + public User getUserDefProperties(String id, String name) { + return new User(id, name + id); + } + + } + + @Configurable + public static class CollapserPropertiesTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java new file mode 100644 index 000000000..0ae3f2ea9 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/configuration/command/CommandPropertiesTest.java @@ -0,0 +1,94 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.configuration.command; + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandPropertiesTest.CommandPropertiesTestConfig.class}) +public class CommandPropertiesTest { + + @Autowired + private UserService userService; + + @Test + public void testGetUser() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUser("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("GetUserCommand", command.getCommandKey().name()); + assertEquals("UserGroupKey", command.getCommandGroup().name()); + assertEquals("Test", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + // assert properties + assertEquals(110, command.getProperties().executionIsolationThreadTimeoutInMilliseconds().get().intValue()); + assertEquals(false, command.getProperties().executionIsolationThreadInterruptOnTimeout().get()); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetUserDefaultPropertiesValues() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserDefProperties("1", "name: "); + assertEquals("name: 1", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("getUserDefProperties", command.getCommandKey().name()); + assertEquals("UserService", command.getCommandGroup().name()); + assertEquals("UserService", command.getThreadPoolKey().name()); + assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + public static class UserService { + + @HystrixCommand(commandKey = "GetUserCommand", groupKey = "UserGroupKey", threadPoolKey = "Test", + commandProperties = { + @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "110"), + @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "false") + }) + public User getUser(String id, String name) { + return new User(id, name + id); // it should be network call + } + + @HystrixCommand + public User getUserDefProperties(String id, String name) { + return new User(id, name + id); // it should be network call + } + + } + + @Configurable + public static class CommandPropertiesTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/domain/User.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java similarity index 92% rename from hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/domain/User.java rename to hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java index 6161f428a..bc79a6ae6 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/domain/User.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java @@ -1,6 +1,8 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.domain; - +package com.netflix.hystrix.contrib.javanica.test.spring.domain; +/** + * Simple domain object for tests. + */ public class User { private String id; diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java new file mode 100644 index 000000000..a30efe256 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/error/ErrorPropagationTest.java @@ -0,0 +1,80 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.error; + + +import com.netflix.hystrix.HystrixEventType; +import com.netflix.hystrix.HystrixRequestLog; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.exception.HystrixBadRequestException; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.apache.commons.lang3.Validate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test covers "Error Propagation" functionality. + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, ErrorPropagationTest.ErrorPropagationTestConfig.class}) +public class ErrorPropagationTest { + + + @Autowired + private UserService userService; + + @Test(expected = HystrixBadRequestException.class) + public void testGetUser() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + userService.getUser("", ""); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserCommand = getHystrixCommandByKey("getUser"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + } finally { + context.shutdown(); + } + } + + + public static class UserService { + + @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey", + ignoreExceptions = {NullPointerException.class, IllegalArgumentException.class}) + public User getUser(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + @HystrixCommand + private String getUserIdCacheKey(String id, String name) { + return id + name; + } + + private void validate(String id, String name) throws NullPointerException, IllegalArgumentException { + Validate.notBlank(id); + Validate.notBlank(name); + } + + } + + @Configurable + public static class ErrorPropagationTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java new file mode 100644 index 000000000..8abb364ec --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/fallback/CommandFallbackTest.java @@ -0,0 +1,215 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.fallback; + +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.AsyncResult; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.apache.commons.lang3.Validate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test covers "Fallback" functionality. + *

+ * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Fallback + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, CommandFallbackTest.CommandTestConfig.class}) +public class CommandFallbackTest { + + @Autowired + private UserService userService; + + @Test + public void testGetUserAsyncWithFallback() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsync(" ", "name: "); + + assertEquals("def", f1.get().getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + assertEquals("getUserAsync", command.getCommandKey().name()); + + // confirm that 'getUserAsync' command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback waw successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetUserSyncWithFallback() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserSync(" ", "name: "); + + assertEquals("def", u1.getName()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand command = HystrixRequestLog.getCurrentRequest() + .getExecutedCommands().iterator().next(); + + assertEquals("getUserSync", command.getCommandKey().name()); + // confirm that command has failed + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } finally { + context.shutdown(); + } + } + + + /** + * * **************************** * + * * * TEST FALLBACK COMMANDS * * + * * **************************** * + */ + + + @Test + public void testGetUserAsyncWithFallbackCommand() throws ExecutionException, InterruptedException { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + Future f1 = userService.getUserAsyncFallbackCommand(" ", "name: "); + + assertEquals("def", f1.get().getName()); + + assertEquals(3, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserAsyncFallbackCommand = getHystrixCommandByKey( + "getUserAsyncFallbackCommand"); + com.netflix.hystrix.HystrixCommand firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); + com.netflix.hystrix.HystrixCommand secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); + + assertEquals("getUserAsyncFallbackCommand", getUserAsyncFallbackCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(getUserAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // confirm that first fallback has failed + assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that second fallback was successful + assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetUserSyncWithFallbackCommand() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User u1 = userService.getUserSyncFallbackCommand(" ", "name: "); + + assertEquals("def", u1.getName()); + assertEquals(3, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserSyncFallbackCommand = getHystrixCommandByKey( + "getUserSyncFallbackCommand"); + com.netflix.hystrix.HystrixCommand firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); + com.netflix.hystrix.HystrixCommand secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); + + assertEquals("getUserSyncFallbackCommand", getUserSyncFallbackCommand.getCommandKey().name()); + // confirm that command has failed + assertTrue(getUserSyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // confirm that first fallback has failed + assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that second fallback was successful + assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } finally { + context.shutdown(); + } + } + + + public static class UserService { + + @HystrixCommand(fallbackMethod = "fallback") + public Future getUserAsync(final String id, final String name) { + validate(id, name); // validate logic can be inside and outside of AsyncResult#invoke method + return new AsyncResult() { + @Override + public User invoke() { + // validate(id, name); possible put validation logic here, in case of any exception a fallback method will be invoked + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "fallback") + public User getUserSync(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + private User fallback(String id, String name) { + return new User("def", "def"); + } + + @HystrixCommand(fallbackMethod = "firstFallbackCommand") + public Future getUserAsyncFallbackCommand(final String id, final String name) { + return new AsyncResult() { + @Override + public User invoke() { + validate(id, name); + return new User(id, name + id); // it should be network call + } + }; + } + + @HystrixCommand(fallbackMethod = "firstFallbackCommand") + public User getUserSyncFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + // FALLBACK COMMANDS METHODS: + // This fallback methods will be processed as hystrix commands + + @HystrixCommand(fallbackMethod = "secondFallbackCommand") + private User firstFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + @HystrixCommand(fallbackMethod = "staticFallback") + private User secondFallbackCommand(String id, String name) { + validate(id, name); + return new User(id, name + id); // it should be network call + } + + private User staticFallback(String id, String name) { + return new User("def", "def"); + } + + private void validate(String id, String name) { + Validate.notBlank(id); + Validate.notBlank(name); + } + + } + + @Configurable + public static class CommandTestConfig { + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java new file mode 100644 index 000000000..540fdb559 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/observable/ObservableTest.java @@ -0,0 +1,135 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.observable; + + +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.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.apache.commons.lang3.Validate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import rx.Observable; +import rx.Observer; +import rx.util.functions.Action1; + +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test covers "Reactive Execution" functionality. + * https://github.com/Netflix/Hystrix/wiki/How-To-Use#Reactive-Execution + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, ObservableTest.ObservableTestConfig.class}) +public class ObservableTest { + + @Autowired + private UserService userService; + + @Test + public void testGetUserByIdObservable() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + // blocking + assertEquals("name: 1", userService.getUser("1", "name: ").toBlockingObservable().single().getName()); + + // non-blocking + // - this is a verbose anonymous inner-class approach and doesn't do assertions + Observable fUser = userService.getUser("1", "name: "); + fUser.subscribe(new Observer() { + + @Override + public void onCompleted() { + // nothing needed here + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(User v) { + System.out.println("onNext: " + v); + } + + }); + + Observable fs = userService.getUser("1", "name: "); + fs.subscribe(new Action1() { + + @Override + public void call(User user) { + assertEquals("name: 1", user.getName()); + } + }); + assertEquals(3, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserCommand = getHystrixCommandByKey("getUser"); + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetUserWithFallback() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + final User exUser = new User("def", "def"); + + // blocking + assertEquals(exUser, userService.getUser(" ", "").toBlockingObservable().single()); + assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); + com.netflix.hystrix.HystrixCommand getUserCommand = getHystrixCommandByKey("getUser"); + // confirm that command has failed + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); + // and that fallback was successful + assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); + } finally { + context.shutdown(); + } + } + + public static class UserService { + + @HystrixCommand(fallbackMethod = "staticFallback") + public Observable getUser(final String id, final String name) { + return new ObservableResult() { + @Override + public User invoke() { + validate(id, name); + return new User(id, name + id); + } + }; + } + + private User staticFallback(String id, String name) { + return new User("def", "def"); + } + + private void validate(String id, String name) { + Validate.notBlank(id); + Validate.notBlank(name); + } + } + + @Configurable + public static class ObservableTestConfig { + + @Bean + public UserService userService() { + return new UserService(); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/RestClient.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/RestClient.java deleted file mode 100644 index 4687fcab3..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/RestClient.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.client; - - -import com.netflix.hystrix.contrib.javanica.test.spring.rest.domain.User; - -import java.util.List; -import java.util.concurrent.Future; - -public interface RestClient { - - User getUserById(String id); - - Future getUserByIdAsync(String id); - - User getUserByName(String name); - - User getUserByIdSecondary(String id); - - List findAll(int pageNum, int pageSize); - - Future> findAllAsync(int pageNum, int pageSize); - - Future getUserByIdCollapserAsync(String id); - - User getUserByIdCollapser(String id); - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/SimpleRestClient.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/SimpleRestClient.java deleted file mode 100644 index 65611915b..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/client/SimpleRestClient.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.client; - -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; -import com.netflix.hystrix.contrib.javanica.collapser.CollapserResult; -import com.netflix.hystrix.contrib.javanica.command.AsyncCommand; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.domain.User; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.resource.UserResource; -import org.springframework.stereotype.Component; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Future; - -@Component("simpleRestClient") -public class SimpleRestClient implements RestClient { - - private static final User DEF_USER = new User("def", "def"); - - private UserResource userResource = new UserResource(); - - @HystrixCommand(commandKey = "GetUserByIdCommand", fallbackMethod = "getUserByIdSecondary") - @Override - public User getUserById(String id) { - return userResource.getUserById(id); - } - - @HystrixCommand(fallbackMethod = "getUserByIdSecondary") - @Override - public Future getUserByIdAsync(final String id) { - return new AsyncCommand() { - @Override - public User invoke() { - return userResource.getUserById(id); - } - }; - } - - @HystrixCommand(fallbackMethod = "defaultUser") - @Override - public User getUserByIdSecondary(String id) { - return userResource.getUserById(id); - } - - @HystrixCommand(fallbackMethod = "findAllFallback") - @Override - public List findAll(int pageNum, int pageSize) { - return userResource.findAll(pageNum, pageSize); - } - - @HystrixCommand(fallbackMethod = "findAllFallback") - @Override - public Future> findAllAsync(final int pageNum, final int pageSize) { - return new AsyncCommand>() { - @Override - public List invoke() { - return userResource.findAll(pageNum, pageSize); - } - }; - } - - @HystrixCollapser(commandMethod = "getUserById", - collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) - @Override - public Future getUserByIdCollapserAsync(String id) { - return CollapserResult.async(); - } - - @HystrixCollapser(commandMethod = "getUserById", - collapserProperties = {@HystrixProperty(name = "maxRequestsInBatch", value = "3")}) - @Override - public User getUserByIdCollapser(String id) { - return CollapserResult.sync(); - } - - @HystrixCommand(fallbackMethod = "findAllFallback2") - private List findAllFallback(int pageNum, int pageSize) { - throw new RuntimeException(); - } - - @HystrixCommand - private List findAllFallback2(int pageNum, int pageSize) { - return Arrays.asList(DEF_USER); - } - - @HystrixCommand(commandKey = "GetUserByNameCommand", fallbackMethod = "defaultUser", - threadPoolKey = "SimpleRestClientTest", - commandProperties = { - @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500"), - @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE") - }) - @Override - public User getUserByName(String name) { - return userResource.getUserByName(name); - } - - private User defaultUser(String id) { - return DEF_USER; - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/BaseRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/BaseRestClientTest.java deleted file mode 100644 index eb150be0a..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/BaseRestClientTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.collapser; - -import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.HystrixEventType; -import com.netflix.hystrix.HystrixRequestLog; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.client.RestClient; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.domain.User; -import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import static com.netflix.hystrix.contrib.javanica.CommonUtils.assertExecutedCommands; -import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public abstract class BaseRestClientTest { - - private static final User DEF_USER = new User("def", "def"); - - @Autowired - protected RestClient restClient; - - private HystrixRequestContext context; - - @Before - public void setUp() throws Exception { - context = HystrixRequestContext.initializeContext(); - } - - @After - public void tearDown() throws Exception { - context.shutdown(); - } - - @Test - public void testGetUserByIdCollapserAsynchronously() throws ExecutionException, InterruptedException { - List> futures = new ArrayList>(); - List users = new ArrayList(); - futures.add(restClient.getUserByIdCollapserAsync("1")); - futures.add(restClient.getUserByIdCollapserAsync("2")); - futures.add(restClient.getUserByIdCollapserAsync("3")); - futures.add(restClient.getUserByIdCollapserAsync("4")); - for (Future userFuture : futures) { - users.add(userFuture.get()); - } - assertEquals(4, users.size()); - assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - assertExecutedCommands("GetUserByIdCommand"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("GetUserByIdCommand"); - // confirm that it was a COLLAPSED command execution - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); - // and that it was successful - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); - } - - @Test - public void testGetUserByIdCollapserAsynchronouslyWithFallback() throws ExecutionException, InterruptedException { - int commandCount = 1; - int fallbackCommandCount = 1; - int totalCommandsCount = commandCount + fallbackCommandCount; - List> futures = new ArrayList>(); - List users = new ArrayList(); - futures.add(restClient.getUserByIdCollapserAsync("1")); - futures.add(restClient.getUserByIdCollapserAsync("5")); // this call should fail - for (Future userFuture : futures) { - users.add(userFuture.get()); - } - assertEquals(2, users.size()); - assertEquals(totalCommandsCount, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - assertExecutedCommands("GetUserByIdCommand", "getUserByIdSecondary"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("GetUserByIdCommand"); - - // confirm that it was a COLLAPSED command execution - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); - // and that one has failed - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that fallback was successful - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - - - @Test - public void testGetUserByIdCollapser() throws ExecutionException, InterruptedException { - - List users = new ArrayList(); - - users.add(restClient.getUserByIdCollapser("1")); - users.add(restClient.getUserByIdCollapser("2")); - users.add(restClient.getUserByIdCollapser("3")); - users.add(restClient.getUserByIdCollapser("4")); - - assertEquals(4, users.size()); - //assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); todo check this. - assertExecutedCommands("GetUserByIdCommand"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("GetUserByIdCommand"); - // confirm that it was a COLLAPSED command execution - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); - // and that it was successful - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/CglibRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/CglibRestClientTest.java deleted file mode 100644 index f77b6576c..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/CglibRestClientTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.collapser; - - -import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AopCglibConfig.class) -public class CglibRestClientTest extends BaseRestClientTest { -} - diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/JdkRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/JdkRestClientTest.java deleted file mode 100644 index ef6477193..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/collapser/JdkRestClientTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.collapser; - - -import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopJdkConfig; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AopJdkConfig.class) -public class JdkRestClientTest extends BaseRestClientTest { -} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/BaseRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/BaseRestClientTest.java deleted file mode 100644 index 79959454a..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/BaseRestClientTest.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.command; - -import com.netflix.hystrix.HystrixCommand; -import com.netflix.hystrix.HystrixEventType; -import com.netflix.hystrix.HystrixRequestLog; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.client.RestClient; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.domain.User; -import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -import static com.netflix.hystrix.contrib.javanica.CommonUtils.assertExecutedCommands; -import static com.netflix.hystrix.contrib.javanica.CommonUtils.getHystrixCommandByKey; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public abstract class BaseRestClientTest { - - private static final User DEF_USER = new User("def", "def"); - - @Autowired - protected RestClient restClient; - - private HystrixRequestContext context; - - @Before - public void setUp() throws Exception { - context = HystrixRequestContext.initializeContext(); - } - - @After - public void tearDown() throws Exception { - context.shutdown(); - } - - @Test - public void testGetUserByIdWithFallback() { - User user = restClient.getUserById("non-exists"); - assertExecutedCommands("GetUserByIdCommand", "getUserByIdSecondary"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("GetUserByIdCommand"); - assertEquals("SimpleRestClient", getUserByIdCommand.getCommandGroup().name()); - assertEquals("GetUserByIdCommand", getUserByIdCommand.getCommandKey().name()); - assertEquals(DEF_USER, user); - // confirm that command has failed - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that fallback was successful - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - - @Test - public void testGetUserByIdSuccess() { - User user = restClient.getUserById("1"); - User exUser = new User("1", "Alex"); - assertExecutedCommands("GetUserByIdCommand"); - assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("GetUserByIdCommand"); - assertEquals("SimpleRestClient", getUserByIdCommand.getCommandGroup().name()); - assertEquals("GetUserByIdCommand", getUserByIdCommand.getCommandKey().name()); - assertEquals(exUser, user); - // confirm that command has success - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); - } - - @Test - public void testGetUserByIdAsynchronouslyWithFallback() throws ExecutionException, InterruptedException { - Future userFuture = restClient.getUserByIdAsync("non-exists"); - User exUser = userFuture.get(); - assertExecutedCommands("getUserByIdAsync", "getUserByIdSecondary"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("getUserByIdAsync"); - assertEquals("SimpleRestClient", getUserByIdCommand.getCommandGroup().name()); - assertEquals("getUserByIdAsync", getUserByIdCommand.getCommandKey().name()); - assertEquals(DEF_USER, exUser); - // confirm that command has failed - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that fallback was successful - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - - @Test - public void testGetUserByIdAsynchronouslySuccess() throws ExecutionException, InterruptedException { - User user = restClient.getUserByIdAsync("1").get(); - User exUser = new User("1", "Alex"); - assertExecutedCommands("getUserByIdAsync"); - HystrixCommand getUserByIdCommand = getHystrixCommandByKey("getUserByIdAsync"); - assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - assertEquals("SimpleRestClient", getUserByIdCommand.getCommandGroup().name()); - assertEquals("getUserByIdAsync", getUserByIdCommand.getCommandKey().name()); - assertEquals(exUser, user); - // confirm that command has success - assertTrue(getUserByIdCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); - } - - @Test - public void testGetUserByName() { - User user = restClient.getUserByName("timeout"); - assertExecutedCommands("GetUserByNameCommand"); - assertEquals(DEF_USER, user); - assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - HystrixCommand hystrixCommand = HystrixRequestLog.getCurrentRequest() - .getExecutedCommands().toArray(new HystrixCommand[1])[0]; - assertEquals("GetUserByNameCommand", hystrixCommand.getCommandKey().name()); - assertEquals("SimpleRestClientTest", hystrixCommand.getThreadPoolKey().name()); - assertFalse(hystrixCommand.isExecutedInThread()); - - assertEquals(Integer.valueOf(500), - hystrixCommand.getProperties().executionIsolationThreadTimeoutInMilliseconds().get()); - // confirm that command has failed - assertTrue(hystrixCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that fallback was successful - assertTrue(hystrixCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - - @Test - public void testFindAllWithFallback() { - int commandCount = 1; - int fallbackCount = 2; - List users = restClient.findAll(3, 10); - assertEquals(1, users.size()); - assertEquals(DEF_USER, users.get(0)); - assertEquals(commandCount + fallbackCount, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - HystrixCommand findAllCommand = getHystrixCommandByKey("findAll"); - HystrixCommand findAllFallbackCommand = getHystrixCommandByKey("findAllFallback"); - HystrixCommand findAllFallback2Command = getHystrixCommandByKey("findAllFallback2"); - assertTrue(findAllCommand.isFailedExecution()); - assertTrue(findAllFallbackCommand.isFailedExecution()); - assertTrue(findAllFallback2Command.isExecutionComplete()); - assertExecutedCommands("findAll", "findAllFallback", "findAllFallback2"); - // confirm that initial command has failed - assertTrue(findAllCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // confirm that fallback has failed - assertTrue(findAllFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that last fallback was successful - assertTrue(findAllFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - - @Test - public void testFindAllAsynchronouslyWithFallback() throws ExecutionException, InterruptedException { - int commandCount = 1; - int fallbackCount = 2; - List users = restClient.findAllAsync(3, 10).get(); - assertEquals(1, users.size()); - assertEquals(DEF_USER, users.get(0)); - assertEquals(commandCount + fallbackCount, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size()); - HystrixCommand findAllAsyncCommand = getHystrixCommandByKey("findAllAsync"); - HystrixCommand findAllFallbackCommand = getHystrixCommandByKey("findAllFallback"); - HystrixCommand findAllFallback2Command = getHystrixCommandByKey("findAllFallback2"); - assertTrue(findAllAsyncCommand.isFailedExecution()); - assertTrue(findAllFallbackCommand.isFailedExecution()); - assertTrue(findAllFallback2Command.isExecutionComplete()); - assertExecutedCommands("findAllAsync", "findAllFallback", "findAllFallback2"); - // confirm that initial command has failed - assertTrue(findAllAsyncCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // confirm that fallback has failed - assertTrue(findAllFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); - // and that last fallback was successful - assertTrue(findAllFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/CglibRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/CglibRestClientTest.java deleted file mode 100644 index a4bec2477..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/CglibRestClientTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.command; - -import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AopCglibConfig.class) -public class CglibRestClientTest extends BaseRestClientTest { -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/JdkRestClientTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/JdkRestClientTest.java deleted file mode 100644 index 06e09a840..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/JdkRestClientTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.command; - -import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopJdkConfig; -import org.junit.runner.RunWith; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = AopJdkConfig.class) -public class JdkRestClientTest extends BaseRestClientTest { -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/LoadTimeWeavingTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/LoadTimeWeavingTest.java deleted file mode 100644 index a0be11f04..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/command/LoadTimeWeavingTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.command; - -//import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopLoadTimeWeavingConfig; -//import org.junit.runner.RunWith; -//import org.springframework.test.context.ContextConfiguration; -//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -//@RunWith(SpringJUnit4ClassRunner.class) -//@ContextConfiguration(classes = AopLoadTimeWeavingConfig.class) -// todo find way how only this test can be run with -javaagent:lib/spring-instrument.jar -public class LoadTimeWeavingTest /*extends BaseRestClientTest*/ { -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/BadRequestException.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/BadRequestException.java deleted file mode 100644 index 94030739a..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/BadRequestException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.exception; - -public class BadRequestException extends RuntimeException { - - public BadRequestException() { - } - - public BadRequestException(String message) { - super(message); - } - - public BadRequestException(String message, Throwable cause) { - super(message, cause); - } - - public BadRequestException(Throwable cause) { - super(cause); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/NotFoundException.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/NotFoundException.java deleted file mode 100644 index 771386d72..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/exception/NotFoundException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.exception; - -public class NotFoundException extends RuntimeException { - - public NotFoundException() { - } - - public NotFoundException(String message) { - super(message); - } - - public NotFoundException(String message, Throwable cause) { - super(message, cause); - } - - public NotFoundException(Throwable cause) { - super(cause); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/resource/UserResource.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/resource/UserResource.java deleted file mode 100644 index 4998cde2e..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/rest/resource/UserResource.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.rest.resource; - - -import static org.slf4j.helpers.MessageFormatter.format; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.domain.User; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.exception.BadRequestException; -import com.netflix.hystrix.contrib.javanica.test.spring.rest.exception.NotFoundException; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.Map; - -public class UserResource { - - public static final Map USER_MAP = ImmutableMap.builder() - .put("1", new User("1", "Alex")) - .put("2", new User("2", "Dave")) - .put("3", new User("3", "Jeff")) - .put("4", new User("4", "Chris")).build(); - private static final String USER_ID_ERROR_MSG = "user id cannot be null or empty. current value: {}"; - private static final String USER_NOT_FOUND = "user with id {} not found"; - - public User getUserById(String id) { - if (StringUtils.isBlank(id)) { - throw new BadRequestException(format(USER_ID_ERROR_MSG, id).getMessage()); - } - User user = USER_MAP.get(id); - if (user == null) { - throw new NotFoundException(format(USER_NOT_FOUND, id).getMessage()); - } - return user; - } - - public User getUserByName(String name) { - User user = null; - - if ("timeout".equalsIgnoreCase(name)) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - //e.printStackTrace(); - } - } - if (StringUtils.isBlank(name)) { - throw new BadRequestException("user name cannot be null or empty"); - } - for (Map.Entry entry : USER_MAP.entrySet()) { - if (name.equalsIgnoreCase(entry.getValue().getName())) { - user = entry.getValue(); - break; - } - } - if (user == null) { - throw new NotFoundException("user with name '" + name + "' not found"); - } - return user; - } - - public List findAll(int pageNum, int pageSize) { - List users = Lists.newArrayList(USER_MAP.values()) - .subList(pageNum - 1 * pageSize, pageNum * pageSize); - if (CollectionUtils.isEmpty(users)) { - throw new NotFoundException(); - } - return users; - } - -}