diff --git a/README.md b/README.md index b32a2d2..23f3306 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ In the simplest case, a retry is just a while loop: the `RetryTemplate` can just Where the failure has caused a transactional resource to become invalid, there are some special considerations. This does not apply to a simple remote call because there is no transactional resource (usually), but it does sometimes apply to a database update, especially when using Hibernate. In this case it only makes sense to rethrow the exception that called the failure immediately so that the transaction can roll back and we can start a new valid one. -In these cases a stateless retry is not good enough because the re-throw and roll back necessarily involve leaving the `RetryOperations.execute()` method and potentially losing the context that was on the stack. To avoid losing it we have to introduce a storage strategy to lift it off the stack and put it (at a minimum) in heap storage. For this purpose Spring Retry provides a storage strategy `RetryContextCache` which can be injected into the `RetryTemplate`. The default implementation of the `RetryContextCache` is in memory, using a simple `Map. Advanced usage with multiple processes in a clustered environment might also consider implementing the `RetryContextCache` with a cluster cache of some sort (though, even in a clustered environment this might be overkill). +In these cases a stateless retry is not good enough because the re-throw and roll back necessarily involve leaving the `RetryOperations.execute()` method and potentially losing the context that was on the stack. To avoid losing it we have to introduce a storage strategy to lift it off the stack and put it (at a minimum) in heap storage. For this purpose Spring Retry provides a storage strategy `RetryContextCache` which can be injected into the `RetryTemplate`. The default implementation of the `RetryContextCache` is in memory, using a simple `Map`. It has a strictly enforced maximum capacity, to avoid memory leaks, but it doesn't have any advanced cache features like time to live. You should consider injecting a `Map` that had those features if you need them. Advanced usage with multiple processes in a clustered environment might also consider implementing the `RetryContextCache` with a cluster cache of some sort (though, even in a clustered environment this might be overkill). Part of the responsibility of the `RetryOperations` is to recognize the failed operations when they come back in a new execution (and usually wrapped in a new transaction). To facilitate this, Spring Retry provides the `RetryState` abstraction. This works in conjunction with a special execute methods in the `RetryOperations`. diff --git a/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java index e2cb4cc..39e45ba 100644 --- a/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java +++ b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java @@ -15,6 +15,7 @@ */ package org.springframework.classify; +import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -32,7 +33,8 @@ import java.util.Map; * @author Gary Russell * */ -public class BinaryExceptionClassifier extends SubclassClassifier { +@SuppressWarnings("serial") +public class BinaryExceptionClassifier extends SubclassClassifier implements Serializable { private boolean traverseCauses; diff --git a/src/main/java/org/springframework/classify/Classifier.java b/src/main/java/org/springframework/classify/Classifier.java index 5e58b8c..26f9214 100644 --- a/src/main/java/org/springframework/classify/Classifier.java +++ b/src/main/java/org/springframework/classify/Classifier.java @@ -17,8 +17,8 @@ package org.springframework.classify; /** - * Interface for a classifier. At its simplest a {@link Classifier} is just a - * map from objects of one type to objects of another type. + * Interface for a classifier. At its simplest a {@link Classifier} is just a map from + * objects of one type to objects of another type. * * @author Dave Syer * @@ -26,12 +26,12 @@ package org.springframework.classify; public interface Classifier { /** - * Classify the given object and return an object of a different type, - * possibly an enumerated type. + * Classify the given object and return an object of a different type, possibly an + * enumerated type. * * @param classifiable the input object. Can be null. - * @return an object. Can be null, but implementations should declare if - * this is the case. + * @return an object. Can be null, but implementations should declare if this is the + * case. */ T classify(C classifiable); diff --git a/src/main/java/org/springframework/classify/ClassifierSupport.java b/src/main/java/org/springframework/classify/ClassifierSupport.java index 8490229..7892b2c 100644 --- a/src/main/java/org/springframework/classify/ClassifierSupport.java +++ b/src/main/java/org/springframework/classify/ClassifierSupport.java @@ -16,6 +16,8 @@ package org.springframework.classify; +import java.io.Serializable; + /** * Base class for {@link Classifier} implementations. Provides default behaviour * and some convenience members, like constants. @@ -23,7 +25,8 @@ package org.springframework.classify; * @author Dave Syer * */ -public class ClassifierSupport implements Classifier { +@SuppressWarnings("serial") +public class ClassifierSupport implements Classifier, Serializable { final private T defaultValue; diff --git a/src/main/java/org/springframework/classify/SubclassClassifier.java b/src/main/java/org/springframework/classify/SubclassClassifier.java index d384470..6009e16 100644 --- a/src/main/java/org/springframework/classify/SubclassClassifier.java +++ b/src/main/java/org/springframework/classify/SubclassClassifier.java @@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentMap; * @author Gary Russell * */ +@SuppressWarnings("serial") public class SubclassClassifier implements Classifier { private ConcurrentMap, C> classified = new ConcurrentHashMap, C>(); @@ -144,7 +145,6 @@ public class SubclassClassifier implements Classifier { * @author Dave Syer * */ - @SuppressWarnings("serial") private static class ClassComparator implements Comparator>, Serializable { /** * @return 1 if arg0 is assignable from arg1, -1 otherwise diff --git a/src/main/java/org/springframework/retry/RetryPolicy.java b/src/main/java/org/springframework/retry/RetryPolicy.java index a2cf102..a0dc3dc 100644 --- a/src/main/java/org/springframework/retry/RetryPolicy.java +++ b/src/main/java/org/springframework/retry/RetryPolicy.java @@ -16,6 +16,8 @@ package org.springframework.retry; +import java.io.Serializable; + /** * A {@link RetryPolicy} is responsible for allocating and managing resources * needed by {@link RetryOperations}. The {@link RetryPolicy} allows retry @@ -27,7 +29,7 @@ package org.springframework.retry; * @author Dave Syer * */ -public interface RetryPolicy { +public interface RetryPolicy extends Serializable { /** * @param context the current retry status diff --git a/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java b/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java index 8871a60..29609d4 100644 --- a/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java @@ -26,6 +26,7 @@ import org.springframework.retry.RetryPolicy; * @author Dave Syer * */ +@SuppressWarnings("serial") public class AlwaysRetryPolicy extends NeverRetryPolicy { /** diff --git a/src/main/java/org/springframework/retry/policy/CircuitBreakerRetryPolicy.java b/src/main/java/org/springframework/retry/policy/CircuitBreakerRetryPolicy.java index 4c92d2e..405083e 100644 --- a/src/main/java/org/springframework/retry/policy/CircuitBreakerRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/CircuitBreakerRetryPolicy.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.retry.RetryContext; import org.springframework.retry.RetryPolicy; import org.springframework.retry.context.RetryContextSupport; @@ -28,6 +29,7 @@ import org.springframework.retry.context.RetryContextSupport; * @author Dave Syer * */ +@SuppressWarnings("serial") public class CircuitBreakerRetryPolicy implements RetryPolicy { public static final String CIRCUIT_OPEN = "circuit.open"; @@ -39,6 +41,10 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy { private final RetryPolicy delegate; private long resetTimeout = 20000; private long openTimeout = 5000; + + public CircuitBreakerRetryPolicy() { + this(new SimpleRetryPolicy()); + } public CircuitBreakerRetryPolicy(RetryPolicy delegate) { this.delegate = delegate; @@ -97,7 +103,6 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy { this.delegate.registerThrowable(circuit.context, throwable); } - @SuppressWarnings("serial") static class CircuitBreakerRetryContext extends RetryContextSupport { private volatile RetryContext context; private final RetryPolicy policy; diff --git a/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java b/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java index 94864af..271d51e 100644 --- a/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java @@ -32,6 +32,7 @@ import org.springframework.retry.context.RetryContextSupport; * @author Michael Minella * */ +@SuppressWarnings("serial") public class CompositeRetryPolicy implements RetryPolicy { RetryPolicy[] policies = new RetryPolicy[0]; @@ -129,7 +130,7 @@ public class CompositeRetryPolicy implements RetryPolicy { for (RetryPolicy policy : this.policies) { list.add(policy.open(parent)); } - return new CompositeRetryContext(parent, list); + return new CompositeRetryContext(parent, list, this.policies); } /** @@ -148,16 +149,15 @@ public class CompositeRetryPolicy implements RetryPolicy { ((RetryContextSupport) context).registerThrowable(throwable); } - @SuppressWarnings("serial") - private class CompositeRetryContext extends RetryContextSupport { + private static class CompositeRetryContext extends RetryContextSupport { RetryContext[] contexts; RetryPolicy[] policies; - public CompositeRetryContext(RetryContext parent, List contexts) { + public CompositeRetryContext(RetryContext parent, List contexts, RetryPolicy[] policies) { super(parent); this.contexts = contexts.toArray(new RetryContext[contexts.size()]); - this.policies = CompositeRetryPolicy.this.policies; + this.policies = policies; } } diff --git a/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java b/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java index d056f15..295525f 100644 --- a/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java @@ -34,6 +34,7 @@ import org.springframework.util.Assert; * @author Dave Syer * */ +@SuppressWarnings("serial") public class ExceptionClassifierRetryPolicy implements RetryPolicy { private Classifier exceptionClassifier = new ClassifierSupport(new NeverRetryPolicy()); @@ -102,7 +103,6 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy { ((RetryContextSupport) context).registerThrowable(throwable); } - @SuppressWarnings("serial") private static class ExceptionClassifierRetryContext extends RetryContextSupport implements RetryPolicy { final private Classifier exceptionClassifier; diff --git a/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java b/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java index b48ed9c..61e8944 100644 --- a/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java @@ -28,6 +28,7 @@ import org.springframework.retry.context.RetryContextSupport; * @author Dave Syer * */ +@SuppressWarnings("serial") public class NeverRetryPolicy implements RetryPolicy { /** @@ -80,7 +81,6 @@ public class NeverRetryPolicy implements RetryPolicy { * @author Dave Syer * */ - @SuppressWarnings("serial") private static class NeverRetryContext extends RetryContextSupport { private boolean finished = false; diff --git a/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java index f608917..3e391d6 100644 --- a/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java @@ -43,6 +43,7 @@ import org.springframework.util.ClassUtils; * @author Gary Russell * */ +@SuppressWarnings("serial") public class SimpleRetryPolicy implements RetryPolicy { /** @@ -182,7 +183,6 @@ public class SimpleRetryPolicy implements RetryPolicy { return new SimpleRetryContext(parent); } - @SuppressWarnings("serial") private static class SimpleRetryContext extends RetryContextSupport { public SimpleRetryContext(RetryContext parent) { super(parent); diff --git a/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java b/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java index 2470d1e..cdaeaa5 100644 --- a/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java +++ b/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java @@ -27,6 +27,7 @@ import org.springframework.retry.context.RetryContextSupport; * @author Dave Syer * */ +@SuppressWarnings("serial") public class TimeoutRetryPolicy implements RetryPolicy { /** @@ -75,7 +76,6 @@ public class TimeoutRetryPolicy implements RetryPolicy { // otherwise no-op - we only time out, otherwise retry everything... } - @SuppressWarnings("serial") private static class TimeoutRetryContext extends RetryContextSupport { private long timeout; diff --git a/src/test/java/org/springframework/classify/ClassifierAdapterTests.java b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java index 83c46f8..3fe1dc7 100644 --- a/src/test/java/org/springframework/classify/ClassifierAdapterTests.java +++ b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java @@ -15,11 +15,12 @@ */ package org.springframework.classify; -import static org.junit.Assert.assertEquals; - import org.junit.Test; + import org.springframework.classify.annotation.Classifier; +import static org.junit.Assert.assertEquals; + /** * @author Dave Syer * diff --git a/src/test/java/org/springframework/retry/policy/CompositeRetryPolicyTests.java b/src/test/java/org/springframework/retry/policy/CompositeRetryPolicyTests.java index c76c1e7..b546859 100644 --- a/src/test/java/org/springframework/retry/policy/CompositeRetryPolicyTests.java +++ b/src/test/java/org/springframework/retry/policy/CompositeRetryPolicyTests.java @@ -50,6 +50,7 @@ public class CompositeRetryPolicyTests { assertTrue(policy.canRetry(context)); } + @SuppressWarnings("serial") @Test public void testNonTrivialPolicies() throws Exception { CompositeRetryPolicy policy = new CompositeRetryPolicy(); @@ -63,6 +64,7 @@ public class CompositeRetryPolicyTests { assertFalse(policy.canRetry(context)); } + @SuppressWarnings("serial") @Test public void testNonTrivialPoliciesWithThrowable() throws Exception { CompositeRetryPolicy policy = new CompositeRetryPolicy(); @@ -84,6 +86,7 @@ public class CompositeRetryPolicyTests { assertFalse("Should be still able to retry", policy.canRetry(context)); } + @SuppressWarnings("serial") @Test public void testNonTrivialPoliciesClose() throws Exception { final List list = new ArrayList(); @@ -103,6 +106,7 @@ public class CompositeRetryPolicyTests { assertEquals(2, list.size()); } + @SuppressWarnings("serial") @Test public void testExceptionOnPoliciesClose() throws Exception { final List list = new ArrayList(); @@ -150,6 +154,7 @@ public class CompositeRetryPolicyTests { assertSame(context, child.getParent()); } + @SuppressWarnings("serial") @Test public void testOptimistic() throws Exception { CompositeRetryPolicy policy = new CompositeRetryPolicy(); diff --git a/src/test/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicyTests.java b/src/test/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicyTests.java index 8dbe63a..53451a1 100644 --- a/src/test/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicyTests.java +++ b/src/test/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicyTests.java @@ -103,6 +103,7 @@ public class ExceptionClassifierRetryPolicyTests { int count = 0; + @SuppressWarnings("serial") @Test public void testClose() throws Exception { policy.setExceptionClassifier(new Classifier() { diff --git a/src/test/java/org/springframework/retry/policy/MockRetryPolicySupport.java b/src/test/java/org/springframework/retry/policy/MockRetryPolicySupport.java index b0e3ca6..966af9e 100644 --- a/src/test/java/org/springframework/retry/policy/MockRetryPolicySupport.java +++ b/src/test/java/org/springframework/retry/policy/MockRetryPolicySupport.java @@ -16,6 +16,7 @@ package org.springframework.retry.policy; +@SuppressWarnings("serial") public class MockRetryPolicySupport extends AlwaysRetryPolicy { } diff --git a/src/test/java/org/springframework/retry/policy/RetryContextSerializationTests.java b/src/test/java/org/springframework/retry/policy/RetryContextSerializationTests.java new file mode 100644 index 0000000..da911a7 --- /dev/null +++ b/src/test/java/org/springframework/retry/policy/RetryContextSerializationTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * 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 org.springframework.retry.policy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryPolicy; +import org.springframework.util.ClassUtils; +import org.springframework.util.SerializationUtils; + +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + * + */ +@RunWith(Parameterized.class) +public class RetryContextSerializationTests { + + private static Log logger = LogFactory.getLog(RetryContextSerializationTests.class); + + private RetryPolicy policy; + + @Parameters + public static List policies() { + List result = new ArrayList(); + ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true); + scanner.addIncludeFilter(new AssignableTypeFilter(RetryPolicy.class)); + scanner.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*Test.*"))); + scanner.addExcludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*Mock.*"))); + Set candidates = scanner.findCandidateComponents("org.springframework.retry.policy"); + for (BeanDefinition beanDefinition : candidates) { + try { + result.add(new Object[] { BeanUtils.instantiate(ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), null)) }); + } catch (Exception e) { + logger.warn("Cannot create instance of " + beanDefinition.getBeanClassName()); + } + } + return result; + } + + public RetryContextSerializationTests(RetryPolicy policy) { + this.policy = policy; + } + + @Test + public void testSerializationCycleForContext() { + assertTrue(SerializationUtils.deserialize( + SerializationUtils.serialize(policy.open(null))) instanceof RetryContext); + } + + @Test + public void testSerializationCycleForPolicy() { + assertTrue(SerializationUtils.deserialize( + SerializationUtils.serialize(policy)) instanceof RetryPolicy); + } + +} diff --git a/src/test/java/org/springframework/retry/support/RetryTemplateTests.java b/src/test/java/org/springframework/retry/support/RetryTemplateTests.java index 9dc94f9..a687076 100644 --- a/src/test/java/org/springframework/retry/support/RetryTemplateTests.java +++ b/src/test/java/org/springframework/retry/support/RetryTemplateTests.java @@ -294,6 +294,7 @@ public class RetryTemplateTests { } } + @SuppressWarnings("serial") @Test public void testFailedPolicy() throws Throwable { RetryTemplate retryTemplate = new RetryTemplate();