diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java index 1e82bf1c66f..50938cd0be7 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicy.java @@ -21,10 +21,10 @@ import org.springframework.core.retry.RetryPolicy; import org.springframework.util.Assert; /** - * A {@link RetryPolicy} based on a number of attempts that should not exceed a - * configured maximum number. + * A {@link RetryPolicy} based on a maximum number of retry attempts. * * @author Mahmoud Ben Hassine + * @author Sam Brannen * @since 7.0 */ public class MaxRetryAttemptsPolicy implements RetryPolicy { @@ -56,11 +56,6 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy { setMaxRetryAttempts(maxRetryAttempts); } - @Override - public RetryExecution start() { - return new MaxRetryAttemptsPolicyExecution(); - } - /** * Set the maximum number of retry attempts. * @param maxRetryAttempts the maximum number of retry attempts; must be greater @@ -71,6 +66,16 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy { this.maxRetryAttempts = maxRetryAttempts; } + @Override + public RetryExecution start() { + return new MaxRetryAttemptsPolicyExecution(); + } + + @Override + public String toString() { + return "MaxRetryAttemptsPolicy[maxRetryAttempts=%d]".formatted(this.maxRetryAttempts); + } + /** * A {@link RetryExecution} based on a maximum number of retry attempts. @@ -83,6 +88,13 @@ public class MaxRetryAttemptsPolicy implements RetryPolicy { public boolean shouldRetry(Throwable throwable) { return (this.retryAttempts++ < MaxRetryAttemptsPolicy.this.maxRetryAttempts); } + + @Override + public String toString() { + return "MaxRetryAttemptsPolicyExecution[retryAttempts=%d, maxRetryAttempts=%d]" + .formatted(this.retryAttempts, MaxRetryAttemptsPolicy.this.maxRetryAttempts); + } + } } diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java index bbd32d4bb5d..ea4b377c1d4 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/MaxRetryDurationPolicy.java @@ -24,9 +24,10 @@ import org.springframework.core.retry.RetryPolicy; import org.springframework.util.Assert; /** - * A {@link RetryPolicy} based on a maximum retry duration. + * A {@link RetryPolicy} based on a maximum retry {@link Duration}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen * @since 7.0 */ public class MaxRetryDurationPolicy implements RetryPolicy { @@ -42,7 +43,7 @@ public class MaxRetryDurationPolicy implements RetryPolicy { /** * Create a new {@code MaxRetryDurationPolicy} with the default maximum retry - * duration. + * {@link Duration}. * @see #DEFAULT_MAX_RETRY_DURATION */ public MaxRetryDurationPolicy() { @@ -50,20 +51,15 @@ public class MaxRetryDurationPolicy implements RetryPolicy { /** * Create a new {@code MaxRetryDurationPolicy} with the specified maximum retry - * duration. + * {@link Duration}. * @param maxRetryDuration the maximum retry duration; must be positive */ public MaxRetryDurationPolicy(Duration maxRetryDuration) { setMaxRetryDuration(maxRetryDuration); } - @Override - public RetryExecution start() { - return new MaxRetryDurationPolicyExecution(); - } - /** - * Set the maximum retry duration. + * Set the maximum retry {@link Duration}. * @param maxRetryDuration the maximum retry duration; must be positive */ public void setMaxRetryDuration(Duration maxRetryDuration) { @@ -72,6 +68,17 @@ public class MaxRetryDurationPolicy implements RetryPolicy { this.maxRetryDuration = maxRetryDuration; } + @Override + public RetryExecution start() { + return new MaxRetryDurationPolicyExecution(); + } + + @Override + public String toString() { + return "MaxRetryDurationPolicy[maxRetryDuration=%dms]".formatted(this.maxRetryDuration.toMillis()); + } + + /** * A {@link RetryExecution} based on a maximum retry duration. */ @@ -84,6 +91,13 @@ public class MaxRetryDurationPolicy implements RetryPolicy { Duration currentRetryDuration = Duration.between(this.retryStartTime, LocalDateTime.now()); return currentRetryDuration.compareTo(MaxRetryDurationPolicy.this.maxRetryDuration) <= 0; } + + @Override + public String toString() { + return "MaxRetryDurationPolicyExecution[retryStartTime=%s, maxRetryDuration=%dms]" + .formatted(this.retryStartTime, MaxRetryDurationPolicy.this.maxRetryDuration.toMillis()); + } + } } diff --git a/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java index afe186a5e9e..4e294f12e18 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/support/PredicateRetryPolicy.java @@ -25,6 +25,7 @@ import org.springframework.core.retry.RetryPolicy; * A {@link RetryPolicy} based on a {@link Predicate}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen * @since 7.0 */ public class PredicateRetryPolicy implements RetryPolicy { @@ -44,7 +45,31 @@ public class PredicateRetryPolicy implements RetryPolicy { @Override public RetryExecution start() { - return this.predicate::test; + return new PredicateRetryPolicyExecution(); + } + + @Override + public String toString() { + return "PredicateRetryPolicy[predicate=%s]".formatted(this.predicate.getClass().getSimpleName()); + } + + + /** + * A {@link RetryExecution} based on a {@link Predicate}. + */ + private class PredicateRetryPolicyExecution implements RetryExecution { + + @Override + public boolean shouldRetry(Throwable throwable) { + return PredicateRetryPolicy.this.predicate.test(throwable); + } + + @Override + public String toString() { + return "PredicateRetryPolicyExecution[predicate=%s]" + .formatted(PredicateRetryPolicy.this.predicate.getClass().getSimpleName()); + } + } } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java index 2912f5b9a12..db1ce6ed164 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryAttemptsPolicyTests.java @@ -25,9 +25,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import static org.mockito.Mockito.mock; /** - * Tests for {@link MaxRetryAttemptsPolicy}. + * Tests for {@link MaxRetryAttemptsPolicy} and its {@link RetryExecution}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen + * @since 7.0 */ class MaxRetryAttemptsPolicyTests { @@ -44,14 +46,69 @@ class MaxRetryAttemptsPolicyTests { assertThat(retryExecution.shouldRetry(throwable)).isTrue(); assertThat(retryExecution.shouldRetry(throwable)).isTrue(); assertThat(retryExecution.shouldRetry(throwable)).isTrue(); + + assertThat(retryExecution.shouldRetry(throwable)).isFalse(); + assertThat(retryExecution.shouldRetry(throwable)).isFalse(); + } + + @Test + void customMaxRetryAttempts() { + // given + MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy(2); + Throwable throwable = mock(); + + // when + RetryExecution retryExecution = retryPolicy.start(); + + // then + assertThat(retryExecution.shouldRetry(throwable)).isTrue(); + assertThat(retryExecution.shouldRetry(throwable)).isTrue(); + + assertThat(retryExecution.shouldRetry(throwable)).isFalse(); assertThat(retryExecution.shouldRetry(throwable)).isFalse(); } @Test void invalidMaxRetryAttempts() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new MaxRetryAttemptsPolicy(0)) + .withMessage("Max retry attempts must be greater than zero"); assertThatIllegalArgumentException() .isThrownBy(() -> new MaxRetryAttemptsPolicy(-1)) .withMessage("Max retry attempts must be greater than zero"); } + @Test + void toStringImplementations() { + MaxRetryAttemptsPolicy policy1 = new MaxRetryAttemptsPolicy(); + MaxRetryAttemptsPolicy policy2 = new MaxRetryAttemptsPolicy(1); + + assertThat(policy1).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=3]"); + assertThat(policy2).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=1]"); + + RetryExecution retryExecution = policy1.start(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=0, maxRetryAttempts=3]"); + + assertThat(retryExecution.shouldRetry(mock())).isTrue(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=1, maxRetryAttempts=3]"); + + assertThat(retryExecution.shouldRetry(mock())).isTrue(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=2, maxRetryAttempts=3]"); + + assertThat(retryExecution.shouldRetry(mock())).isTrue(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=3, maxRetryAttempts=3]"); + + assertThat(retryExecution.shouldRetry(mock())).isFalse(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=4, maxRetryAttempts=3]"); + + assertThat(retryExecution.shouldRetry(mock())).isFalse(); + assertThat(retryExecution).asString() + .isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=5, maxRetryAttempts=3]"); + } + } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java index 3f8a82896e5..a46a4f52c80 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/MaxRetryDurationPolicyTests.java @@ -20,12 +20,17 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.springframework.core.retry.RetryExecution; + +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** - * Tests for {@link MaxRetryDurationPolicy}. + * Tests for {@link MaxRetryDurationPolicy} and its {@link RetryExecution}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen + * @since 7.0 */ class MaxRetryDurationPolicyTests { @@ -36,4 +41,18 @@ class MaxRetryDurationPolicyTests { .withMessage("Max retry duration must be positive"); } + @Test + void toStringImplementations() { + MaxRetryDurationPolicy policy1 = new MaxRetryDurationPolicy(); + MaxRetryDurationPolicy policy2 = new MaxRetryDurationPolicy(Duration.ofSeconds(1)); + + assertThat(policy1).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=3000ms]"); + assertThat(policy2).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=1000ms]"); + + assertThat(policy1.start()).asString() + .matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=3000ms\\]"); + assertThat(policy2.start()).asString() + .matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=1000ms\\]"); + } + } diff --git a/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java index 2ddac2f8588..e9b16e95c09 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/support/PredicateRetryPolicyTests.java @@ -25,28 +25,46 @@ import org.springframework.core.retry.RetryExecution; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link PredicateRetryPolicy}. + * Tests for {@link PredicateRetryPolicy} and its {@link RetryExecution}. * * @author Mahmoud Ben Hassine + * @author Sam Brannen + * @since 7.0 */ class PredicateRetryPolicyTests { @Test void predicateRetryPolicy() { - // given - class MyException extends Exception { - @java.io.Serial - private static final long serialVersionUID = 1L; - } - Predicate predicate = MyException.class::isInstance; + Predicate predicate = NumberFormatException.class::isInstance; PredicateRetryPolicy retryPolicy = new PredicateRetryPolicy(predicate); - // when RetryExecution retryExecution = retryPolicy.start(); - // then - assertThat(retryExecution.shouldRetry(new MyException())).isTrue(); + assertThat(retryExecution.shouldRetry(new NumberFormatException())).isTrue(); assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse(); } + @Test + void toStringImplementations() { + PredicateRetryPolicy policy1 = new PredicateRetryPolicy(NumberFormatException.class::isInstance); + PredicateRetryPolicy policy2 = new PredicateRetryPolicy(new NumberFormatExceptionMatcher()); + + assertThat(policy1).asString().matches("PredicateRetryPolicy\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]"); + assertThat(policy2).asString().isEqualTo("PredicateRetryPolicy[predicate=NumberFormatExceptionMatcher]"); + + assertThat(policy1.start()).asString() + .matches("PredicateRetryPolicyExecution\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]"); + assertThat(policy2.start()).asString() + .isEqualTo("PredicateRetryPolicyExecution[predicate=NumberFormatExceptionMatcher]"); + } + + + private static class NumberFormatExceptionMatcher implements Predicate { + + @Override + public boolean test(Throwable throwable) { + return (throwable instanceof NumberFormatException); + } + } + }