/*
* Copyright 2014 Avanza Bank AB
*
* 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.avanza.astrix.ft.hystrix;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avanza.astrix.core.AstrixCallStackTrace;
import com.avanza.astrix.core.ServiceUnavailableException;
import com.avanza.astrix.core.function.CheckedCommand;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommand.Setter;
import com.netflix.hystrix.exception.HystrixRuntimeException;
/**
* @author Elias Lindholm (elilin)
* @author Kristoffer Erlandsson (krierl)
*/
class HystrixCommandFacade<T> {
private static final Logger log = LoggerFactory.getLogger(HystrixCommandFacade.class);
private final CheckedCommand<T> command;
private final Setter hystrixConfiguration;
private HystrixCommandFacade(CheckedCommand<T> command, Setter hystrixConfiguration) {
this.command = command;
this.hystrixConfiguration = hystrixConfiguration;
}
public static <T> T execute(CheckedCommand<T> command, Setter settings) throws Throwable {
return new HystrixCommandFacade<>(command, settings).execute();
}
protected T execute() throws Throwable {
HystrixCommand<HystrixResult<T>> command = createHystrixCommand();
HystrixResult<T> result;
try {
result = command.execute();
} catch (HystrixRuntimeException e) {
// TODO: Add unit test for this case
e.printStackTrace();
throw new ServiceUnavailableException(e.getFailureType().toString());
}
throwExceptionIfExecutionFailed(result);
return result.getResult();
}
private void throwExceptionIfExecutionFailed(HystrixResult<T> result) throws Throwable {
if (result.getException() != null) {
AstrixCallStackTrace trace = new AstrixCallStackTrace();
appendStackTrace(result.getException(), trace);
throw result.getException();
}
}
private void appendStackTrace(Throwable exception, AstrixCallStackTrace trace) {
Throwable lastThowableInChain = exception;
while (lastThowableInChain.getCause() != null) {
lastThowableInChain = lastThowableInChain.getCause();
}
lastThowableInChain.initCause(trace);
}
private HystrixCommand<HystrixResult<T>> createHystrixCommand() {
return new HystrixCommand<HystrixResult<T>>(hystrixConfiguration) {
@Override
protected HystrixResult<T> run() throws Exception {
try {
return HystrixResult.success(command.call());
} catch (Throwable e) {
return handleException(e);
}
}
private HystrixResult<T> handleException(Throwable cause) {
if (cause instanceof ServiceUnavailableException) {
// Only ServiceUnavailableExceptions are propagated and counted as failures for the circuit breaker
throw (ServiceUnavailableException) cause;
}
// Any other exception is treated as a service exception and does not count as failures for the circuit breaker
return HystrixResult.exception(cause);
}
@Override
protected HystrixResult<T> getFallback() {
// getFallback is only invoked when the underlying api threw an ServiceUnavailableException, or the
// when the invocation reached timeout. In any case, treat this as service unavailable.
String cause = resolveUnavailableCause();
log.info(String.format("Aborted command execution: cause=%s circuit=%s", cause, this.getCommandKey().name()));
if (isFailedExecution()) {
// Underlying service threw ServiceUnavailableException
return HystrixResult.exception(getFailedExecutionException());
}
// Timeout or rejected in queue
return HystrixResult.exception(new ServiceUnavailableException(String.format("cause=%s service=%s executionTime=%s",
Objects.toString(cause), getCommandKey().name(), getExecutionTimeInMilliseconds())));
}
private String resolveUnavailableCause() {
if (isResponseRejected()) {
return "REJECTED_EXECUTION";
}
if (isResponseTimedOut()) {
return "TIMEOUT";
}
if (isResponseShortCircuited()) {
return "SHORT_CIRCUITED";
}
if (isFailedExecution()) {
return "UNAVAILABLE";
}
return "UNKNOWN";
}
};
}
private static class HystrixResult<T> {
private T result;
private Throwable exception;
public static <T> HystrixResult<T> success(T result) {
return new HystrixResult<>(result, null);
}
public static <T> HystrixResult<T> exception(Throwable exception) {
return new HystrixResult<>(null, exception);
}
public HystrixResult(T result, Throwable exception) {
this.result = result;
this.exception = exception;
}
public T getResult() {
return result;
}
public Throwable getException() {
return exception;
}
}
}