io.github.resilience4j.retry.RetryConfig Maven / Gradle / Ivy
Show all versions of resilience4j-retry Show documentation
/*
*
* Copyright 2016 Robert Winkler
*
* 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 io.github.resilience4j.retry;
import io.github.resilience4j.core.IntervalBiFunction;
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.core.lang.Nullable;
import io.github.resilience4j.core.predicate.PredicateCreator;
import java.io.Serializable;
import java.time.Duration;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class RetryConfig implements Serializable {
private static final long serialVersionUID = 3522903275067138911L;
public static final long DEFAULT_WAIT_DURATION = 500;
public static final int DEFAULT_MAX_ATTEMPTS = 3;
private static final IntervalFunction DEFAULT_INTERVAL_FUNCTION = numOfAttempts -> DEFAULT_WAIT_DURATION;
private static final IntervalBiFunction DEFAULT_INTERVAL_BI_FUNCTION = IntervalBiFunction.ofIntervalFunction(DEFAULT_INTERVAL_FUNCTION);
private static final Predicate DEFAULT_RECORD_FAILURE_PREDICATE = throwable -> true;
@SuppressWarnings("unchecked")
private Class extends Throwable>[] retryExceptions = new Class[0];
@SuppressWarnings("unchecked")
private Class extends Throwable>[] ignoreExceptions = new Class[0];
@Nullable
private transient Predicate retryOnExceptionPredicate;
@Nullable
private transient Predicate retryOnResultPredicate;
@Nullable
private BiConsumer consumeResultBeforeRetryAttempt;
private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
private boolean failAfterMaxAttempts = false;
private boolean writableStackTraceEnabled = true;
@Nullable
private transient IntervalFunction intervalFunction;
private transient IntervalBiFunction intervalBiFunction = DEFAULT_INTERVAL_BI_FUNCTION;
// The final exception predicate
private transient Predicate exceptionPredicate;
private RetryConfig() {
}
/**
* Returns a builder to create a custom RetryConfig.
*
* @param The type being built.
* @return a {@link Builder}
*/
public static Builder custom() {
return new Builder<>();
}
public static Builder from(RetryConfig baseConfig) {
return new Builder<>(baseConfig);
}
/**
* Creates a default Retry configuration.
*
* @return a default Retry configuration.
*/
public static RetryConfig ofDefaults() {
return new Builder().build();
}
/**
* @return the maximum allowed attempts to make.
*/
public int getMaxAttempts() {
return maxAttempts;
}
/**
* @return if exception should be thrown after max attempts are made with no satisfactory result
*/
public boolean isFailAfterMaxAttempts() {
return failAfterMaxAttempts;
}
/**
* @return if any thrown {@link MaxRetriesExceededException} should contain a stacktrace
*/
public boolean isWritableStackTraceEnabled() {
return writableStackTraceEnabled;
}
/**
* Use {@link RetryConfig#intervalBiFunction} instead, this method is kept for backwards compatibility
*/
@Nullable
@Deprecated
public Function getIntervalFunction() {
return intervalFunction;
}
/**
* Return the IntervalBiFunction which calculates wait interval based on result or exception
*
* @param The type of result.
* @return the interval bi function
*/
@SuppressWarnings("unchecked")
public IntervalBiFunction getIntervalBiFunction() {
return intervalBiFunction;
}
public Predicate getExceptionPredicate() {
return exceptionPredicate;
}
/**
* Return the Predicate which evaluates if an result should be retried. The Predicate must
* return true if the result should be retried, otherwise it must return false.
*
* @param The type of result.
* @return the result predicate
*/
@SuppressWarnings("unchecked")
@Nullable
public Predicate getResultPredicate() {
return retryOnResultPredicate;
}
/**
* Return the BiConsumer which performs post actions if the result should be retried.
*
* @param The type of result.
* @return the onFailureBiConsumer
*/
public BiConsumer getConsumeResultBeforeRetryAttempt(){
return consumeResultBeforeRetryAttempt;
}
public static class Builder {
private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
private boolean failAfterMaxAttempts = false;
private boolean writableStackTraceEnabled = true;
@Nullable
private IntervalFunction intervalFunction;
@Nullable
private Predicate retryOnExceptionPredicate;
@Nullable
private Predicate retryOnResultPredicate;
@Nullable
private IntervalBiFunction intervalBiFunction;
@Nullable
BiConsumer consumeResultBeforeRetryAttempt;
@SuppressWarnings("unchecked")
private Class extends Throwable>[] retryExceptions = new Class[0];
@SuppressWarnings("unchecked")
private Class extends Throwable>[] ignoreExceptions = new Class[0];
@Override
public String toString() {
StringBuilder retryConfig = new StringBuilder("RetryConfig {");
retryConfig.append("maxAttempts=");
retryConfig.append(maxAttempts);
retryConfig.append(", failAfterMaxAttempts=");
retryConfig.append(failAfterMaxAttempts);
retryConfig.append(", writableStackTraceEnabled=");
retryConfig.append(writableStackTraceEnabled);
retryConfig.append(", intervalFunction=");
retryConfig.append(intervalFunction);
retryConfig.append(", retryOnExceptionPredicate=");
retryConfig.append(retryOnExceptionPredicate);
retryConfig.append(", retryOnResultPredicate=");
retryConfig.append(retryOnResultPredicate);
retryConfig.append(", intervalBiFunction=");
retryConfig.append(intervalBiFunction);
retryConfig.append(", consumeResultBeforeRetryAttempt=");
retryConfig.append(consumeResultBeforeRetryAttempt);
retryConfig.append(", retryExceptions=");
retryConfig.append(retryExceptions);
retryConfig.append(", ignoreExceptions=");
retryConfig.append(ignoreExceptions);
return retryConfig.toString();
}
public Builder() {
}
@SuppressWarnings("unchecked")
public Builder(RetryConfig baseConfig) {
this.maxAttempts = baseConfig.maxAttempts;
this.retryOnExceptionPredicate = baseConfig.retryOnExceptionPredicate;
this.retryOnResultPredicate = baseConfig.retryOnResultPredicate;
this.consumeResultBeforeRetryAttempt = baseConfig.consumeResultBeforeRetryAttempt;
this.failAfterMaxAttempts = baseConfig.failAfterMaxAttempts;
this.writableStackTraceEnabled = baseConfig.writableStackTraceEnabled;
this.retryExceptions = baseConfig.retryExceptions;
this.ignoreExceptions = baseConfig.ignoreExceptions;
if (baseConfig.intervalFunction != null) {
this.intervalFunction = baseConfig.intervalFunction;
} else {
this.intervalBiFunction = baseConfig.intervalBiFunction;
}
}
public Builder maxAttempts(int maxAttempts) {
if (maxAttempts < 1) {
throw new IllegalArgumentException(
"maxAttempts must be greater than or equal to 1");
}
this.maxAttempts = maxAttempts;
return this;
}
public Builder waitDuration(Duration waitDuration) {
if (waitDuration.toMillis() >= 0) {
this.intervalBiFunction = (attempt, either) -> waitDuration.toMillis();
} else {
throw new IllegalArgumentException(
"waitDuration must be a positive value");
}
return this;
}
/**
* Configures a Predicate which evaluates if an result should be retried. The Predicate must
* return true if the result should be retried, otherwise it must return false.
*
* @param predicate the Predicate which evaluates if an result should be retried or not.
* @return the RetryConfig.Builder
*/
public Builder retryOnResult(Predicate predicate) {
this.retryOnResultPredicate = predicate;
return this;
}
/**
* Configure a BiConsumer which performs post actions if a result should be retried.
*
* @param consumeResultBeforeRetryAttempt the BiConsumer which performs post actions if a result should be retried.
* @return the RetryConfig.Builder
*/
public Builder consumeResultBeforeRetryAttempt(BiConsumer consumeResultBeforeRetryAttempt){
this.consumeResultBeforeRetryAttempt = consumeResultBeforeRetryAttempt;
return this;
}
/**
* Configures the Retry to throw a {@link MaxRetriesExceeded} exception once {@link #maxAttempts} has been reached,
* and the result is still not satisfactory (according to {@link #retryOnResultPredicate})
*
* @param bool a boolean flag to enable or disable throwing. (Default is {@code false}
* @return the RetryConfig.Builder
*/
public Builder failAfterMaxAttempts(boolean bool) {
this.failAfterMaxAttempts = bool;
return this;
}
/**
* Enables writable stack traces. When set to false, {@link Exception#getStackTrace()}
* returns a zero length array. This may be used to reduce log spam when the Retry
* has exceeded the maximum nbr of attempts, and flag {@link #failAfterMaxAttempts} has been enabled.
* The thrown {@link MaxRetriesExceededException} will then have no stacktrace.
*
* @param bool the flag to enable writable stack traces.
* @return the RetryConfig.Builder
*/
public Builder writableStackTraceEnabled(boolean bool) {
this.writableStackTraceEnabled = bool;
return this;
}
/**
* Set a function to modify the waiting interval after a failure. By default the interval
* stays the same.
*
* @param f Function to modify the interval after a failure
* @return the RetryConfig.Builder
*/
public Builder intervalFunction(IntervalFunction f) {
this.intervalFunction = f;
return this;
}
/**
* Set a function to modify the waiting interval after a failure based on attempt number and result or exception.
*
* @param f Function to modify the interval after a failure
* @return the RetryConfig.Builder
*/
public Builder intervalBiFunction(IntervalBiFunction f) {
this.intervalBiFunction = f;
return this;
}
/**
* Configures a Predicate which evaluates if an exception should be retried. The Predicate
* must return true if the exception should be retried, otherwise it must return false.
*
* @param predicate the Predicate which evaluates if an exception should be retried or not.
* @return the RetryConfig.Builder
*/
public Builder retryOnException(Predicate predicate) {
this.retryOnExceptionPredicate = predicate;
return this;
}
/**
* Configures a list of error classes that are recorded as a failure and thus are retried.
* Any exception matching or inheriting from one of the list will be retried, unless ignored
* via
*
* @param errorClasses the error classes that are retried
* @return the RetryConfig.Builder
* @see #ignoreExceptions(Class[]) ). Ignoring an exception has priority over retrying an
* exception.
*
* Example: retryOnExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class)
* would retry all Errors and checked Exceptions, and ignore unchecked
*
* For a more sophisticated exception management use the
* @see #retryOnException(Predicate) method
*/
@SuppressWarnings("unchecked")
@SafeVarargs
public final Builder retryExceptions(
@Nullable Class extends Throwable>... errorClasses) {
this.retryExceptions = errorClasses != null ? errorClasses : new Class[0];
return this;
}
/**
* Configures a list of error classes that are ignored and thus are not retried. Any
* exception matching or inheriting from one of the list will not be retried, even if marked
* via
*
* @param errorClasses the error classes that are retried
* @return the RetryConfig.Builder
* @see #retryExceptions(Class[]) . Ignoring an exception has priority over retrying an
* exception.
*
* Example: ignoreExceptions(Throwable.class) and retryOnExceptions(Exception.class) would
* capture nothing
*
* Example: ignoreExceptions(Exception.class) and retryOnExceptions(Throwable.class) would
* capture Errors
*
* For a more sophisticated exception management use the
* @see #retryOnException(Predicate) method
*/
@SuppressWarnings("unchecked")
@SafeVarargs
public final Builder ignoreExceptions(
@Nullable Class extends Throwable>... errorClasses) {
this.ignoreExceptions = errorClasses != null ? errorClasses : new Class[0];
return this;
}
public RetryConfig build() {
if (intervalFunction != null && intervalBiFunction != null) {
throw new IllegalStateException("The intervalFunction was configured twice which could result in an" +
" undesired state. Please use either intervalFunction or intervalBiFunction.");
}
RetryConfig config = new RetryConfig();
config.maxAttempts = maxAttempts;
config.failAfterMaxAttempts = failAfterMaxAttempts;
config.writableStackTraceEnabled = writableStackTraceEnabled;
config.retryOnExceptionPredicate = retryOnExceptionPredicate;
config.retryOnResultPredicate = retryOnResultPredicate;
config.consumeResultBeforeRetryAttempt = consumeResultBeforeRetryAttempt;
config.retryExceptions = retryExceptions;
config.ignoreExceptions = ignoreExceptions;
config.exceptionPredicate = createExceptionPredicate();
config.intervalFunction = createIntervalFunction();
config.intervalBiFunction = Optional.ofNullable(intervalBiFunction)
.orElse(IntervalBiFunction.ofIntervalFunction(config.intervalFunction));
return config;
}
@Nullable
private IntervalFunction createIntervalFunction() {
if (intervalFunction == null && intervalBiFunction == null) {
return IntervalFunction.ofDefaults();
}
return intervalFunction;
}
private Predicate createExceptionPredicate() {
return createRetryOnExceptionPredicate()
.and(PredicateCreator.createNegatedExceptionsPredicate(ignoreExceptions)
.orElse(DEFAULT_RECORD_FAILURE_PREDICATE));
}
private Predicate createRetryOnExceptionPredicate() {
return PredicateCreator.createExceptionsPredicate(retryExceptions)
.map(predicate -> retryOnExceptionPredicate != null ? predicate
.or(retryOnExceptionPredicate) : predicate)
.orElseGet(() -> retryOnExceptionPredicate != null ? retryOnExceptionPredicate
: DEFAULT_RECORD_FAILURE_PREDICATE);
}
}
}