/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.common.util;
import com.google.common.base.Optional;
import java.util.concurrent.Callable;
import java.util.function.IntBinaryOperator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
*
*/
public class RetryHelper {
private RetryHelper() {
}
public static <R> R retry(
@Nonnull ExceptionHandler<R, RuntimeException> handler,
Callable<R> job) {
return retryOrThrow(handler, job);
}
public static <R, T extends Exception> R retryOrThrow(@Nonnull ExceptionHandler<R, T> handler,
Callable<R> job) throws T {
RetryCallback<R> retryCallback = null;
int attempts = 0;
do {
try {
return job.call();
} catch (Exception ex) {
attempts++;
if (retryCallback == null) {
retryCallback = new RetryCallback<>();
}
handler.handleException(retryCallback, ex, attempts);
switch (retryCallback.action) {
case RETURN: {
return retryCallback.result;
}
default:
}
}
}
while (true);
}
public static class RetryCallback<R> {
@Nonnull
RetryAction action = RetryAction.RETRY;
@Nullable
R result;
public void doRetry() {
action = RetryAction.RETRY;
}
public void doReturn(R result) {
this.result = result;
action = RetryAction.RETURN;
}
public RetryAction getAction() {
return action;
}
public R getResult() {
return result;
}
}
public static enum RetryAction {
RETRY,
RETURN;
}
public static interface ExceptionHandler<R, T extends Exception> {
void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T;
}
public static <R, T extends Exception> ExceptionHandler<R, T> throwHandler() {
return ThrowExceptionHandler.getInstance();
}
public static <R, T extends Exception> ExceptionHandler<R, T> alwaysRetryHandler() {
return AlwaysRetryExceptionHandler.getInstance();
}
public static <R, T extends Exception> ExceptionHandler<R, T> defaultValueHandler(
R defaultResult) {
return new DefaultValueExceptionHandler<>(defaultResult);
}
public static <R, T extends Exception> ExceptionHandler<R, T> retryUntilHandler(int maxAttempts,
R defaultValue) {
ExceptionHandler<R, T> beforeHandler = alwaysRetryHandler();
ExceptionHandler<R, T> afterHandler = defaultValueHandler(defaultValue);
return new UntilAttemptsExceptionHandler<>(maxAttempts, beforeHandler, afterHandler);
}
public static <R, T extends Exception> ExceptionHandler<R, T> retryUntilHandler(int maxAttempts,
ExceptionHandler<R, T> afterHandler) {
ExceptionHandler<R, T> beforeHandler = alwaysRetryHandler();
return new UntilAttemptsExceptionHandler<>(maxAttempts, beforeHandler, afterHandler);
}
public static <R, T extends Exception> ExceptionHandler<R, T> waitExceptionHandler(int millis) {
return new FixedMillisWaitExceptionHandler<>(millis);
}
/**
* Creates an {@link ExceptionHandler} that stores the given exception.
* @param <T1> The exception type that the given exception can store
*/
public static <R, T1 extends Exception, T2 extends Exception> StorerExceptionHandler<R, T1, T2>
storerExceptionHandler(Class<T1> excetionClass, ExceptionHandler<R, T2> delegate) {
return new StorerExceptionHandler<>(delegate, excetionClass);
}
public static class ThrowExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
@SuppressWarnings("rawtypes")
private static final ThrowExceptionHandler INSTANCE = new ThrowExceptionHandler();
@SuppressWarnings("unchecked")
private static <R2, T2 extends Exception> ThrowExceptionHandler<R2, T2> getInstance() {
return INSTANCE;
}
@SuppressWarnings("unchecked")
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
try {
throw (T) t;
} catch (Exception throwable) {
throw new RuntimeException(throwable);
}
}
}
public static class AlwaysRetryExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
@SuppressWarnings("rawtypes")
private static final AlwaysRetryExceptionHandler INSTANCE = new AlwaysRetryExceptionHandler();
@SuppressWarnings("unchecked")
private static <R2, T2 extends Exception> AlwaysRetryExceptionHandler<R2, T2> getInstance() {
return INSTANCE;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
callback.doRetry();
}
}
public static class DefaultValueExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
private final R defaultValue;
public DefaultValueExceptionHandler(R defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
callback.doReturn(defaultValue);
}
}
public static class DelegateExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
private final ExceptionHandler<R, T> delegate;
public DelegateExceptionHandler(ExceptionHandler<R, T> delegate) {
this.delegate = delegate;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
delegate.handleException(callback, t, attempts);
}
}
public static class UntilAttemptsExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
private final int maxAttempts;
private final ExceptionHandler<R, T> beforeLimitDelegate;
private final ExceptionHandler<R, T> afterLimitDelegate;
public UntilAttemptsExceptionHandler(int maxAttempts,
ExceptionHandler<R, T> beforeLimitDelegate,
ExceptionHandler<R, T> afterLimitDelegate) {
this.maxAttempts = maxAttempts;
this.beforeLimitDelegate = beforeLimitDelegate;
this.afterLimitDelegate = afterLimitDelegate;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
if (attempts < maxAttempts) {
beforeLimitDelegate.handleException(callback, t, attempts);
} else {
afterLimitDelegate.handleException(callback, t, attempts);
}
}
}
public static class FixedMillisWaitExceptionHandler<R, T extends Exception> implements
ExceptionHandler<R, T> {
private final int millis;
public FixedMillisWaitExceptionHandler(int millis) {
this.millis = millis;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
Thread.interrupted();
}
callback.doRetry();
}
}
@NotThreadSafe
public static class IncrementalWaitExceptionHandler<R, T extends Exception>
extends DelegateExceptionHandler<R, T> {
private final IntBinaryOperator newMillisFunction;
private int currentMillis;
/**
*
* @param newMillisFunction the first argument is the millis that it has wait on the last
* iteration (or 0 for the first) and the second the attempts. The
* result is the number of millis that will wait or a negative number
* if it should give up.
* @param delegate the exception handler on which delegate when giving up.
*/
public IncrementalWaitExceptionHandler(IntBinaryOperator newMillisFunction,
ExceptionHandler<R, T> delegate) {
super(delegate);
this.newMillisFunction = newMillisFunction;
this.currentMillis = 1;
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts)
throws T {
try {
currentMillis = newMillisFunction.applyAsInt(currentMillis, attempts);
if (currentMillis < 0) {
super.handleException(callback, t, attempts);
} else {
if (currentMillis > 0) {
Thread.sleep(currentMillis);
}
callback.doRetry();
}
} catch (InterruptedException ex) {
Thread.interrupted();
}
}
}
public static class StorerExceptionHandler<R, T1 extends Exception, T2 extends Exception>
extends DelegateExceptionHandler<R, T2> {
private final Class<T1> exClass;
private Optional<T1> thrown;
public StorerExceptionHandler(ExceptionHandler<R, T2> delegate, Class<T1> exClass) {
super(delegate);
this.exClass = exClass;
thrown = Optional.absent();
}
@Override
@SuppressWarnings("unchecked")
public void handleException(RetryCallback<R> callback, Exception t, int attempts) throws T2 {
if (exClass.isInstance(t)) {
thrown = Optional.of((T1) t);
} else {
super.handleException(callback, t, attempts);
}
}
public Optional<T1> getThrown() {
return thrown;
}
}
}