diff --git a/src/main/java/com/evanlennick/retry4j/config/AlternateRetryConfigBuilder.java b/src/main/java/com/evanlennick/retry4j/config/AlternateRetryConfigBuilder.java new file mode 100644 index 0000000..d1ee12e --- /dev/null +++ b/src/main/java/com/evanlennick/retry4j/config/AlternateRetryConfigBuilder.java @@ -0,0 +1,165 @@ +package com.evanlennick.retry4j.config; + +import com.evanlennick.retry4j.backoff.*; +import com.evanlennick.retry4j.exception.InvalidRetryConfigException; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +public class AlternateRetryConfigBuilder { + + private Object valueToRetryOn; + private Boolean retryOnValue = false; + // Using some defaults here, but we need not declare defaults and validate for nulls during `build()` + private BackoffStrategy backoffStrategy = BackoffStrategyRegistry.fixedBackoff; + private ExceptionRetryConfig exceptionRetryConfig = new ExceptionRetryConfig() + .retryOnAnyException(); + private TimingRetryConfig timingRetryConfig = TimingRetryConfig.retryIndefinitely() + .withDelayBetweenTries(2, ChronoUnit.SECONDS); + + private AlternateRetryConfigBuilder() { + } + + public static AlternateRetryConfigBuilder exceptionRetryConfig(ExceptionRetryConfig exceptionRetryConfig) { + AlternateRetryConfigBuilder builder = new AlternateRetryConfigBuilder(); + builder.exceptionRetryConfig = exceptionRetryConfig; + return builder; + } + + public static AlternateRetryConfigBuilder retryOnReturnValue(Object value) { + AlternateRetryConfigBuilder builder = new AlternateRetryConfigBuilder(); + builder.valueToRetryOn = value; + builder.retryOnValue = true; + return builder; + } + + public static class ExceptionRetryConfig { + private Boolean retryOnAnyException = false; + private Function customRetryOnLogic; + private ExceptionsCriteria exceptionsCriteriaBuilder = new ExceptionsCriteria(); + + public static ExceptionRetryConfig retryOnAnyException() { + ExceptionRetryConfig config = new ExceptionRetryConfig(); + config.retryOnAnyException = true; + return config; + } + + public static ExceptionRetryConfig failOnAnyException() { + ExceptionRetryConfig config = new ExceptionRetryConfig(); + config.retryOnAnyException = false; + return config; + } + + public static ExceptionRetryConfig retryOnExceptions(ExceptionsCriteria exceptionsCriteriaBuilder) { + ExceptionRetryConfig config = new ExceptionRetryConfig(); + config.exceptionsCriteriaBuilder = exceptionsCriteriaBuilder; + return config; + } + + public static ExceptionRetryConfig retryOnCustomExceptionLogic(Function customRetryFunction) { + ExceptionRetryConfig config = new ExceptionRetryConfig(); + config.customRetryOnLogic = customRetryFunction; + return config; + } + } + + public static class ExceptionsCriteria { + private Set> retryOnSpecificExceptions = new HashSet<>(); + private Set> retryOnAnyExceptionExcluding = new HashSet<>(); + private boolean retryOnCausedBy; + + public ExceptionsCriteria retryOnSpecificExceptions(Class... exceptions) { + this.retryOnSpecificExceptions = new HashSet<>(Arrays.asList(exceptions)); + return this; + } + + public ExceptionsCriteria retryOnAnyExceptionExcluding(Class... exceptions) { + this.retryOnAnyExceptionExcluding = new HashSet<>(Arrays.asList(exceptions)); + return this; + } + + public ExceptionsCriteria retryOnCausedBy() { + this.retryOnCausedBy = true; + return this; + } + } + + public static class TimingRetryConfig { + + private Integer maxNumberOfTries; + private Duration delayBetweenRetries; + public static final String SHOULD_SPECIFY_DELAY_BETWEEN_RETRIES_AS_POSTIVE__ERROR_MSG + = "Delay between retries must be a non-negative Duration."; + + private TimingRetryConfig() { + } + + public static TimingRetryConfig withMaxTries(int maxTries) { + TimingRetryConfig config = new TimingRetryConfig(); + config.maxNumberOfTries = maxTries; + return config; + } + + public static TimingRetryConfig retryIndefinitely() { + TimingRetryConfig config = new TimingRetryConfig(); + config.maxNumberOfTries = Integer.MAX_VALUE; + return config; + } + + + public TimingRetryConfig withDelayBetweenTries(Duration duration) { + if (duration.isNegative()) { + throw new InvalidRetryConfigException(SHOULD_SPECIFY_DELAY_BETWEEN_RETRIES_AS_POSTIVE__ERROR_MSG); + } + + delayBetweenRetries = duration; + return this; + } + + public TimingRetryConfig withDelayBetweenTries(long amount, ChronoUnit time) { + delayBetweenRetries = Duration.of(amount, time); + return this; + } + + } + + public static class BackoffStrategyRegistry { + public static final BackoffStrategy fixedBackoff = new FixedBackoffStrategy(); + public static final BackoffStrategy exponentialBackoff = new ExponentialBackoffStrategy(); + public static final BackoffStrategy fibonacciBackoff = new FibonacciBackoffStrategy(); + public static final BackoffStrategy noWaitBackoff= new NoWaitBackoffStrategy(); + public static final BackoffStrategy randomBackoff= new RandomBackoffStrategy(); + public static final BackoffStrategy randomExponentialBackoff = new RandomExponentialBackoffStrategy(); + } + + public AlternateRetryConfigBuilder withBackoffStrategy(BackoffStrategy backoffStrategy) { + this.backoffStrategy = backoffStrategy; + return this; + } + + public AlternateRetryConfigBuilder withExceptionRetryconfig(ExceptionRetryConfig exceptionRetryConfig) { + this.exceptionRetryConfig = exceptionRetryConfig; + return this; + } + + public AlternateRetryConfigBuilder withTimingRetryConfig(TimingRetryConfig timingRetryConfig) { + this.timingRetryConfig = timingRetryConfig; + return this; + } + + public RetryConfig build() { + RetryConfig retryConfig = new RetryConfig( + exceptionRetryConfig.retryOnAnyException, exceptionRetryConfig.exceptionsCriteriaBuilder.retryOnSpecificExceptions, + exceptionRetryConfig.exceptionsCriteriaBuilder.retryOnAnyExceptionExcluding, timingRetryConfig.maxNumberOfTries, + timingRetryConfig.delayBetweenRetries, backoffStrategy, valueToRetryOn, retryOnValue, + exceptionRetryConfig.customRetryOnLogic, exceptionRetryConfig.exceptionsCriteriaBuilder.retryOnCausedBy + ); + return retryConfig; + } + +} + diff --git a/src/test/java/com/evanlennick/retry4j/AlternateRetryConfigBuilderTest.java b/src/test/java/com/evanlennick/retry4j/AlternateRetryConfigBuilderTest.java new file mode 100644 index 0000000..3eb4148 --- /dev/null +++ b/src/test/java/com/evanlennick/retry4j/AlternateRetryConfigBuilderTest.java @@ -0,0 +1,71 @@ +package com.evanlennick.retry4j; + + +import com.evanlennick.retry4j.config.AlternateRetryConfigBuilder; +import com.evanlennick.retry4j.config.AlternateRetryConfigBuilder.BackoffStrategyRegistry; +import com.evanlennick.retry4j.config.AlternateRetryConfigBuilder.ExceptionRetryConfig; +import com.evanlennick.retry4j.config.AlternateRetryConfigBuilder.ExceptionsCriteria; +import com.evanlennick.retry4j.config.AlternateRetryConfigBuilder.TimingRetryConfig; +import org.testng.annotations.Test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.time.temporal.ChronoUnit; + +public class AlternateRetryConfigBuilderTest { + + @Test + public void testConfig1() { + AlternateRetryConfigBuilder + .exceptionRetryConfig( + new ExceptionRetryConfig().retryOnAnyException() + ) + .withTimingRetryConfig( + TimingRetryConfig.withMaxTries(200) + ) + .withBackoffStrategy(BackoffStrategyRegistry.noWaitBackoff) + .build(); + } + + @Test + public void testConfig2() { + AlternateRetryConfigBuilder + .exceptionRetryConfig( + new ExceptionRetryConfig().retryOnExceptions(new ExceptionsCriteria() + .retryOnSpecificExceptions(IllegalArgumentException.class)) + ) + .withTimingRetryConfig( + TimingRetryConfig.retryIndefinitely().withDelayBetweenTries(3, ChronoUnit.MINUTES) + ) + .withBackoffStrategy(BackoffStrategyRegistry.exponentialBackoff) + .build(); + } + + @Test + public void testConfig3() { + AlternateRetryConfigBuilder + .exceptionRetryConfig( + new ExceptionRetryConfig().retryOnExceptions(new ExceptionsCriteria() + .retryOnSpecificExceptions(IOException.class) + .retryOnAnyExceptionExcluding(FileNotFoundException.class) + .retryOnCausedBy()) + ) + .withTimingRetryConfig( + TimingRetryConfig.retryIndefinitely().withDelayBetweenTries(3, ChronoUnit.MINUTES) + ) + .withBackoffStrategy(BackoffStrategyRegistry.exponentialBackoff) + .build(); + } + + @Test + public void testConfig4() { + AlternateRetryConfigBuilder + .retryOnReturnValue("retry on this value!") + .withTimingRetryConfig( + TimingRetryConfig.retryIndefinitely() + ) + .withBackoffStrategy(BackoffStrategyRegistry.fibonacciBackoff) + .build(); + } + +}