package org.fishwife.jrugged.spring;
import java.util.concurrent.Callable;
import org.fishwife.jrugged.CircuitBreakerException;
import org.fishwife.jrugged.CircuitBreakerExceptionMapper;
import org.fishwife.jrugged.DefaultFailureInterpreter;
import org.fishwife.jrugged.FailureInterpreter;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
public class AsyncCircuitBreaker extends org.fishwife.jrugged.CircuitBreaker {
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
* behavior (throwing a {@link CircuitBreakerException}). */
public AsyncCircuitBreaker() {
}
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
* behavior (throwing a {@link CircuitBreakerException}).
* @param name the name for the {@link AsyncCircuitBreaker}.
*/
public AsyncCircuitBreaker(String name) {
this.name = name;
}
/** Creates a {@link AsyncCircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
* behavior (throwing a {@link CircuitBreakerException}).
* @param fi the <code>FailureInterpreter</code> to use when
* determining whether a specific failure ought to cause the
* breaker to trip
*/
public AsyncCircuitBreaker(FailureInterpreter fi) {
failureInterpreter = fi;
}
/** Creates a {@link AsyncCircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
* behavior (throwing a {@link CircuitBreakerException}).
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param fi the <code>FailureInterpreter</code> to use when
* determining whether a specific failure ought to cause the
* breaker to trip
*/
public AsyncCircuitBreaker(String name, FailureInterpreter fi) {
this.name = name;
failureInterpreter = fi;
}
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and using the supplied {@link
* CircuitBreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param mapper helper used to translate a {@link
* CircuitBreakerException} into an application-specific one */
public AsyncCircuitBreaker(String name, CircuitBreakerExceptionMapper<? extends Exception> mapper) {
this.name = name;
exceptionMapper = mapper;
}
/** Creates a {@link AsyncCircuitBreaker} with the provided {@link
* FailureInterpreter} and using the provided {@link
* CircuitBreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param fi the <code>FailureInterpreter</code> to use when
* determining whether a specific failure ought to cause the
* breaker to trip
* @param mapper helper used to translate a {@link
* CircuitBreakerException} into an application-specific one */
public AsyncCircuitBreaker(String name,
FailureInterpreter fi,
CircuitBreakerExceptionMapper<? extends Exception> mapper) {
this.name = name;
failureInterpreter = fi;
exceptionMapper = mapper;
}
/** Wrap the given service call with the {@link AsyncCircuitBreaker} protection logic.
* @param callable the {@link java.util.concurrent.Callable} to attempt
* @return {@link ListenableFuture} of whatever callable would return
* @throws org.fishwife.jrugged.CircuitBreakerException if the
* breaker was OPEN or HALF_CLOSED and this attempt wasn't the
* reset attempt
* @throws Exception if the {@link org.fishwife.jrugged.CircuitBreaker} is in OPEN state
*/
public <T> ListenableFuture<T> invokeAsync(Callable<ListenableFuture<T>> callable) throws Exception {
final SettableListenableFuture<T> response = new SettableListenableFuture<T>();
ListenableFutureCallback<T> callback = new ListenableFutureCallback<T>() {
@Override
public void onSuccess(T result) {
close();
response.set(result);
}
@Override
public void onFailure(Throwable ex) {
try {
handleFailure(ex);
} catch (Exception e) {
response.setException(e);
}
}
};
if (!byPass) {
if (!allowRequest()) {
throw mapException(new CircuitBreakerException());
}
try {
isAttemptLive = true;
callable.call().addCallback(callback);
return response;
} catch (Throwable cause) {
// This shouldn't happen because Throwables are handled in the async onFailure callback
handleFailure(cause);
}
throw new IllegalStateException("not possible");
}
else {
callable.call().addCallback(callback);
return response;
}
}
}