/*
* 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.core.retrier;
import com.torodb.common.util.RetryHelper;
import com.torodb.common.util.RetryHelper.DelegateExceptionHandler;
import com.torodb.common.util.RetryHelper.ExceptionHandler;
import com.torodb.common.util.RetryHelper.RetryCallback;
import com.torodb.core.exceptions.user.UserException;
import com.torodb.core.transaction.RollbackException;
import java.util.EnumSet;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import javax.annotation.Nullable;
/**
*
*/
public abstract class AbstractHintableRetrier implements Retrier {
private final ExceptionHandler<Object, RetrierGiveUpException> throwHandler =
(RetryCallback<Object> callback, Exception t, int attempts) -> {
throw new RetrierGiveUpException(t);
};
protected abstract <R, T extends Exception> ExceptionHandler<R, T> getExceptionHandler(
EnumSet<Hint> hints, ExceptionHandler<R, T> delegateHandler);
@SuppressWarnings("unchecked")
protected <R> ExceptionHandler<R, RetrierGiveUpException> getExceptionHandler(
EnumSet<Hint> hints) {
//This horrible cast is done to not create the default handler each time
return new AbortFailFastExceptionHandler<>(
getExceptionHandler(
hints,
(ExceptionHandler<R, RetrierGiveUpException>) throwHandler
)
);
}
@Override
public <R> R retry(Callable<R> callable, EnumSet<Hint> hints) throws RetrierGiveUpException {
ExceptionHandler<R, RetrierGiveUpException> handler = getExceptionHandler(hints);
return RetryHelper.retryOrThrow(handler, callable);
}
@Override
public <R> R retry(Callable<R> callable, Supplier<R> defaultValueSupplier,
EnumSet<Hint> hints) {
ExceptionHandler<R, RetrierGiveUpException> handler = getExceptionHandler(hints);
try {
return RetryHelper.retryOrThrow(handler, callable);
} catch (RetrierGiveUpException ex) {
return defaultValueSupplier.get();
}
}
@Override
public <R, T extends Exception> R retry(Callable<R> callable,
ExceptionHandler<R, T> handler, EnumSet<Hint> hints)
throws T {
ExceptionHandler<R, T> subHandler = new AbortFailFastExceptionHandler<>(
getExceptionHandler(hints, handler)
);
return RetryHelper.retryOrThrow(subHandler, callable);
}
@Override
public <R> R retryOrUserEx(Callable<R> callable, EnumSet<Hint> hints) throws
UserException, RetrierGiveUpException {
ExceptionHandler<R, RetrierGiveUpException> giveUpHandler = getExceptionHandler(hints);
ExceptionHandler<R, WrapperException> throwOrUserRetrier =
(RetryCallback<R> callback, Exception t, int attempts) -> {
if (t instanceof UserException) {
throw new WrapperException((UserException) t);
}
try {
giveUpHandler.handleException(callback, t, attempts);
} catch (RetrierGiveUpException ex) {
throw new WrapperException(ex);
}
};
try {
return RetryHelper.retryOrThrow(throwOrUserRetrier, callable);
} catch (WrapperException ex) {
if (ex.getGiveUpException() != null) {
throw ex.getGiveUpException();
} else if (ex.getUserException() != null) {
throw ex.getUserException();
} else {
throw new AssertionError("Unexpected case where " + WrapperException.class + " does "
+ "not wrap either a give up exception or a user exception. Its cause is "
+ ex.getCause(), ex);
}
}
}
/**
* A {@link ExceptionHandler} that fails if {@link RetrierAbortException} is catched and delegates
* on the given delegate in other case.
*
* @param <R>
* @param <T>
*/
private static class AbortFailFastExceptionHandler<R, T extends Exception>
extends DelegateExceptionHandler<R, T> {
public AbortFailFastExceptionHandler(ExceptionHandler<R, T> delegate) {
super(delegate);
}
@Override
public void handleException(RetryCallback<R> callback, Exception t, int attempts)
throws T {
if (t instanceof RuntimeException) {
if (t instanceof RollbackException) {
super.handleException(callback, t, attempts);
} else {
throw (RuntimeException) t;
}
} else {
super.handleException(callback, t, attempts);
}
}
}
private static final class WrapperException extends Exception {
private static final long serialVersionUID = -3680673355882631935L;
private final UserException userException;
private final RetrierGiveUpException giveUpException;
public WrapperException(RetrierGiveUpException giveUpException) {
super(giveUpException);
this.giveUpException = giveUpException;
this.userException = null;
}
public WrapperException(UserException userException) {
super(userException);
this.userException = userException;
this.giveUpException = null;
}
@Nullable
public UserException getUserException() {
return userException;
}
@Nullable
public RetrierGiveUpException getGiveUpException() {
return giveUpException;
}
}
}