From d72c8d5a4a29f8a6d9dbaaa07b6fd2e731ed6c7d Mon Sep 17 00:00:00 2001 From: raghavan chockalingam Date: Tue, 14 Aug 2018 22:02:46 +0100 Subject: [PATCH] Alternate RetryConfigBuilder to group relevant configs together for user of use AlternateRetryConfigBuilder is the alternate implementation Usage is described using AlternateRetryConfigBuilder contains few config groups Many subclasses are designed mutable for use like Builders Many subclasses could be extracted to become parent level classes in their own files --- .../config/AlternateRetryConfigBuilder.java | 165 ++++++++++++++++++ .../AlternateRetryConfigBuilderTest.java | 71 ++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/main/java/com/evanlennick/retry4j/config/AlternateRetryConfigBuilder.java create mode 100644 src/test/java/com/evanlennick/retry4j/AlternateRetryConfigBuilderTest.java 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(); + } + +}