/**
* Copyright 2014 Opower, 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.opower.rest.client.generator.hystrix;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* InvocationHandler that proxies method calls in a HystrixCommand execution.
*
* @param <T> The type of the Resource
*/
final class HystrixCommandInvocationHandler<T> implements InvocationHandler {
private static final Logger LOG = LoggerFactory.getLogger(HystrixCommandInvocationHandler.class);
private final T target;
private final Map<Method, HystrixCommand.Setter> commandSetters;
private final Map<Method, Callable<?>> fallbacks;
private HystrixCommandInvocationHandler(T target,
final Map<Method, HystrixCommand.Setter> commandSetters,
final Map<Method, Callable<?>> fallbacks) {
this.target = checkNotNull(target);
this.commandSetters = checkNotNull(commandSetters);
this.fallbacks = checkNotNull(fallbacks);
}
/**
* All methods will be wrapped in a HystrixCommand set up according to the provided Map of HystrixCommand.Setter.
*
* @param resourceInterface the interface that has methods annotated for JAX-RS resource purposes
* @param toProxy the actual resource instance to proxy
* @param commandSetters should you desire to have different configuration for the HystrixCommands per method,
* you can pass that mapping here directly.
* @param fallbacks The fallbacks to use
* @param <T> the type of the resource interface
* @return a archmage that wraps calls to the underlying resource instance with metrics tracking logic
*/
@SuppressWarnings("unchecked")
static <T> T proxy(Class<T> resourceInterface,
T toProxy,
Map<Method, HystrixCommand.Setter> commandSetters,
Map<Method, Callable<?>> fallbacks) {
LOG.info("Creating Hystrix based client");
return (T) Proxy.newProxyInstance(
toProxy.getClass().getClassLoader(),
new Class<?>[]{resourceInterface},
new HystrixCommandInvocationHandler<>(toProxy, commandSetters, fallbacks));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (this.commandSetters.containsKey(method)) {
@SuppressWarnings("unchecked")
ProxyCommand command = new ProxyCommand(this.commandSetters.get(method), method, args,
(Callable<Object>) this.fallbacks.get(method), this.target);
return execute(command);
} else {
return method.invoke(this.target, args);
}
}
/**
* Executes the command synchronously and throws HystrixRuntimeExceptions for all but cases where there is no fallback
* configured and a non-hystrix related exception is thrown by the underlying work. Visible for testing
*
* @param command the HystrixCommand to execute
* @return the result of the HystrixCommand
* @throws Throwable for convenience
*/
static Object execute(HystrixCommand command) throws Throwable {
try {
return command.execute();
} catch (HystrixBadRequestException ex) {
throw ex.getCause();
} catch (HystrixRuntimeException ex) {
// fallback failures should always just throw the HystrixRuntimeException
if (ex.getFallbackException() != null) {
throw ex;
}
switch (ex.getFailureType()) {
case COMMAND_EXCEPTION:
throw throwCause(ex);
default:
throw ex;
}
}
}
private static Throwable throwCause(HystrixRuntimeException ex) {
Throwable cause = ex.getCause();
if (cause != null) {
if (cause instanceof InvocationTargetException) {
return ((InvocationTargetException) cause).getTargetException();
} else {
return cause;
}
} else {
return ex;
}
}
}