Apply format plugin
This commit is contained in:
17
pom.xml
17
pom.xml
@@ -23,6 +23,7 @@
|
||||
<properties>
|
||||
<maven.test.failure.ignore>true</maven.test.failure.ignore>
|
||||
<spring.framework.version>4.3.22.RELEASE</spring.framework.version>
|
||||
<disable.checks>false</disable.checks>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
@@ -348,6 +349,22 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.spring.javaformat</groupId>
|
||||
<artifactId>spring-javaformat-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<skip>${disable.checks}</skip>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>apply</goal>
|
||||
<goal>validate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -18,11 +18,11 @@ package org.springframework.classify;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A special purpose {@link Classifier} with easy configuration options for
|
||||
* mapping from one arbitrary type of object to another via a pattern matcher.
|
||||
*
|
||||
* A special purpose {@link Classifier} with easy configuration options for mapping from
|
||||
* one arbitrary type of object to another via a pattern matcher.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BackToBackPatternClassifier<C, T> implements Classifier<C, T> {
|
||||
@@ -32,28 +32,26 @@ public class BackToBackPatternClassifier<C, T> implements Classifier<C, T> {
|
||||
private Classifier<String, T> matcher;
|
||||
|
||||
/**
|
||||
* Default constructor, provided as a convenience for people using setter
|
||||
* injection.
|
||||
* Default constructor, provided as a convenience for people using setter injection.
|
||||
*/
|
||||
public BackToBackPatternClassifier() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a classifier with input to the router and output from the matcher.
|
||||
*
|
||||
* @param router see {@link #setRouterDelegate(Object)}
|
||||
* @param matcher see {@link #setMatcherMap(Map)}
|
||||
*/
|
||||
public BackToBackPatternClassifier(Classifier<C, String> router, Classifier<String, T> matcher) {
|
||||
public BackToBackPatternClassifier(Classifier<C, String> router,
|
||||
Classifier<String, T> matcher) {
|
||||
super();
|
||||
this.router = router;
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for creating a pattern matching classifier for the
|
||||
* matcher component.
|
||||
*
|
||||
* A convenience method for creating a pattern matching classifier for the matcher
|
||||
* component.
|
||||
* @param map maps pattern keys with wildcards to output values
|
||||
*/
|
||||
public void setMatcherMap(Map<String, T> map) {
|
||||
@@ -61,21 +59,19 @@ public class BackToBackPatternClassifier<C, T> implements Classifier<C, T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method of creating a router classifier based on a plain old
|
||||
* Java Object. The object provided must have precisely one public method
|
||||
* that either has the <code>@Classifier</code> annotation or accepts a single argument
|
||||
* and outputs a String. This will be used to create an input classifier for
|
||||
* the router component.
|
||||
*
|
||||
* A convenience method of creating a router classifier based on a plain old Java
|
||||
* Object. The object provided must have precisely one public method that either has
|
||||
* the <code>@Classifier</code> annotation or accepts a single argument and outputs a
|
||||
* String. This will be used to create an input classifier for the router component.
|
||||
* @param delegate the delegate object used to create a router classifier
|
||||
*/
|
||||
public void setRouterDelegate(Object delegate) {
|
||||
this.router = new ClassifierAdapter<C,String>(delegate);
|
||||
this.router = new ClassifierAdapter<C, String>(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the input and map to a String, then take that and put it into a
|
||||
* pattern matcher to match to an output value.
|
||||
* Classify the input and map to a String, then take that and put it into a pattern
|
||||
* matcher to match to an output value.
|
||||
*/
|
||||
public T classify(C classifiable) {
|
||||
return matcher.classify(router.classify(classifiable));
|
||||
|
||||
@@ -21,14 +21,12 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link Classifier} for exceptions that has only two classes (true and
|
||||
* false). Classifies objects according to their inheritance relation with the
|
||||
* supplied types. If the object to be classified is one of the provided types,
|
||||
* or is a subclass of one of the types, then the non-default value is returned
|
||||
* (usually true).
|
||||
* A {@link Classifier} for exceptions that has only two classes (true and false).
|
||||
* Classifies objects according to their inheritance relation with the supplied types. If
|
||||
* the object to be classified is one of the provided types, or is a subclass of one of
|
||||
* the types, then the non-default value is returned (usually true).
|
||||
*
|
||||
* @see SubclassClassifier
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Gary Russell
|
||||
*
|
||||
@@ -44,14 +42,13 @@ public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boo
|
||||
|
||||
public static BinaryExceptionClassifier newDefaultClassifier() {
|
||||
// create new instance for each call due to mutability
|
||||
return new BinaryExceptionClassifier(
|
||||
Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true), false
|
||||
);
|
||||
return new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>, Boolean>singletonMap(Exception.class, true),
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the provided default value.
|
||||
*
|
||||
* @param defaultValue defaults to false
|
||||
*/
|
||||
public BinaryExceptionClassifier(boolean defaultValue) {
|
||||
@@ -60,13 +57,13 @@ public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boo
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the provided classes and their
|
||||
* subclasses. The mapped value for these exceptions will be the one
|
||||
* provided (which will be the opposite of the default).
|
||||
*
|
||||
* subclasses. The mapped value for these exceptions will be the one provided (which
|
||||
* will be the opposite of the default).
|
||||
* @param exceptionClasses the exceptions to classify among
|
||||
* @param value the value to classify
|
||||
*/
|
||||
public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses, boolean value) {
|
||||
public BinaryExceptionClassifier(
|
||||
Collection<Class<? extends Throwable>> exceptionClasses, boolean value) {
|
||||
this(!value);
|
||||
if (exceptionClasses != null) {
|
||||
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>();
|
||||
@@ -78,18 +75,18 @@ public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boo
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the default value false and
|
||||
* value mapping true for the provided classes and their subclasses.
|
||||
*
|
||||
* Create a binary exception classifier with the default value false and value mapping
|
||||
* true for the provided classes and their subclasses.
|
||||
* @param exceptionClasses the exception types to throw
|
||||
*/
|
||||
public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses) {
|
||||
public BinaryExceptionClassifier(
|
||||
Collection<Class<? extends Throwable>> exceptionClasses) {
|
||||
this(exceptionClasses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier using the given classification map
|
||||
* and a default classification of false.
|
||||
* Create a binary exception classifier using the given classification map and a
|
||||
* default classification of false.
|
||||
* @param typeMap the map of types
|
||||
*/
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap) {
|
||||
@@ -97,25 +94,25 @@ public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boo
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier using the given classification map
|
||||
* and the given value for default class.
|
||||
*
|
||||
* Create a binary exception classifier using the given classification map and the
|
||||
* given value for default class.
|
||||
* @param defaultValue the default value to use
|
||||
* @param typeMap the map of types to classify
|
||||
*/
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap, boolean defaultValue) {
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap,
|
||||
boolean defaultValue) {
|
||||
super(typeMap, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier.
|
||||
*
|
||||
* @param defaultValue the default value to use
|
||||
* @param typeMap the map of types to classify
|
||||
* @param traverseCauses if true, throwable's causes will be inspected to find non-default class
|
||||
* @param traverseCauses if true, throwable's causes will be inspected to find
|
||||
* non-default class
|
||||
*/
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap, boolean defaultValue,
|
||||
boolean traverseCauses) {
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap,
|
||||
boolean defaultValue, boolean traverseCauses) {
|
||||
super(typeMap, defaultValue);
|
||||
this.traverseCauses = traverseCauses;
|
||||
}
|
||||
@@ -132,8 +129,8 @@ public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boo
|
||||
}
|
||||
|
||||
/*
|
||||
* If the result is the default, we need to find out if it was by default
|
||||
* or so configured; if default, try the cause(es).
|
||||
* If the result is the default, we need to find out if it was by default or so
|
||||
* configured; if default, try the cause(es).
|
||||
*/
|
||||
if (classified.equals(this.getDefault())) {
|
||||
Throwable cause = classifiable;
|
||||
|
||||
@@ -24,15 +24,12 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* Fluent API for BinaryExceptionClassifier configuration.
|
||||
* <p>
|
||||
* Can be used in while list style:
|
||||
* <pre>{@code
|
||||
* Can be used in while list style: <pre>{@code
|
||||
* BinaryExceptionClassifier.newBuilder()
|
||||
* .retryOn(IOException.class)
|
||||
* .retryOn(IllegalArgumentException.class)
|
||||
* .build();
|
||||
* } </pre>
|
||||
* or in black list style:
|
||||
* <pre>{@code
|
||||
* } </pre> or in black list style: <pre>{@code
|
||||
* BinaryExceptionClassifier.newBuilder()
|
||||
* .notRetryOn(Error.class)
|
||||
* .build();
|
||||
@@ -40,18 +37,16 @@ import org.springframework.util.Assert;
|
||||
* <p>
|
||||
* Provides traverseCauses=false by default, and no default rules for exceptions.
|
||||
* <p>
|
||||
* Not thread safe. Building should be performed in a single thread, publishing of
|
||||
* newly created instance should be safe.
|
||||
* Not thread safe. Building should be performed in a single thread, publishing of newly
|
||||
* created instance should be safe.
|
||||
*
|
||||
* @author Aleksandr Shamukov
|
||||
*/
|
||||
public class BinaryExceptionClassifierBuilder {
|
||||
|
||||
/**
|
||||
* Building notation type (white list or black list)
|
||||
* - null: has not selected yet
|
||||
* - true: white list
|
||||
* - false: black list
|
||||
* Building notation type (white list or black list) - null: has not selected yet -
|
||||
* true: white list - false: black list
|
||||
*/
|
||||
private Boolean isWhiteList = null;
|
||||
|
||||
@@ -59,7 +54,8 @@ public class BinaryExceptionClassifierBuilder {
|
||||
|
||||
private List<Class<? extends Throwable>> exceptionClasses = new ArrayList<Class<? extends Throwable>>();
|
||||
|
||||
public BinaryExceptionClassifierBuilder retryOn(Class<? extends Throwable> throwable) {
|
||||
public BinaryExceptionClassifierBuilder retryOn(
|
||||
Class<? extends Throwable> throwable) {
|
||||
Assert.isTrue(isWhiteList == null || isWhiteList,
|
||||
"Please use only retryOn() or only notRetryOn()");
|
||||
Assert.notNull(throwable, "Exception class can not be null");
|
||||
@@ -69,7 +65,8 @@ public class BinaryExceptionClassifierBuilder {
|
||||
|
||||
}
|
||||
|
||||
public BinaryExceptionClassifierBuilder notRetryOn(Class<? extends Throwable> throwable) {
|
||||
public BinaryExceptionClassifierBuilder notRetryOn(
|
||||
Class<? extends Throwable> throwable) {
|
||||
Assert.isTrue(isWhiteList == null || !isWhiteList,
|
||||
"Please use only retryOn() or only notRetryOn()");
|
||||
Assert.notNull(throwable, "Exception class can not be null");
|
||||
@@ -88,10 +85,12 @@ public class BinaryExceptionClassifierBuilder {
|
||||
"Attempt to build classifier with empty rules. To build always true, or always false "
|
||||
+ "instance, please use explicit rule for Throwable");
|
||||
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(
|
||||
exceptionClasses,
|
||||
isWhiteList // using white list means classifying provided classes as "true" (is retryable)
|
||||
exceptionClasses, isWhiteList // using white list means classifying
|
||||
// provided classes as "true" (is
|
||||
// retryable)
|
||||
);
|
||||
classifier.setTraverseCauses(traverseCauses);
|
||||
return classifier;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,14 +26,13 @@ import java.io.Serializable;
|
||||
* themselves serializable.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface Classifier<C, T> extends Serializable {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -21,9 +21,9 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Wrapper for an object to adapt it to the {@link Classifier} interface.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ClassifierAdapter<C, T> implements Classifier<C, T> {
|
||||
@@ -40,9 +40,8 @@ public class ClassifierAdapter<C, T> implements Classifier<C, T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Classifier} from the delegate provided. Use the
|
||||
* constructor as an alternative to the {@link #setDelegate(Object)} method.
|
||||
*
|
||||
* Create a new {@link Classifier} from the delegate provided. Use the constructor as
|
||||
* an alternative to the {@link #setDelegate(Object)} method.
|
||||
* @param delegate the delegate
|
||||
*/
|
||||
public ClassifierAdapter(Object delegate) {
|
||||
@@ -50,10 +49,8 @@ public class ClassifierAdapter<C, T> implements Classifier<C, T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Classifier} from the delegate provided. Use the
|
||||
* constructor as an alternative to the {@link #setDelegate(Classifier)}
|
||||
* method.
|
||||
*
|
||||
* Create a new {@link Classifier} from the delegate provided. Use the constructor as
|
||||
* an alternative to the {@link #setDelegate(Classifier)} method.
|
||||
* @param delegate the classifier to delegate to
|
||||
*/
|
||||
public ClassifierAdapter(Classifier<C, T> delegate) {
|
||||
@@ -66,15 +63,13 @@ public class ClassifierAdapter<C, T> implements Classifier<C, T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the
|
||||
* {@link org.springframework.classify.annotation.Classifier
|
||||
* Classifier} annotation on a method in the supplied delegate and use that
|
||||
* to create a {@link Classifier} from the parameter type to the return
|
||||
* type. If the annotation is not found a unique non-void method with a
|
||||
* single parameter will be used, if it exists. The signature of the method
|
||||
* cannot be checked here, so might be a runtime exception when the method
|
||||
* is invoked if the signature doesn't match the classifier types.
|
||||
*
|
||||
* Search for the {@link org.springframework.classify.annotation.Classifier
|
||||
* Classifier} annotation on a method in the supplied delegate and use that to create
|
||||
* a {@link Classifier} from the parameter type to the return type. If the annotation
|
||||
* is not found a unique non-void method with a single parameter will be used, if it
|
||||
* exists. The signature of the method cannot be checked here, so might be a runtime
|
||||
* exception when the method is invoked if the signature doesn't match the classifier
|
||||
* types.
|
||||
* @param delegate an object with an annotated method
|
||||
*/
|
||||
public final void setDelegate(Object delegate) {
|
||||
@@ -82,7 +77,8 @@ public class ClassifierAdapter<C, T> implements Classifier<C, T> {
|
||||
invoker = MethodInvokerUtils.getMethodInvokerByAnnotation(
|
||||
org.springframework.classify.annotation.Classifier.class, delegate);
|
||||
if (invoker == null) {
|
||||
invoker = MethodInvokerUtils.<C, T> getMethodInvokerForSingleArgument(delegate);
|
||||
invoker = MethodInvokerUtils
|
||||
.<C, T>getMethodInvokerForSingleArgument(delegate);
|
||||
}
|
||||
Assert.state(invoker != null, "No single argument public method with or without "
|
||||
+ "@Classifier was found in delegate of type " + delegate.getClass());
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package org.springframework.classify;
|
||||
|
||||
/**
|
||||
* Base class for {@link Classifier} implementations. Provides default behaviour
|
||||
* and some convenience members, like constants.
|
||||
*
|
||||
* Base class for {@link Classifier} implementations. Provides default behaviour and some
|
||||
* convenience members, like constants.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ClassifierSupport<C, T> implements Classifier<C, T> {
|
||||
@@ -37,9 +37,9 @@ public class ClassifierSupport<C, T> implements Classifier<C, T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the default value. This is the main extension point for
|
||||
* subclasses, so it must be able to classify null.
|
||||
*
|
||||
* Always returns the default value. This is the main extension point for subclasses,
|
||||
* so it must be able to classify null.
|
||||
*
|
||||
* @see org.springframework.classify.Classifier#classify(Object)
|
||||
*/
|
||||
public T classify(C throwable) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
|
||||
public class PatternMatcher<S> {
|
||||
|
||||
private Map<String, S> map = new HashMap<String, S>();
|
||||
|
||||
private List<String> sorted = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
@@ -52,12 +53,10 @@ public class PatternMatcher<S> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
|
||||
* matches against a pattern. The pattern may contain two special
|
||||
* characters:<br>
|
||||
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string matches
|
||||
* against a pattern. The pattern may contain two special characters:<br>
|
||||
* '*' means zero or more characters<br>
|
||||
* '?' means one and only one character
|
||||
*
|
||||
* @param pattern pattern to match against. Must not be <code>null</code>.
|
||||
* @param str string which must be matched against the pattern. Must not be
|
||||
* <code>null</code>.
|
||||
@@ -192,20 +191,17 @@ public class PatternMatcher<S> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method takes a String key and a map from Strings to values of any
|
||||
* type. During processing, the method will identify the most specific key
|
||||
* in the map that matches the line. Once the correct is identified, its
|
||||
* value is returned. Note that if the map contains the wildcard string "*"
|
||||
* as a key, then it will serve as the "default" case, matching every line
|
||||
* that does not match anything else.
|
||||
*
|
||||
* This method takes a String key and a map from Strings to values of any type. During
|
||||
* processing, the method will identify the most specific key in the map that matches
|
||||
* the line. Once the correct is identified, its value is returned. Note that if the
|
||||
* map contains the wildcard string "*" as a key, then it will serve as the "default"
|
||||
* case, matching every line that does not match anything else.
|
||||
*
|
||||
* <p>
|
||||
* If no matching prefix is found, a {@link IllegalStateException} will be
|
||||
* thrown.
|
||||
*
|
||||
* If no matching prefix is found, a {@link IllegalStateException} will be thrown.
|
||||
*
|
||||
* <p>
|
||||
* Null keys are not allowed in the map.
|
||||
*
|
||||
* @param line An input string
|
||||
* @return the value whose prefix matches the given line
|
||||
*/
|
||||
@@ -222,7 +218,8 @@ public class PatternMatcher<S> {
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Could not find a matching pattern for key=[" + line + "]");
|
||||
throw new IllegalStateException(
|
||||
"Could not find a matching pattern for key=[" + line + "]");
|
||||
}
|
||||
return value;
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link Classifier} that maps from String patterns with wildcards to a set
|
||||
* of values of a given type. An input String is matched with the most specific
|
||||
* pattern possible to the corresponding value in an input map. A default value
|
||||
* should be specified with a pattern key of "*".
|
||||
*
|
||||
* A {@link Classifier} that maps from String patterns with wildcards to a set of values
|
||||
* of a given type. An input String is matched with the most specific pattern possible to
|
||||
* the corresponding value in an input map. A default value should be specified with a
|
||||
* pattern key of "*".
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class PatternMatchingClassifier<T> implements Classifier<String, T> {
|
||||
@@ -33,17 +33,16 @@ public class PatternMatchingClassifier<T> implements Classifier<String, T> {
|
||||
private PatternMatcher<T> values;
|
||||
|
||||
/**
|
||||
* Default constructor. Use the setter or the other constructor to create a
|
||||
* sensible classifier, otherwise all inputs will cause an exception.
|
||||
* Default constructor. Use the setter or the other constructor to create a sensible
|
||||
* classifier, otherwise all inputs will cause an exception.
|
||||
*/
|
||||
public PatternMatchingClassifier() {
|
||||
this(new HashMap<String, T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classifier from the provided map. The keys are patterns, using
|
||||
* '?' as a single character and '*' as multi-character wildcard.
|
||||
*
|
||||
* Create a classifier from the provided map. The keys are patterns, using '?' as a
|
||||
* single character and '*' as multi-character wildcard.
|
||||
* @param values the values to use in the {@link PatternMatcher}
|
||||
*/
|
||||
public PatternMatchingClassifier(Map<String, T> values) {
|
||||
@@ -61,11 +60,9 @@ public class PatternMatchingClassifier<T> implements Classifier<String, T> {
|
||||
|
||||
/**
|
||||
* Classify the input by matching it against the patterns provided in
|
||||
* {@link #setPatternMap(Map)}. The most specific pattern that matches will
|
||||
* be used to locate a value.
|
||||
*
|
||||
* {@link #setPatternMap(Map)}. The most specific pattern that matches will be used to
|
||||
* locate a value.
|
||||
* @return the value matching the most specific pattern possible
|
||||
*
|
||||
* @throws IllegalStateException if no matching value is found.
|
||||
*/
|
||||
public T classify(String classifiable) {
|
||||
|
||||
@@ -21,12 +21,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* A {@link Classifier} for a parameterised object type based on a map.
|
||||
* Classifies objects according to their inheritance relation with the supplied
|
||||
* type map. If the object to be classified is one of the keys of the provided
|
||||
* map, or is a subclass of one of the keys, then the map entry value for that
|
||||
* key is returned. Otherwise returns the default value which is null by
|
||||
* default.
|
||||
* A {@link Classifier} for a parameterised object type based on a map. Classifies objects
|
||||
* according to their inheritance relation with the supplied type map. If the object to be
|
||||
* classified is one of the keys of the provided map, or is a subclass of one of the keys,
|
||||
* then the map entry value for that key is returned. Otherwise returns the default value
|
||||
* which is null by default.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Gary Russell
|
||||
@@ -49,7 +48,6 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
public SubclassClassifier(C defaultValue) {
|
||||
@@ -58,7 +56,6 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue the default value
|
||||
* @param typeMap the map of types
|
||||
*/
|
||||
@@ -69,9 +66,8 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the default value for mapping keys that are not found
|
||||
* in the map (or their subclasses). Defaults to false.
|
||||
*
|
||||
* Public setter for the default value for mapping keys that are not found in the map
|
||||
* (or their subclasses). Defaults to false.
|
||||
* @param defaultValue the default value to set
|
||||
*/
|
||||
public void setDefaultValue(C defaultValue) {
|
||||
@@ -79,10 +75,9 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the classifications up as a map. The keys are types and these will be
|
||||
* mapped along with all their subclasses to the corresponding value. The
|
||||
* most specific types will match first.
|
||||
*
|
||||
* Set the classifications up as a map. The keys are types and these will be mapped
|
||||
* along with all their subclasses to the corresponding value. The most specific types
|
||||
* will match first.
|
||||
* @param map a map from type to class
|
||||
*/
|
||||
public void setTypeMap(Map<Class<? extends T>, C> map) {
|
||||
@@ -90,9 +85,8 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value from the type map whose key is the class of the given
|
||||
* Throwable, or its nearest ancestor if a subclass.
|
||||
*
|
||||
* Return the value from the type map whose key is the class of the given Throwable,
|
||||
* or its nearest ancestor if a subclass.
|
||||
* @return C the classified value
|
||||
* @param classifiable the classifiable thing
|
||||
*/
|
||||
@@ -110,11 +104,12 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
// check for subclasses
|
||||
C value = null;
|
||||
for (Class<?> cls = exceptionClass; !cls.equals(Object.class) && value == null; cls = cls.getSuperclass()) {
|
||||
for (Class<?> cls = exceptionClass; !cls.equals(Object.class)
|
||||
&& value == null; cls = cls.getSuperclass()) {
|
||||
value = classified.get(cls);
|
||||
}
|
||||
|
||||
//ConcurrentHashMap doesn't allow nulls
|
||||
// ConcurrentHashMap doesn't allow nulls
|
||||
if (value != null) {
|
||||
this.classified.put(exceptionClass, value);
|
||||
}
|
||||
@@ -128,7 +123,6 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
/**
|
||||
* Return the default value supplied in the constructor (default false).
|
||||
*
|
||||
* @return C the default value
|
||||
*/
|
||||
final public C getDefault() {
|
||||
@@ -138,4 +132,5 @@ public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
protected Map<Class<? extends T>, C> getClassified() {
|
||||
return classified;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mark a method as capable of classifying its input to an instance of its
|
||||
* output. Should only be used on non-void methods with one parameter.
|
||||
*
|
||||
* Mark a method as capable of classifying its input to an instance of its output. Should
|
||||
* only be used on non-void methods with one parameter.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -29,16 +29,15 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* MethodResolver implementation that finds a <em>single</em> Method on the
|
||||
* given Class that contains the specified annotation type.
|
||||
*
|
||||
* MethodResolver implementation that finds a <em>single</em> Method on the given Class
|
||||
* that contains the specified annotation type.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class AnnotationMethodResolver implements MethodResolver {
|
||||
|
||||
private Class<? extends Annotation> annotationType;
|
||||
|
||||
|
||||
/**
|
||||
* Create a MethodResolver for the specified Method-level annotation type
|
||||
* @param annotationType the type of the annotation
|
||||
@@ -51,19 +50,14 @@ public class AnnotationMethodResolver implements MethodResolver {
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a <em>single</em> Method on the Class of the given candidate object
|
||||
* that contains the annotation type for which this resolver is searching.
|
||||
*
|
||||
* @param candidate the instance whose Class will be checked for the
|
||||
* Find a <em>single</em> Method on the Class of the given candidate object that
|
||||
* contains the annotation type for which this resolver is searching.
|
||||
* @param candidate the instance whose Class will be checked for the annotation
|
||||
* @return a single matching Method instance or <code>null</code> if the candidate's
|
||||
* Class contains no Methods with the specified annotation
|
||||
* @throws IllegalArgumentException if more than one Method has the specified
|
||||
* annotation
|
||||
*
|
||||
* @return a single matching Method instance or <code>null</code> if the
|
||||
* candidate's Class contains no Methods with the specified annotation
|
||||
*
|
||||
* @throws IllegalArgumentException if more than one Method has the
|
||||
* specified annotation
|
||||
*/
|
||||
public Method findMethod(Object candidate) {
|
||||
Assert.notNull(candidate, "candidate object must not be null");
|
||||
@@ -75,26 +69,27 @@ public class AnnotationMethodResolver implements MethodResolver {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a <em>single</em> Method on the given Class that contains the
|
||||
* annotation type for which this resolver is searching.
|
||||
*
|
||||
* Find a <em>single</em> Method on the given Class that contains the annotation type
|
||||
* for which this resolver is searching.
|
||||
* @param clazz the Class instance to check for the annotation
|
||||
*
|
||||
* @return a single matching Method instance or <code>null</code> if the
|
||||
* Class contains no Methods with the specified annotation
|
||||
*
|
||||
* @throws IllegalArgumentException if more than one Method has the
|
||||
* specified annotation
|
||||
* @return a single matching Method instance or <code>null</code> if the Class
|
||||
* contains no Methods with the specified annotation
|
||||
* @throws IllegalArgumentException if more than one Method has the specified
|
||||
* annotation
|
||||
*/
|
||||
public Method findMethod(final Class<?> clazz) {
|
||||
Assert.notNull(clazz, "class must not be null");
|
||||
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method,
|
||||
annotationType);
|
||||
if (annotation != null) {
|
||||
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
|
||||
+ clazz + "] with the annotation type [" + annotationType + "]");
|
||||
Assert.isNull(annotatedMethod.get(),
|
||||
"found more than one method on target class [" + clazz
|
||||
+ "] with the annotation type [" + annotationType
|
||||
+ "]");
|
||||
annotatedMethod.set(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,12 @@
|
||||
package org.springframework.classify.util;
|
||||
|
||||
/**
|
||||
* A strategy interface for invoking a method.
|
||||
* Typically used by adapters.
|
||||
*
|
||||
* A strategy interface for invoking a method. Typically used by adapters.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public interface MethodInvoker {
|
||||
|
||||
Object invokeMethod(Object ... args);
|
||||
Object invokeMethod(Object... args);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ public class MethodInvokerUtils {
|
||||
|
||||
/**
|
||||
* Create a {@link MethodInvoker} using the provided method name to search.
|
||||
*
|
||||
* @param object to be invoked
|
||||
* @param methodName of the method to be invoked
|
||||
* @param paramsRequired boolean indicating whether the parameters are required, if
|
||||
@@ -71,7 +70,6 @@ public class MethodInvokerUtils {
|
||||
|
||||
/**
|
||||
* Create a String representation of the array of parameter types.
|
||||
*
|
||||
* @param paramTypes the types of parameters
|
||||
* @return the paramTypes as String representation
|
||||
*/
|
||||
@@ -89,7 +87,6 @@ public class MethodInvokerUtils {
|
||||
/**
|
||||
* Create a {@link MethodInvoker} using the provided interface, and method name from
|
||||
* that interface.
|
||||
*
|
||||
* @param cls the interface to search for the method named
|
||||
* @param methodName of the method to be invoked
|
||||
* @param object to be invoked
|
||||
@@ -111,7 +108,6 @@ public class MethodInvokerUtils {
|
||||
/**
|
||||
* Create a MethodInvoker from the delegate based on the annotationType. Ensure that
|
||||
* the annotated method has a valid set of parameters.
|
||||
*
|
||||
* @param annotationType the annotation to scan for
|
||||
* @param target the target object
|
||||
* @param expectedParamTypes the expected parameter types for the method
|
||||
@@ -163,7 +159,6 @@ public class MethodInvokerUtils {
|
||||
* Create {@link MethodInvoker} for the method with the provided annotation on the
|
||||
* provided object. Annotations that cannot be applied to methods (i.e. that aren't
|
||||
* annotated with an element type of METHOD) will cause an exception to be thrown.
|
||||
*
|
||||
* @param annotationType to be searched for
|
||||
* @param target to be invoked
|
||||
* @return MethodInvoker for the provided annotation, null if none is found.
|
||||
@@ -209,7 +204,6 @@ public class MethodInvokerUtils {
|
||||
|
||||
/**
|
||||
* Create a {@link MethodInvoker} for the delegate from a single public method.
|
||||
*
|
||||
* @param target an object to search for an appropriate method
|
||||
* @return a MethodInvoker that calls a method on the delegate
|
||||
* @param <T> the t
|
||||
@@ -241,4 +235,5 @@ public class MethodInvokerUtils {
|
||||
Method method = methodHolder.get();
|
||||
return new SimpleMethodInvoker(target, method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,37 +20,29 @@ import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Strategy interface for detecting a single Method on a Class.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public interface MethodResolver {
|
||||
|
||||
/**
|
||||
* Find a single Method on the provided Object that matches this resolver's
|
||||
* criteria.
|
||||
*
|
||||
* @param candidate the candidate Object whose Class should be searched for
|
||||
* a Method
|
||||
*
|
||||
* @return a single Method or <code>null</code> if no Method matching this
|
||||
* resolver's criteria can be found.
|
||||
*
|
||||
* @throws IllegalArgumentException if more than one Method defined on the
|
||||
* given candidate's Class matches this resolver's criteria
|
||||
* Find a single Method on the provided Object that matches this resolver's criteria.
|
||||
* @param candidate the candidate Object whose Class should be searched for a Method
|
||||
* @return a single Method or <code>null</code> if no Method matching this resolver's
|
||||
* criteria can be found.
|
||||
* @throws IllegalArgumentException if more than one Method defined on the given
|
||||
* candidate's Class matches this resolver's criteria
|
||||
*/
|
||||
Method findMethod(Object candidate) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Find a <em>single</em> Method on the given Class that matches this
|
||||
* resolver's criteria.
|
||||
*
|
||||
* Find a <em>single</em> Method on the given Class that matches this resolver's
|
||||
* criteria.
|
||||
* @param clazz the Class instance on which to search for a Method
|
||||
*
|
||||
* @return a single Method or <code>null</code> if no Method matching this
|
||||
* resolver's criteria can be found.
|
||||
*
|
||||
* @throws IllegalArgumentException if more than one Method defined on the
|
||||
* given Class matches this resolver's criteria
|
||||
* @return a single Method or <code>null</code> if no Method matching this resolver's
|
||||
* criteria can be found.
|
||||
* @throws IllegalArgumentException if more than one Method defined on the given Class
|
||||
* matches this resolver's criteria
|
||||
*/
|
||||
Method findMethod(Class<?> clazz);
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Simple implementation of the {@link MethodInvoker} interface that invokes a
|
||||
* method on an object. If the method has no arguments, but arguments are
|
||||
* provided, they are ignored and the method is invoked anyway. If there are
|
||||
* more arguments than there are provided, then an exception is thrown.
|
||||
* Simple implementation of the {@link MethodInvoker} interface that invokes a method on
|
||||
* an object. If the method has no arguments, but arguments are provided, they are ignored
|
||||
* and the method is invoked anyway. If there are more arguments than there are provided,
|
||||
* then an exception is thrown.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @author Artem Bilan
|
||||
@@ -54,14 +54,18 @@ public class SimpleMethodInvoker implements MethodInvoker {
|
||||
|
||||
public SimpleMethodInvoker(Object object, String methodName, Class<?>... paramTypes) {
|
||||
Assert.notNull(object, "Object to invoke must not be null");
|
||||
Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
|
||||
Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName,
|
||||
paramTypes);
|
||||
if (method == null) {
|
||||
// try with no params
|
||||
method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[] {});
|
||||
method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName,
|
||||
new Class[] {});
|
||||
}
|
||||
|
||||
Assert.notNull(method, "No methods found for name: [" + methodName + "] in class: ["
|
||||
+ object.getClass() + "] with arguments of type: [" + Arrays.toString(paramTypes) + "]");
|
||||
Assert.notNull(method,
|
||||
"No methods found for name: [" + methodName + "] in class: ["
|
||||
+ object.getClass() + "] with arguments of type: ["
|
||||
+ Arrays.toString(paramTypes) + "]");
|
||||
|
||||
this.object = object;
|
||||
this.method = method;
|
||||
@@ -72,14 +76,14 @@ public class SimpleMethodInvoker implements MethodInvoker {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod
|
||||
* @see org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod
|
||||
* (java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public Object invokeMethod(Object... args) {
|
||||
Assert.state(this.parameterTypes.length == args.length,
|
||||
"Wrong number of arguments, expected no more than: [" + this.parameterTypes.length + "]");
|
||||
"Wrong number of arguments, expected no more than: ["
|
||||
+ this.parameterTypes.length + "]");
|
||||
|
||||
try {
|
||||
// Extract the target from an Advised as late as possible
|
||||
@@ -88,8 +92,9 @@ public class SimpleMethodInvoker implements MethodInvoker {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException("Unable to invoke method: [" + this.method + "] on object: ["
|
||||
+ this.object + "] with arguments: [" + Arrays.toString(args) + "]", e);
|
||||
throw new IllegalArgumentException("Unable to invoke method: [" + this.method
|
||||
+ "] on object: [" + this.object + "] with arguments: ["
|
||||
+ Arrays.toString(args) + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +106,8 @@ public class SimpleMethodInvoker implements MethodInvoker {
|
||||
source = ((Advised) target).getTargetSource().getTarget();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Could not extract target from proxy", e);
|
||||
throw new IllegalStateException("Could not extract target from proxy",
|
||||
e);
|
||||
}
|
||||
if (source instanceof Advised) {
|
||||
source = extractTarget(source, method);
|
||||
|
||||
@@ -17,9 +17,8 @@ package org.springframework.retry;
|
||||
|
||||
/**
|
||||
* Callback for stateful retry after all tries are exhausted.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface RecoveryCallback<T> {
|
||||
|
||||
@@ -22,7 +22,6 @@ package org.springframework.retry;
|
||||
*
|
||||
* @param <T> the type of object returned by the callback
|
||||
* @param <E> the type of exception it declares may be thrown
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@@ -30,11 +29,12 @@ public interface RetryCallback<T, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* Execute an operation with retry semantics. Operations should generally be
|
||||
* idempotent, but implementations may choose to implement compensation
|
||||
* semantics when an operation is retried.
|
||||
* idempotent, but implementations may choose to implement compensation semantics when
|
||||
* an operation is retried.
|
||||
* @param context the current retry context.
|
||||
* @return the result of the successful operation.
|
||||
* @throws E of type E if processing fails
|
||||
*/
|
||||
T doWithRetry(RetryContext context) throws E;
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import org.springframework.core.AttributeAccessor;
|
||||
/**
|
||||
* Low-level access to ongoing retry operation. Normally not needed by clients, but can be
|
||||
* used to alter the course of the retry, e.g. force an early termination.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface RetryContext extends AttributeAccessor {
|
||||
|
||||
@@ -35,7 +35,8 @@ public interface RetryContext extends AttributeAccessor {
|
||||
String NAME = "context.name";
|
||||
|
||||
/**
|
||||
* Retry context attribute name for state key. Can be used to identify a stateful retry from its context.
|
||||
* Retry context attribute name for state key. Can be used to identify a stateful
|
||||
* retry from its context.
|
||||
*/
|
||||
String STATE_KEY = "context.state";
|
||||
|
||||
@@ -62,14 +63,12 @@ public interface RetryContext extends AttributeAccessor {
|
||||
|
||||
/**
|
||||
* Public accessor for the exhausted flag {@link #setExhaustedOnly()}.
|
||||
*
|
||||
* @return true if the flag has been set.
|
||||
*/
|
||||
boolean isExhaustedOnly();
|
||||
|
||||
/**
|
||||
* Accessor for the parent context if retry blocks are nested.
|
||||
*
|
||||
* @return the parent or null if there is none.
|
||||
*/
|
||||
RetryContext getParent();
|
||||
@@ -77,14 +76,12 @@ public interface RetryContext extends AttributeAccessor {
|
||||
/**
|
||||
* Counts the number of retry attempts. Before the first attempt this counter is zero,
|
||||
* and before the first and subsequent attempts it should increment accordingly.
|
||||
*
|
||||
* @return the number of retries.
|
||||
*/
|
||||
int getRetryCount();
|
||||
|
||||
/**
|
||||
* Accessor for the exception object that caused the current retry.
|
||||
*
|
||||
* @return the last exception that caused a retry, or possibly null. It will be null
|
||||
* if this is the first attempt, but also if the enclosing policy decides not to
|
||||
* provide it (e.g. because of concerns about memory usage).
|
||||
|
||||
@@ -16,53 +16,51 @@
|
||||
|
||||
package org.springframework.retry;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for listener that can be used to add behaviour to a retry.
|
||||
* Implementations of {@link RetryOperations} can chose to issue callbacks to an
|
||||
* interceptor during the retry lifecycle.
|
||||
*
|
||||
* Interface for listener that can be used to add behaviour to a retry. Implementations of
|
||||
* {@link RetryOperations} can chose to issue callbacks to an interceptor during the retry
|
||||
* lifecycle.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface RetryListener {
|
||||
|
||||
/**
|
||||
* Called before the first attempt in a retry. For instance, implementers
|
||||
* can set up state that is needed by the policies in the
|
||||
* {@link RetryOperations}. The whole retry can be vetoed by returning
|
||||
* false from this method, in which case a {@link TerminatedRetryException}
|
||||
* will be thrown.
|
||||
*
|
||||
* Called before the first attempt in a retry. For instance, implementers can set up
|
||||
* state that is needed by the policies in the {@link RetryOperations}. The whole
|
||||
* retry can be vetoed by returning false from this method, in which case a
|
||||
* {@link TerminatedRetryException} will be thrown.
|
||||
* @param <T> the type of object returned by the callback
|
||||
* @param <E> the type of exception it declares may be thrown
|
||||
* @param context the current {@link RetryContext}.
|
||||
* @param callback the current {@link RetryCallback}.
|
||||
* @return true if the retry should proceed.
|
||||
*/
|
||||
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);
|
||||
<T, E extends Throwable> boolean open(RetryContext context,
|
||||
RetryCallback<T, E> callback);
|
||||
|
||||
/**
|
||||
* Called after the final attempt (successful or not). Allow the interceptor
|
||||
* to clean up any resource it is holding before control returns to the
|
||||
* retry caller.
|
||||
*
|
||||
* Called after the final attempt (successful or not). Allow the interceptor to clean
|
||||
* up any resource it is holding before control returns to the retry caller.
|
||||
* @param context the current {@link RetryContext}.
|
||||
* @param callback the current {@link RetryCallback}.
|
||||
* @param throwable the last exception that was thrown by the callback.
|
||||
* @param <E> the exception type
|
||||
* @param <T> the return value
|
||||
*/
|
||||
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
|
||||
<T, E extends Throwable> void close(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable);
|
||||
|
||||
/**
|
||||
* Called after every unsuccessful attempt at a retry.
|
||||
*
|
||||
* @param context the current {@link RetryContext}.
|
||||
* @param callback the current {@link RetryCallback}.
|
||||
* @param throwable the last exception that was thrown by the callback.
|
||||
* @param <T> the return value
|
||||
* @param <E> the exception to throw
|
||||
*/
|
||||
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
|
||||
<T, E extends Throwable> void onError(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,22 +19,20 @@ package org.springframework.retry;
|
||||
import org.springframework.retry.support.DefaultRetryState;
|
||||
|
||||
/**
|
||||
* Defines the basic set of operations implemented by {@link RetryOperations} to
|
||||
* execute operations with configurable retry behaviour.
|
||||
*
|
||||
* Defines the basic set of operations implemented by {@link RetryOperations} to execute
|
||||
* operations with configurable retry behaviour.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface RetryOperations {
|
||||
|
||||
/**
|
||||
* Execute the supplied {@link RetryCallback} with the configured retry
|
||||
* semantics. See implementations for configuration details.
|
||||
*
|
||||
* @return the value returned by the {@link RetryCallback} upon successful
|
||||
* invocation.
|
||||
* @throws E any {@link Exception} raised by the
|
||||
* {@link RetryCallback} upon unsuccessful retry.
|
||||
* Execute the supplied {@link RetryCallback} with the configured retry semantics. See
|
||||
* implementations for configuration details.
|
||||
* @return the value returned by the {@link RetryCallback} upon successful invocation.
|
||||
* @throws E any {@link Exception} raised by the {@link RetryCallback} upon
|
||||
* unsuccessful retry.
|
||||
* @throws E the exception thrown
|
||||
* @param <T> the return value
|
||||
* @param retryCallback the {@link RetryCallback}
|
||||
@@ -43,60 +41,58 @@ public interface RetryOperations {
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
|
||||
|
||||
/**
|
||||
* Execute the supplied {@link RetryCallback} with a fallback on exhausted
|
||||
* retry to the {@link RecoveryCallback}. See implementations for
|
||||
* configuration details.
|
||||
*
|
||||
* @return the value returned by the {@link RetryCallback} upon successful
|
||||
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* Execute the supplied {@link RetryCallback} with a fallback on exhausted retry to
|
||||
* the {@link RecoveryCallback}. See implementations for configuration details.
|
||||
* @return the value returned by the {@link RetryCallback} upon successful invocation,
|
||||
* and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @throws E any {@link Exception} raised by the
|
||||
* @param <T> the type to return
|
||||
* @param <E> the type of the exception
|
||||
* @param recoveryCallback the {@link RecoveryCallback}
|
||||
* @param retryCallback the {@link RetryCallback}
|
||||
* {@link RecoveryCallback} upon unsuccessful retry.
|
||||
* @param retryCallback the {@link RetryCallback} {@link RecoveryCallback} upon
|
||||
* unsuccessful retry.
|
||||
*/
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
|
||||
RecoveryCallback<T> recoveryCallback) throws E;
|
||||
|
||||
/**
|
||||
* A simple stateful retry. Execute the supplied {@link RetryCallback} with
|
||||
* a target object for the attempt identified by the {@link DefaultRetryState}.
|
||||
* Exceptions thrown by the callback are always propagated immediately so
|
||||
* the state is required to be able to identify the previous attempt, if
|
||||
* there is one - hence the state is required. Normal patterns would see
|
||||
* this method being used inside a transaction, where the callback might
|
||||
* invalidate the transaction if it fails.
|
||||
*
|
||||
* See implementations for configuration details.
|
||||
* A simple stateful retry. Execute the supplied {@link RetryCallback} with a target
|
||||
* object for the attempt identified by the {@link DefaultRetryState}. Exceptions
|
||||
* thrown by the callback are always propagated immediately so the state is required
|
||||
* to be able to identify the previous attempt, if there is one - hence the state is
|
||||
* required. Normal patterns would see this method being used inside a transaction,
|
||||
* where the callback might invalidate the transaction if it fails.
|
||||
*
|
||||
* See implementations for configuration details.
|
||||
* @param retryCallback the {@link RetryCallback}
|
||||
* @param retryState the {@link RetryState}
|
||||
* @return the value returned by the {@link RetryCallback} upon successful
|
||||
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @return the value returned by the {@link RetryCallback} upon successful invocation,
|
||||
* and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @throws E any {@link Exception} raised by the {@link RecoveryCallback}.
|
||||
* @throws ExhaustedRetryException if the last attempt for this state has
|
||||
* already been reached
|
||||
* @throws ExhaustedRetryException if the last attempt for this state has already been
|
||||
* reached
|
||||
* @param <T> the type of the return value
|
||||
* @param <E> the type of the exception to return
|
||||
*/
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
|
||||
RetryState retryState) throws E, ExhaustedRetryException;
|
||||
|
||||
/**
|
||||
* A stateful retry with a recovery path. Execute the supplied
|
||||
* {@link RetryCallback} with a fallback on exhausted retry to the
|
||||
* {@link RecoveryCallback} and a target object for the retry attempt
|
||||
* identified by the {@link DefaultRetryState}.
|
||||
* A stateful retry with a recovery path. Execute the supplied {@link RetryCallback}
|
||||
* with a fallback on exhausted retry to the {@link RecoveryCallback} and a target
|
||||
* object for the retry attempt identified by the {@link DefaultRetryState}.
|
||||
* @param recoveryCallback the {@link RecoveryCallback}
|
||||
* @param retryState the {@link RetryState}
|
||||
* @param retryCallback the {@link RetryCallback}
|
||||
* @see #execute(RetryCallback, RetryState)
|
||||
* @param <T> the return value type
|
||||
* @param <E> the exception type
|
||||
* @return the value returned by the {@link RetryCallback} upon successful
|
||||
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @throws E any {@link Exception} raised by the {@link RecoveryCallback} upon unsuccessful retry.
|
||||
* @return the value returned by the {@link RetryCallback} upon successful invocation,
|
||||
* and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @throws E any {@link Exception} raised by the {@link RecoveryCallback} upon
|
||||
* unsuccessful retry.
|
||||
*/
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
|
||||
throws E;
|
||||
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
|
||||
RecoveryCallback<T> recoveryCallback, RetryState retryState) throws E;
|
||||
|
||||
}
|
||||
|
||||
@@ -19,12 +19,11 @@ 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
|
||||
* operations to be aware of their context. Context can be internal to the retry
|
||||
* framework, e.g. to support nested retries. Context can also be external, and
|
||||
* the {@link RetryPolicy} provides a uniform API for a range of different
|
||||
* platforms for the external context.
|
||||
* A {@link RetryPolicy} is responsible for allocating and managing resources needed by
|
||||
* {@link RetryOperations}. The {@link RetryPolicy} allows retry operations to be aware of
|
||||
* their context. Context can be internal to the retry framework, e.g. to support nested
|
||||
* retries. Context can also be external, and the {@link RetryPolicy} provides a uniform
|
||||
* API for a range of different platforms for the external context.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -38,25 +37,23 @@ public interface RetryPolicy extends Serializable {
|
||||
boolean canRetry(RetryContext context);
|
||||
|
||||
/**
|
||||
* Acquire resources needed for the retry operation. The callback is passed
|
||||
* in so that marker interfaces can be used and a manager can collaborate
|
||||
* with the callback to set up some state in the status token.
|
||||
* Acquire resources needed for the retry operation. The callback is passed in so that
|
||||
* marker interfaces can be used and a manager can collaborate with the callback to
|
||||
* set up some state in the status token.
|
||||
* @param parent the parent context if we are in a nested retry.
|
||||
*
|
||||
* @return a {@link RetryContext} object specific to this policy.
|
||||
*
|
||||
*/
|
||||
RetryContext open(RetryContext parent);
|
||||
|
||||
/**
|
||||
* @param context a retry status created by the
|
||||
* {@link #open(RetryContext)} method of this policy.
|
||||
* @param context a retry status created by the {@link #open(RetryContext)} method of
|
||||
* this policy.
|
||||
*/
|
||||
void close(RetryContext context);
|
||||
|
||||
/**
|
||||
* Called once per retry attempt, after the callback fails.
|
||||
*
|
||||
* @param context the current status object.
|
||||
* @param throwable the exception to throw
|
||||
*/
|
||||
|
||||
@@ -16,42 +16,39 @@
|
||||
package org.springframework.retry;
|
||||
|
||||
/**
|
||||
* Stateful retry is characterised by having to recognise the items that are
|
||||
* being processed, so this interface is used primarily to provide a cache key in
|
||||
* between failed attempts. It also provides a hints to the
|
||||
* {@link RetryOperations} for optimisations to do with avoidable cache hits and
|
||||
* switching to stateless retry if a rollback is not needed.
|
||||
*
|
||||
* Stateful retry is characterised by having to recognise the items that are being
|
||||
* processed, so this interface is used primarily to provide a cache key in between failed
|
||||
* attempts. It also provides a hints to the {@link RetryOperations} for optimisations to
|
||||
* do with avoidable cache hits and switching to stateless retry if a rollback is not
|
||||
* needed.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RetryState {
|
||||
|
||||
/**
|
||||
* Key representing the state for a retry attempt. Stateful retry is
|
||||
* characterised by having to recognise the items that are being processed,
|
||||
* so this value is used as a cache key in between failed attempts.
|
||||
*
|
||||
* Key representing the state for a retry attempt. Stateful retry is characterised by
|
||||
* having to recognise the items that are being processed, so this value is used as a
|
||||
* cache key in between failed attempts.
|
||||
* @return the key that this state represents
|
||||
*/
|
||||
Object getKey();
|
||||
|
||||
/**
|
||||
* Indicate whether a cache lookup can be avoided. If the key is known ahead
|
||||
* of the retry attempt to be fresh (i.e. has never been seen before) then a
|
||||
* cache lookup can be avoided if this flag is true.
|
||||
*
|
||||
* Indicate whether a cache lookup can be avoided. If the key is known ahead of the
|
||||
* retry attempt to be fresh (i.e. has never been seen before) then a cache lookup can
|
||||
* be avoided if this flag is true.
|
||||
* @return true if the state does not require an explicit check for the key
|
||||
*/
|
||||
boolean isForceRefresh();
|
||||
|
||||
/**
|
||||
* Check whether this exception requires a rollback. The default is always
|
||||
* true, which is conservative, so this method provides an optimisation for
|
||||
* switching to stateless retry if there is an exception for which rollback
|
||||
* is unnecessary. Example usage would be for a stateful retry to specify a
|
||||
* validation exception as not for rollback.
|
||||
*
|
||||
* Check whether this exception requires a rollback. The default is always true, which
|
||||
* is conservative, so this method provides an optimisation for switching to stateless
|
||||
* retry if there is an exception for which rollback is unnecessary. Example usage
|
||||
* would be for a stateful retry to specify a validation exception as not for
|
||||
* rollback.
|
||||
* @param exception the exception that caused a retry attempt to fail
|
||||
* @return true if this exception should cause a rollback
|
||||
*/
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package org.springframework.retry;
|
||||
|
||||
/**
|
||||
* Interface for statistics reporting of retry attempts. Counts the number of
|
||||
* retry attempts, successes, errors (including retries), and aborts.
|
||||
*
|
||||
* Interface for statistics reporting of retry attempts. Counts the number of retry
|
||||
* attempts, successes, errors (including retries), and aborts.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface RetryStatistics {
|
||||
|
||||
@@ -31,39 +31,32 @@ public interface RetryStatistics {
|
||||
int getCompleteCount();
|
||||
|
||||
/**
|
||||
* Get the number of times a retry block has been entered, irrespective of
|
||||
* how many times the operation was retried.
|
||||
*
|
||||
* Get the number of times a retry block has been entered, irrespective of how many
|
||||
* times the operation was retried.
|
||||
* @return the number of retry blocks started.
|
||||
*/
|
||||
int getStartedCount();
|
||||
|
||||
/**
|
||||
* Get the number of errors detected, whether or not they resulted in a
|
||||
* retry.
|
||||
*
|
||||
* Get the number of errors detected, whether or not they resulted in a retry.
|
||||
* @return the number of errors detected.
|
||||
*/
|
||||
int getErrorCount();
|
||||
|
||||
/**
|
||||
* Get the number of times a block failed to complete successfully, even
|
||||
* after retry.
|
||||
*
|
||||
* Get the number of times a block failed to complete successfully, even after retry.
|
||||
* @return the number of retry attempts that failed overall.
|
||||
*/
|
||||
int getAbortCount();
|
||||
|
||||
/**
|
||||
* Get the number of times a recovery callback was applied.
|
||||
*
|
||||
* @return the number of recovered attempts.
|
||||
*/
|
||||
int getRecoveryCount();
|
||||
|
||||
/**
|
||||
* Get an identifier for the retry block for reporting purposes.
|
||||
*
|
||||
* @return an identifier for the block.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
@@ -63,8 +63,8 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Interceptor that parses the retry metadata on the method it is invoking and
|
||||
* delegates to an appropriate RetryOperationsInterceptor.
|
||||
* Interceptor that parses the retry metadata on the method it is invoking and delegates
|
||||
* to an appropriate RetryOperationsInterceptor.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
@@ -72,7 +72,8 @@ import org.springframework.util.StringUtils;
|
||||
* @since 1.1
|
||||
*
|
||||
*/
|
||||
public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
|
||||
public class AnnotationAwareRetryOperationsInterceptor
|
||||
implements IntroductionInterceptor, BeanFactoryAware {
|
||||
|
||||
private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
|
||||
|
||||
@@ -80,8 +81,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
private final Map<Object, Map<Method, MethodInterceptor>> delegates =
|
||||
new HashMap<Object, Map<Method, MethodInterceptor>>();
|
||||
private final Map<Object, Map<Method, MethodInterceptor>> delegates = new HashMap<Object, Map<Method, MethodInterceptor>>();
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
|
||||
@@ -104,7 +104,6 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
*
|
||||
* @param retryContextCache the {@link RetryContextCache} to set.
|
||||
*/
|
||||
public void setRetryContextCache(RetryContextCache retryContextCache) {
|
||||
@@ -121,7 +120,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
/**
|
||||
* @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier}
|
||||
*/
|
||||
public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
public void setNewItemIdentifier(
|
||||
NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
* @param globalListeners the default listeners
|
||||
*/
|
||||
public void setListeners(Collection<RetryListener> globalListeners) {
|
||||
ArrayList<RetryListener> retryListeners = new ArrayList<RetryListener>(globalListeners);
|
||||
ArrayList<RetryListener> retryListeners = new ArrayList<RetryListener>(
|
||||
globalListeners);
|
||||
AnnotationAwareOrderComparator.sort(retryListeners);
|
||||
this.globalListeners = retryListeners.toArray(new RetryListener[0]);
|
||||
}
|
||||
@@ -143,12 +144,14 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
@Override
|
||||
public boolean implementsInterface(Class<?> intf) {
|
||||
return org.springframework.retry.interceptor.Retryable.class.isAssignableFrom(intf);
|
||||
return org.springframework.retry.interceptor.Retryable.class
|
||||
.isAssignableFrom(intf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
|
||||
MethodInterceptor delegate = getDelegate(invocation.getThis(),
|
||||
invocation.getMethod());
|
||||
if (delegate != null) {
|
||||
return delegate.invoke(invocation);
|
||||
}
|
||||
@@ -158,16 +161,20 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
|
||||
private MethodInterceptor getDelegate(Object target, Method method) {
|
||||
if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
|
||||
if (!this.delegates.containsKey(target)
|
||||
|| !this.delegates.get(target).containsKey(method)) {
|
||||
synchronized (this.delegates) {
|
||||
if (!this.delegates.containsKey(target)) {
|
||||
this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
|
||||
}
|
||||
Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
|
||||
Map<Method, MethodInterceptor> delegatesForTarget = this.delegates
|
||||
.get(target);
|
||||
if (!delegatesForTarget.containsKey(method)) {
|
||||
Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
|
||||
Retryable retryable = AnnotationUtils.findAnnotation(method,
|
||||
Retryable.class);
|
||||
if (retryable == null) {
|
||||
retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
|
||||
retryable = AnnotationUtils.findAnnotation(
|
||||
method.getDeclaringClass(), Retryable.class);
|
||||
}
|
||||
if (retryable == null) {
|
||||
retryable = findAnnotationOnTarget(target, method);
|
||||
@@ -177,7 +184,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
MethodInterceptor delegate;
|
||||
if (StringUtils.hasText(retryable.interceptor())) {
|
||||
delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
|
||||
delegate = this.beanFactory.getBean(retryable.interceptor(),
|
||||
MethodInterceptor.class);
|
||||
}
|
||||
else if (retryable.stateful()) {
|
||||
delegate = getStatefulInterceptor(target, method, retryable);
|
||||
@@ -194,10 +202,13 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
private Retryable findAnnotationOnTarget(Object target, Method method) {
|
||||
try {
|
||||
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
|
||||
Retryable retryable = AnnotationUtils.findAnnotation(targetMethod, Retryable.class);
|
||||
Method targetMethod = target.getClass().getMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
Retryable retryable = AnnotationUtils.findAnnotation(targetMethod,
|
||||
Retryable.class);
|
||||
if (retryable == null) {
|
||||
retryable = AnnotationUtils.findAnnotation(targetMethod.getDeclaringClass(), Retryable.class);
|
||||
retryable = AnnotationUtils.findAnnotation(
|
||||
targetMethod.getDeclaringClass(), Retryable.class);
|
||||
}
|
||||
|
||||
return retryable;
|
||||
@@ -207,23 +218,23 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
|
||||
private MethodInterceptor getStatelessInterceptor(Object target, Method method,
|
||||
Retryable retryable) {
|
||||
RetryTemplate template = createTemplate(retryable.listeners());
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
return RetryInterceptorBuilder.stateless()
|
||||
.retryOperations(template)
|
||||
.label(retryable.label())
|
||||
.recoverer(getRecoverer(target, method))
|
||||
.build();
|
||||
return RetryInterceptorBuilder.stateless().retryOperations(template)
|
||||
.label(retryable.label()).recoverer(getRecoverer(target, method)).build();
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
|
||||
private MethodInterceptor getStatefulInterceptor(Object target, Method method,
|
||||
Retryable retryable) {
|
||||
RetryTemplate template = createTemplate(retryable.listeners());
|
||||
template.setRetryContextCache(this.retryContextCache);
|
||||
|
||||
CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
|
||||
if (circuit!=null) {
|
||||
CircuitBreaker circuit = AnnotationUtils.findAnnotation(method,
|
||||
CircuitBreaker.class);
|
||||
if (circuit != null) {
|
||||
RetryPolicy policy = getRetryPolicy(circuit);
|
||||
CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
|
||||
breaker.setOpenTimeout(getOpenTimeout(circuit));
|
||||
@@ -231,15 +242,13 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
template.setRetryPolicy(breaker);
|
||||
template.setBackOffPolicy(new NoBackOffPolicy());
|
||||
String label = circuit.label();
|
||||
if (!StringUtils.hasText(label)) {
|
||||
if (!StringUtils.hasText(label)) {
|
||||
label = method.toGenericString();
|
||||
}
|
||||
return RetryInterceptorBuilder.circuitBreaker()
|
||||
.keyGenerator(new FixedKeyGenerator("circuit"))
|
||||
.retryOperations(template)
|
||||
.recoverer(getRecoverer(target, method))
|
||||
.label(label)
|
||||
.build();
|
||||
.retryOperations(template).recoverer(getRecoverer(target, method))
|
||||
.label(label).build();
|
||||
}
|
||||
RetryPolicy policy = getRetryPolicy(retryable);
|
||||
template.setRetryPolicy(policy);
|
||||
@@ -248,16 +257,14 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
return RetryInterceptorBuilder.stateful()
|
||||
.keyGenerator(this.methodArgumentsKeyGenerator)
|
||||
.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier)
|
||||
.retryOperations(template)
|
||||
.label(label)
|
||||
.recoverer(getRecoverer(target, method))
|
||||
.build();
|
||||
.retryOperations(template).label(label)
|
||||
.recoverer(getRecoverer(target, method)).build();
|
||||
}
|
||||
|
||||
private long getOpenTimeout(CircuitBreaker circuit) {
|
||||
if (StringUtils.hasText(circuit.openTimeoutExpression())) {
|
||||
Long value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()), PARSER_CONTEXT)
|
||||
.getValue(Long.class);
|
||||
Long value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()),
|
||||
PARSER_CONTEXT).getValue(Long.class);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
@@ -267,8 +274,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
private long getResetTimeout(CircuitBreaker circuit) {
|
||||
if (StringUtils.hasText(circuit.resetTimeoutExpression())) {
|
||||
Long value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()), PARSER_CONTEXT)
|
||||
.getValue(Long.class);
|
||||
Long value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()),
|
||||
PARSER_CONTEXT).getValue(Long.class);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
@@ -280,7 +287,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
if (listenersBeanNames.length > 0) {
|
||||
template.setListeners(getListenersBeans(listenersBeanNames));
|
||||
} else if (globalListeners !=null) {
|
||||
}
|
||||
else if (globalListeners != null) {
|
||||
template.setListeners(globalListeners);
|
||||
}
|
||||
return template;
|
||||
@@ -289,7 +297,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
private RetryListener[] getListenersBeans(String[] listenersBeanNames) {
|
||||
RetryListener[] listeners = new RetryListener[listenersBeanNames.length];
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
listeners[i] = beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
|
||||
listeners[i] = beanFactory.getBean(listenersBeanNames[i],
|
||||
RetryListener.class);
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
@@ -301,8 +310,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
final AtomicBoolean foundRecoverable = new AtomicBoolean(false);
|
||||
ReflectionUtils.doWithMethods(target.getClass(), new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
if (AnnotationUtils.findAnnotation(method, Recover.class) != null) {
|
||||
foundRecoverable.set(true);
|
||||
}
|
||||
@@ -318,26 +327,31 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
private RetryPolicy getRetryPolicy(Annotation retryable) {
|
||||
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
|
||||
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs
|
||||
.get("value");
|
||||
String exceptionExpression = (String) attrs.get("exceptionExpression");
|
||||
boolean hasExpression = StringUtils.hasText(exceptionExpression);
|
||||
if (includes.length == 0) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
|
||||
Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs
|
||||
.get("include");
|
||||
includes = value;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
|
||||
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs
|
||||
.get("exclude");
|
||||
Integer maxAttempts = (Integer) attrs.get("maxAttempts");
|
||||
String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
|
||||
if (StringUtils.hasText(maxAttemptsExpression)) {
|
||||
maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
|
||||
maxAttempts = PARSER
|
||||
.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Integer.class);
|
||||
}
|
||||
if (includes.length == 0 && excludes.length == 0) {
|
||||
SimpleRetryPolicy simple = hasExpression ? new ExpressionRetryPolicy(resolve(exceptionExpression))
|
||||
.withBeanFactory(this.beanFactory)
|
||||
: new SimpleRetryPolicy();
|
||||
SimpleRetryPolicy simple = hasExpression
|
||||
? new ExpressionRetryPolicy(resolve(exceptionExpression))
|
||||
.withBeanFactory(this.beanFactory)
|
||||
: new SimpleRetryPolicy();
|
||||
simple.setMaxAttempts(maxAttempts);
|
||||
return simple;
|
||||
}
|
||||
@@ -350,8 +364,9 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
boolean retryNotExcluded = includes.length == 0;
|
||||
if (hasExpression) {
|
||||
return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression, retryNotExcluded)
|
||||
.withBeanFactory(this.beanFactory);
|
||||
return new ExpressionRetryPolicy(maxAttempts, policyMap, true,
|
||||
exceptionExpression, retryNotExcluded)
|
||||
.withBeanFactory(this.beanFactory);
|
||||
}
|
||||
else {
|
||||
return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
|
||||
@@ -361,17 +376,20 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
private BackOffPolicy getBackoffPolicy(Backoff backoff) {
|
||||
long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
|
||||
if (StringUtils.hasText(backoff.delayExpression())) {
|
||||
min = PARSER.parseExpression(resolve(backoff.delayExpression()), PARSER_CONTEXT)
|
||||
min = PARSER
|
||||
.parseExpression(resolve(backoff.delayExpression()), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
}
|
||||
long max = backoff.maxDelay();
|
||||
if (StringUtils.hasText(backoff.maxDelayExpression())) {
|
||||
max = PARSER.parseExpression(resolve(backoff.maxDelayExpression()), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
max = PARSER.parseExpression(resolve(backoff.maxDelayExpression()),
|
||||
PARSER_CONTEXT).getValue(this.evaluationContext, Long.class);
|
||||
}
|
||||
double multiplier = backoff.multiplier();
|
||||
if (StringUtils.hasText(backoff.multiplierExpression())) {
|
||||
multiplier = PARSER.parseExpression(resolve(backoff.multiplierExpression()), PARSER_CONTEXT)
|
||||
multiplier = PARSER
|
||||
.parseExpression(resolve(backoff.multiplierExpression()),
|
||||
PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Double.class);
|
||||
}
|
||||
if (multiplier > 0) {
|
||||
@@ -381,7 +399,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
policy.setInitialInterval(min);
|
||||
policy.setMultiplier(multiplier);
|
||||
policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
policy.setMaxInterval(
|
||||
max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
@@ -410,8 +429,10 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
* @see ConfigurableBeanFactory#resolveEmbeddedValue
|
||||
*/
|
||||
private String resolve(String value) {
|
||||
if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) {
|
||||
return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
|
||||
if (this.beanFactory != null
|
||||
&& this.beanFactory instanceof ConfigurableBeanFactory) {
|
||||
return ((ConfigurableBeanFactory) this.beanFactory)
|
||||
.resolveEmbeddedValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -49,19 +49,17 @@ import org.springframework.retry.backoff.BackOffPolicy;
|
||||
public @interface Backoff {
|
||||
|
||||
/**
|
||||
* Synonym for {@link #delay()}. When {@link #delay()} is non-zero, value of this element
|
||||
* is ignored, otherwise value of this element is taken.
|
||||
*
|
||||
* Synonym for {@link #delay()}. When {@link #delay()} is non-zero, value of this
|
||||
* element is ignored, otherwise value of this element is taken.
|
||||
* @return the delay in milliseconds (default 1000)
|
||||
*/
|
||||
long value() default 1000;
|
||||
|
||||
/**
|
||||
* A canonical backoff period. Used as an initial value in the exponential case, and
|
||||
* as a minimum value in the uniform case. When the value of this element is 0,
|
||||
* value of element {@link #value()} istaken, otherwise value of this
|
||||
* element is taken and {@link #value()} is ignored.
|
||||
*
|
||||
* as a minimum value in the uniform case. When the value of this element is 0, value
|
||||
* of element {@link #value()} istaken, otherwise value of this element is taken and
|
||||
* {@link #value()} is ignored.
|
||||
* @return the initial or canonical backoff period in milliseconds (default 0)
|
||||
*/
|
||||
long delay() default 0;
|
||||
@@ -71,14 +69,12 @@ public @interface Backoff {
|
||||
* {@link #delay()} then the default of
|
||||
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
|
||||
* is applied.
|
||||
*
|
||||
* @return the maximum delay between retries (default 0 = ignored)
|
||||
*/
|
||||
long maxDelay() default 0;
|
||||
|
||||
/**
|
||||
* If positive, then used as a multiplier for generating the next delay for backoff.
|
||||
*
|
||||
* @return a multiplier to use to calculate the next backoff delay (default 0 =
|
||||
* ignored)
|
||||
*/
|
||||
@@ -98,7 +94,6 @@ public @interface Backoff {
|
||||
* If less than the {@link #delay()} then the default of
|
||||
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
|
||||
* is applied. Overrides {@link #maxDelay()}
|
||||
*
|
||||
* @return the maximum delay between retries (default 0 = ignored)
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -107,7 +102,6 @@ public @interface Backoff {
|
||||
/**
|
||||
* Evaluates to a vaule used as a multiplier for generating the next delay for
|
||||
* backoff. Overrides {@link #multiplier()}.
|
||||
*
|
||||
* @return a multiplier expression to use to calculate the next backoff delay (default
|
||||
* 0 = ignored)
|
||||
* @since 1.2
|
||||
@@ -118,7 +112,6 @@ public @interface Backoff {
|
||||
* In the exponential case ({@link #multiplier()} > 0) set this to true to have the
|
||||
* backoff delays randomized, so that the maximum delay is multiplier times the
|
||||
* previous delay and the distribution is uniform between the two values.
|
||||
*
|
||||
* @return the flag to signal randomization is required (default false)
|
||||
*/
|
||||
boolean random() default false;
|
||||
|
||||
@@ -53,8 +53,8 @@ public @interface CircuitBreaker {
|
||||
|
||||
/**
|
||||
* Exception types that are not retryable. Defaults to empty (and if includes is also
|
||||
* empty all exceptions are retried).
|
||||
* If includes is empty but excludes is not, all not excluded exceptions are retried
|
||||
* empty all exceptions are retried). If includes is empty but excludes is not, all
|
||||
* not excluded exceptions are retried
|
||||
* @return exception types not to retry
|
||||
*/
|
||||
Class<? extends Throwable>[] exclude() default {};
|
||||
@@ -74,7 +74,6 @@ public @interface CircuitBreaker {
|
||||
/**
|
||||
* A unique label for the circuit for reporting and state management. Defaults to the
|
||||
* method signature where the annotation is declared.
|
||||
*
|
||||
* @return the label for the circuit
|
||||
*/
|
||||
String label() default "";
|
||||
@@ -82,7 +81,6 @@ public @interface CircuitBreaker {
|
||||
/**
|
||||
* If the circuit is open for longer than this timeout then it resets on the next call
|
||||
* to give the downstream component a chance to respond again.
|
||||
*
|
||||
* @return the timeout before an open circuit is reset in milliseconds, defaults to
|
||||
* 20000
|
||||
*/
|
||||
@@ -92,7 +90,6 @@ public @interface CircuitBreaker {
|
||||
* If the circuit is open for longer than this timeout then it resets on the next call
|
||||
* to give the downstream component a chance to respond again. Overrides
|
||||
* {@link #resetTimeout()}.
|
||||
*
|
||||
* @return the timeout before an open circuit is reset in milliseconds, no default.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
@@ -101,7 +98,6 @@ public @interface CircuitBreaker {
|
||||
/**
|
||||
* When {@link #maxAttempts()} failures are reached within this timeout, the circuit
|
||||
* is opened automatically, preventing access to the downstream component.
|
||||
*
|
||||
* @return the timeout before an closed circuit is opened in milliseconds, defaults to
|
||||
* 5000
|
||||
*/
|
||||
@@ -111,7 +107,6 @@ public @interface CircuitBreaker {
|
||||
* When {@link #maxAttempts()} failures are reached within this timeout, the circuit
|
||||
* is opened automatically, preventing access to the downstream component. Overrides
|
||||
* {@link #openTimeout()}.
|
||||
*
|
||||
* @return the timeout before an closed circuit is opened in milliseconds, no default.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
@@ -133,7 +128,6 @@ public @interface CircuitBreaker {
|
||||
* <pre class=code>
|
||||
* {@code "@someBean.shouldRetry(#root)"}.
|
||||
* </pre>
|
||||
*
|
||||
* @return the expression.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.context.annotation.Import;
|
||||
* declared on any <code>@Configuration</code> in the context then beans that have
|
||||
* retryable methods will be proxied and the retry handled according to the metadata in
|
||||
* the annotations.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*
|
||||
@@ -43,9 +43,8 @@ import org.springframework.context.annotation.Import;
|
||||
public @interface EnableRetry {
|
||||
|
||||
/**
|
||||
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
|
||||
* to standard Java interface-based proxies. The default is {@code false}.
|
||||
*
|
||||
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
|
||||
* standard Java interface-based proxies. The default is {@code false}.
|
||||
* @return whether to proxy or not to proxy the class
|
||||
*/
|
||||
boolean proxyTargetClass() default false;
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.springframework.context.annotation.Import;
|
||||
* The Throwable first argument is optional (but a method without it will only be called
|
||||
* if no others match). Subsequent arguments are populated from the argument list of the
|
||||
* failed method in order.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*
|
||||
@@ -41,4 +41,5 @@ import org.springframework.context.annotation.Import;
|
||||
@Import(RetryConfiguration.class)
|
||||
@Documented
|
||||
public @interface Recover {
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
|
||||
|
||||
private SubclassClassifier<Throwable, Method> classifier = new SubclassClassifier<Throwable, Method>();
|
||||
|
||||
private Map<Method, SimpleMetadata> methods = new HashMap<Method, SimpleMetadata>();
|
||||
|
||||
private Object target;
|
||||
|
||||
public RecoverAnnotationRecoveryHandler(Object target, Method method) {
|
||||
@@ -92,8 +94,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
result = method;
|
||||
}
|
||||
else if (distance == min) {
|
||||
boolean parametersMatch = compareParameters( args, meta.getArgCount(),
|
||||
method.getParameterTypes() );
|
||||
boolean parametersMatch = compareParameters(args, meta.getArgCount(),
|
||||
method.getParameterTypes());
|
||||
if (parametersMatch) {
|
||||
result = method;
|
||||
}
|
||||
@@ -114,14 +116,16 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes) {
|
||||
private boolean compareParameters(Object[] args, int argCount,
|
||||
Class<?>[] parameterTypes) {
|
||||
if (argCount == (args.length + 1)) {
|
||||
int startingIndex = 0;
|
||||
if (parameterTypes.length > 0 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
|
||||
if (parameterTypes.length > 0
|
||||
&& Throwable.class.isAssignableFrom(parameterTypes[0])) {
|
||||
startingIndex = 1;
|
||||
}
|
||||
for (int i = startingIndex; i < parameterTypes.length; i++) {
|
||||
final Object argument = args[i-1];
|
||||
final Object argument = args[i - 1];
|
||||
if (argument == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -140,26 +144,25 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(),
|
||||
new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
Recover recover = AnnotationUtils.findAnnotation(method,
|
||||
Recover.class);
|
||||
if (recover != null
|
||||
&& method.getReturnType().isAssignableFrom(
|
||||
failingMethod.getReturnType())) {
|
||||
if (recover != null && method.getReturnType()
|
||||
.isAssignableFrom(failingMethod.getReturnType())) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length > 0
|
||||
&& Throwable.class
|
||||
.isAssignableFrom(parameterTypes[0])) {
|
||||
if (parameterTypes.length > 0 && Throwable.class
|
||||
.isAssignableFrom(parameterTypes[0])) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable> type = (Class<? extends Throwable>) parameterTypes[0];
|
||||
types.put(type, method);
|
||||
methods.put(method, new SimpleMetadata(
|
||||
parameterTypes.length, type));
|
||||
} else {
|
||||
methods.put(method,
|
||||
new SimpleMetadata(parameterTypes.length, type));
|
||||
}
|
||||
else {
|
||||
classifier.setDefaultValue(method);
|
||||
methods.put(method, new SimpleMetadata(
|
||||
parameterTypes.length, null));
|
||||
methods.put(method,
|
||||
new SimpleMetadata(parameterTypes.length, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +184,9 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
}
|
||||
|
||||
private static class SimpleMetadata {
|
||||
|
||||
private int argCount;
|
||||
|
||||
private Class<? extends Throwable> type;
|
||||
|
||||
public SimpleMetadata(int argCount, Class<? extends Throwable> type) {
|
||||
@@ -208,6 +213,7 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
System.arraycopy(args, 0, result, startArgs, result.length - startArgs);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* Basic configuration for <code>@Retryable</code> processing. For stateful retry, if there is a unique bean elsewhere
|
||||
* in the context of type {@link RetryContextCache}, {@link MethodArgumentsKeyGenerator} or
|
||||
* {@link NewMethodArgumentsIdentifier} it will be used by the corresponding retry interceptor (otherwise sensible
|
||||
* defaults are adopted).
|
||||
* Basic configuration for <code>@Retryable</code> processing. For stateful retry, if
|
||||
* there is a unique bean elsewhere in the context of type {@link RetryContextCache},
|
||||
* {@link MethodArgumentsKeyGenerator} or {@link NewMethodArgumentsIdentifier} it will be
|
||||
* used by the corresponding retry interceptor (otherwise sensible defaults are adopted).
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
@@ -62,7 +62,8 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@Configuration
|
||||
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware {
|
||||
public class RetryConfiguration extends AbstractPointcutAdvisor
|
||||
implements IntroductionAdvisor, BeanFactoryAware {
|
||||
|
||||
private Advice advice;
|
||||
|
||||
@@ -87,7 +88,8 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
|
||||
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(
|
||||
1);
|
||||
retryableAnnotationTypes.add(Retryable.class);
|
||||
this.pointcut = buildPointcut(retryableAnnotationTypes);
|
||||
this.advice = buildAdvice();
|
||||
@@ -150,11 +152,11 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
|
||||
/**
|
||||
* Calculate a pointcut for the given retry annotation types, if any.
|
||||
*
|
||||
* @param retryAnnotationTypes the retry annotation types to introspect
|
||||
* @return the applicable Pointcut object, or {@code null} if none
|
||||
*/
|
||||
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
|
||||
protected Pointcut buildPointcut(
|
||||
Set<Class<? extends Annotation>> retryAnnotationTypes) {
|
||||
ComposablePointcut result = null;
|
||||
for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
|
||||
Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
|
||||
@@ -168,7 +170,8 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
return result;
|
||||
}
|
||||
|
||||
private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut {
|
||||
private final class AnnotationClassOrMethodPointcut
|
||||
extends StaticMethodMatcherPointcut {
|
||||
|
||||
private final MethodMatcher methodResolver;
|
||||
|
||||
@@ -179,9 +182,10 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass);
|
||||
return getClassFilter().matches(targetClass)
|
||||
|| this.methodResolver.matches(method, targetClass);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
@@ -191,7 +195,8 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
return false;
|
||||
}
|
||||
AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other;
|
||||
return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
|
||||
return ObjectUtils.nullSafeEquals(this.methodResolver,
|
||||
otherAdvisor.methodResolver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -213,31 +218,32 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements Intro
|
||||
}
|
||||
|
||||
private static class AnnotationMethodsResolver {
|
||||
|
||||
|
||||
private Class<? extends Annotation> annotationType;
|
||||
|
||||
|
||||
public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) {
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAnnotatedMethods(Class<?> clazz) {
|
||||
final AtomicBoolean found = new AtomicBoolean(false);
|
||||
ReflectionUtils.doWithMethods(clazz,
|
||||
new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
if (found.get()) {
|
||||
return;
|
||||
}
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method,
|
||||
annotationType);
|
||||
if (annotation != null) { found.set(true); }
|
||||
}
|
||||
ReflectionUtils.doWithMethods(clazz, new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
if (found.get()) {
|
||||
return;
|
||||
}
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method,
|
||||
annotationType);
|
||||
if (annotation != null) {
|
||||
found.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
return found.get();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ public @interface Retryable {
|
||||
|
||||
/**
|
||||
* Exception types that are not retryable. Defaults to empty (and if includes is also
|
||||
* empty all exceptions are retried).
|
||||
* If includes is empty but excludes is not, all not excluded exceptions are retried
|
||||
* empty all exceptions are retried). If includes is empty but excludes is not, all
|
||||
* not excluded exceptions are retried
|
||||
* @return exception types not to retry
|
||||
*/
|
||||
Class<? extends Throwable>[] exclude() default {};
|
||||
@@ -68,7 +68,6 @@ public @interface Retryable {
|
||||
/**
|
||||
* A unique label for statistics reporting. If not provided the caller may choose to
|
||||
* ignore it, or provide a default.
|
||||
*
|
||||
* @return the label for the statistics
|
||||
*/
|
||||
String label() default "";
|
||||
@@ -87,31 +86,28 @@ public @interface Retryable {
|
||||
int maxAttempts() default 3;
|
||||
|
||||
/**
|
||||
* @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
|
||||
* Overrides {@link #maxAttempts()}.
|
||||
* @return an expression evaluated to the maximum number of attempts (including the
|
||||
* first failure), defaults to 3 Overrides {@link #maxAttempts()}.
|
||||
* @since 1.2
|
||||
*/
|
||||
String maxAttemptsExpression() default "";
|
||||
|
||||
/**
|
||||
* Specify the backoff properties for retrying this operation. The default is a
|
||||
* simple {@link Backoff} specification with no properties - see it's documentation
|
||||
* for defaults.
|
||||
* Specify the backoff properties for retrying this operation. The default is a simple
|
||||
* {@link Backoff} specification with no properties - see it's documentation for
|
||||
* defaults.
|
||||
* @return a backoff specification
|
||||
*/
|
||||
Backoff backoff() default @Backoff();
|
||||
|
||||
/**
|
||||
* Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
|
||||
* returns true - can be used to conditionally suppress the retry. Only invoked after
|
||||
* an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
|
||||
* Other beans in the context can be referenced.
|
||||
* For example:
|
||||
* <pre class=code>
|
||||
* Specify an expression to be evaluated after the
|
||||
* {@code SimpleRetryPolicy.canRetry()} returns true - can be used to conditionally
|
||||
* suppress the retry. Only invoked after an exception is thrown. The root object for
|
||||
* the evaluation is the last {@code Throwable}. Other beans in the context can be
|
||||
* referenced. For example: <pre class=code>
|
||||
* {@code "message.contains('you can retry this')"}.
|
||||
* </pre>
|
||||
* and
|
||||
* <pre class=code>
|
||||
* </pre> and <pre class=code>
|
||||
* {@code "@someBean.shouldRetry(#root)"}.
|
||||
* </pre>
|
||||
* @return the expression.
|
||||
@@ -120,7 +116,8 @@ public @interface Retryable {
|
||||
String exceptionExpression() default "";
|
||||
|
||||
/**
|
||||
* Bean names of retry listeners to use instead of default ones defined in Spring context
|
||||
* Bean names of retry listeners to use instead of default ones defined in Spring
|
||||
* context
|
||||
* @return retry listeners bean names
|
||||
*/
|
||||
String[] listeners() default {};
|
||||
|
||||
@@ -19,10 +19,10 @@ package org.springframework.retry.backoff;
|
||||
import org.springframework.retry.RetryException;
|
||||
|
||||
/**
|
||||
* Exception class signifiying that an attempt to back off using a
|
||||
* {@link BackOffPolicy} was interrupted, most likely by an
|
||||
* {@link InterruptedException} during a call to {@link Thread#sleep(long)}.
|
||||
*
|
||||
* Exception class signifiying that an attempt to back off using a {@link BackOffPolicy}
|
||||
* was interrupted, most likely by an {@link InterruptedException} during a call to
|
||||
* {@link Thread#sleep(long)}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @since 2.1
|
||||
*/
|
||||
@@ -36,4 +36,5 @@ public class BackOffInterruptedException extends RetryException {
|
||||
public BackOffInterruptedException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,28 +22,27 @@ import org.springframework.retry.RetryContext;
|
||||
* Strategy interface to control back off between attempts in a single
|
||||
* {@link org.springframework.retry.support.RetryTemplate retry operation}.
|
||||
*
|
||||
* Implementations are expected to be thread-safe and should be designed
|
||||
* for concurrent access. Configuration for each implementation is also expected
|
||||
* to be thread-safe but need not be suitable for high load concurrent access.
|
||||
* Implementations are expected to be thread-safe and should be designed for concurrent
|
||||
* access. Configuration for each implementation is also expected to be thread-safe but
|
||||
* need not be suitable for high load concurrent access.
|
||||
*
|
||||
* For each block of retry operations the {@link #start} method is called
|
||||
* and implementations can return an implementation-specific
|
||||
* For each block of retry operations the {@link #start} method is called and
|
||||
* implementations can return an implementation-specific
|
||||
*
|
||||
* {@link BackOffContext} that can be used to track state through subsequent
|
||||
* back off invocations. Each back off process is handled via a call to {@link #backOff}.
|
||||
* {@link BackOffContext} that can be used to track state through subsequent back off
|
||||
* invocations. Each back off process is handled via a call to {@link #backOff}.
|
||||
*
|
||||
* The {@link org.springframework.retry.support.RetryTemplate} will pass in the
|
||||
* corresponding {@link BackOffContext} object created by the call to {@link #start}.
|
||||
*
|
||||
* The {@link org.springframework.retry.support.RetryTemplate} will pass in
|
||||
* the corresponding {@link BackOffContext} object created by the call to {@link #start}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface BackOffPolicy {
|
||||
|
||||
/**
|
||||
* Start a new block of back off operations. Implementations can choose to
|
||||
* pause when this method is called, but normally it returns immediately.
|
||||
*
|
||||
* Start a new block of back off operations. Implementations can choose to pause when
|
||||
* this method is called, but normally it returns immediately.
|
||||
* @param context the {@link RetryContext} context, which might contain information
|
||||
* that we can use to decide how to proceed.
|
||||
* @return the implementation-specific {@link BackOffContext} or '<code>null</code>'.
|
||||
@@ -52,11 +51,9 @@ public interface BackOffPolicy {
|
||||
|
||||
/**
|
||||
* Back off/pause in an implementation-specific fashion. The passed in
|
||||
* {@link BackOffContext} corresponds to the one created by the call to
|
||||
* {@link #start} for a given retry operation set.
|
||||
*
|
||||
* @throws BackOffInterruptedException if the attempt at back off is
|
||||
* interrupted.
|
||||
* {@link BackOffContext} corresponds to the one created by the call to {@link #start}
|
||||
* for a given retry operation set.
|
||||
* @throws BackOffInterruptedException if the attempt at back off is interrupted.
|
||||
* @param backOffContext the {@link BackOffContext}
|
||||
*/
|
||||
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ExponentialBackOffPolicy
|
||||
implements SleepingBackOffPolicy<ExponentialBackOffPolicy> {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
/**
|
||||
* The default 'initialInterval' value - 100 millisecs. Coupled with the default
|
||||
* 'multiplier' value this gives a useful initial spread of pauses for 1-5 retries.
|
||||
@@ -104,7 +105,6 @@ public class ExponentialBackOffPolicy
|
||||
/**
|
||||
* Set the initial sleep interval value. Default is {@code 100} millisecond. Cannot be
|
||||
* set to a value less than one.
|
||||
*
|
||||
* @param initialInterval the initial interval
|
||||
*/
|
||||
public void setInitialInterval(long initialInterval) {
|
||||
@@ -125,7 +125,6 @@ public class ExponentialBackOffPolicy
|
||||
* be reset to 1 if this method is called with a value less than 1. Set this to avoid
|
||||
* infinite waits if backing off a large number of times (or if the multiplier is set
|
||||
* too high).
|
||||
*
|
||||
* @param maxInterval in milliseconds.
|
||||
*/
|
||||
public void setMaxInterval(long maxInterval) {
|
||||
@@ -142,7 +141,6 @@ public class ExponentialBackOffPolicy
|
||||
|
||||
/**
|
||||
* The maximum interval to sleep for. Defaults to 30 seconds.
|
||||
*
|
||||
* @return the maximum interval.
|
||||
*/
|
||||
public long getMaxInterval() {
|
||||
@@ -151,7 +149,6 @@ public class ExponentialBackOffPolicy
|
||||
|
||||
/**
|
||||
* The multiplier to use to generate the next backoff interval from the last.
|
||||
*
|
||||
* @return the multiplier in use
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
@@ -226,6 +223,7 @@ public class ExponentialBackOffPolicy
|
||||
public long getMaxInterval() {
|
||||
return maxInterval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -38,11 +38,13 @@ import java.util.Random;
|
||||
*
|
||||
* {@link ExponentialRandomBackOffPolicy} may yield [76, 151, 304, 580, 901] or [53, 190,
|
||||
* 267, 451, 815]
|
||||
*
|
||||
* @author Jon Travis
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link org.springframework.retry.backoff.BackOffContext},
|
||||
* seeded with this policies settings.
|
||||
@@ -58,6 +60,7 @@ public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
|
||||
static class ExponentialRandomBackOffContext
|
||||
extends ExponentialBackOffPolicy.ExponentialBackOffContext {
|
||||
|
||||
private final Random r = new Random();
|
||||
|
||||
public ExponentialRandomBackOffContext(long expSeed, double multiplier,
|
||||
@@ -73,4 +76,5 @@ public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ package org.springframework.retry.backoff;
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class FixedBackOffPolicy extends StatelessBackOffPolicy implements
|
||||
SleepingBackOffPolicy<FixedBackOffPolicy> {
|
||||
public class FixedBackOffPolicy extends StatelessBackOffPolicy
|
||||
implements SleepingBackOffPolicy<FixedBackOffPolicy> {
|
||||
|
||||
/**
|
||||
* Default back off period - 1000ms.
|
||||
@@ -90,4 +90,5 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements
|
||||
public String toString() {
|
||||
return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that performs a no-op and as such all
|
||||
* retry operation in a given set proceed one after the other with no pause.
|
||||
*
|
||||
* Implementation of {@link BackOffPolicy} that performs a no-op and as such all retry
|
||||
* operation in a given set proceed one after the other with no pause.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
@@ -17,8 +17,8 @@ package org.springframework.retry.backoff;
|
||||
|
||||
/**
|
||||
* Simple {@link Sleeper} implementation that just waits on a local Object.
|
||||
* @deprecated in favor of {@link org.springframework.retry.backoff.ThreadWaitSleeper}
|
||||
*
|
||||
* @deprecated in favor of {@link org.springframework.retry.backoff.ThreadWaitSleeper}
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@@ -28,6 +28,7 @@ public class ObjectWaitSleeper implements Sleeper {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.retry.backoff.Sleeper#sleep(long)
|
||||
*/
|
||||
public void sleep(long backOffPeriod) throws InterruptedException {
|
||||
|
||||
@@ -19,15 +19,14 @@ import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Strategy interface for backoff policies to delegate the pausing of execution.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface Sleeper extends Serializable {
|
||||
|
||||
/**
|
||||
* Pause for the specified period using whatever means available.
|
||||
*
|
||||
* @param backOffPeriod the backoff period
|
||||
* @throws InterruptedException the exception when interrupted
|
||||
*/
|
||||
|
||||
@@ -20,13 +20,15 @@ package org.springframework.retry.backoff;
|
||||
* A interface which can be mixed in by {@link BackOffPolicy}s indicating that they sleep
|
||||
* when backing off.
|
||||
*/
|
||||
public interface SleepingBackOffPolicy<T extends SleepingBackOffPolicy<T>> extends BackOffPolicy {
|
||||
/**
|
||||
* Clone the policy and return a new policy which uses the passed sleeper.
|
||||
*
|
||||
* @param sleeper Target to be invoked any time the backoff policy sleeps
|
||||
* @return a clone of this policy which will have all of its backoff sleeps
|
||||
* routed into the passed sleeper
|
||||
*/
|
||||
T withSleeper(Sleeper sleeper);
|
||||
public interface SleepingBackOffPolicy<T extends SleepingBackOffPolicy<T>>
|
||||
extends BackOffPolicy {
|
||||
|
||||
/**
|
||||
* Clone the policy and return a new policy which uses the passed sleeper.
|
||||
* @param sleeper Target to be invoked any time the backoff policy sleeps
|
||||
* @return a clone of this policy which will have all of its backoff sleeps routed
|
||||
* into the passed sleeper
|
||||
*/
|
||||
T withSleeper(Sleeper sleeper);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,26 +19,26 @@ package org.springframework.retry.backoff;
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Simple base class for {@link BackOffPolicy} implementations that maintain no
|
||||
* state across invocations.
|
||||
*
|
||||
* Simple base class for {@link BackOffPolicy} implementations that maintain no state
|
||||
* across invocations.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public abstract class StatelessBackOffPolicy implements BackOffPolicy {
|
||||
|
||||
/**
|
||||
* Delegates directly to the {@link #doBackOff()} method without passing on
|
||||
* the {@link BackOffContext} argument which is not needed for stateless
|
||||
* implementations.
|
||||
* Delegates directly to the {@link #doBackOff()} method without passing on the
|
||||
* {@link BackOffContext} argument which is not needed for stateless implementations.
|
||||
*/
|
||||
public final void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
|
||||
public final void backOff(BackOffContext backOffContext)
|
||||
throws BackOffInterruptedException {
|
||||
doBackOff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns '<code>null</code>'. Subclasses can add behaviour, e.g.
|
||||
* initial sleep before first attempt.
|
||||
* Returns '<code>null</code>'. Subclasses can add behaviour, e.g. initial sleep
|
||||
* before first attempt.
|
||||
*/
|
||||
public BackOffContext start(RetryContext status) {
|
||||
return null;
|
||||
@@ -48,4 +48,5 @@ public abstract class StatelessBackOffPolicy implements BackOffPolicy {
|
||||
* Sub-classes should implement this method to perform the actual back off.
|
||||
*/
|
||||
protected abstract void doBackOff() throws BackOffInterruptedException;
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
/**
|
||||
* Simple {@link Sleeper} implementation that just blocks the current Thread with sleep period.
|
||||
* Simple {@link Sleeper} implementation that just blocks the current Thread with sleep
|
||||
* period.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 1.1
|
||||
|
||||
@@ -18,20 +18,19 @@ package org.springframework.retry.backoff;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a random period of
|
||||
* time before continuing. A pause is implemented using {@link Sleeper#sleep(long)}.
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a random period of time before
|
||||
* continuing. A pause is implemented using {@link Sleeper#sleep(long)}.
|
||||
*
|
||||
* {@link #setMinBackOffPeriod(long)} is thread-safe and it is safe to call
|
||||
* {@link #setMaxBackOffPeriod(long)} during execution from multiple threads, however
|
||||
* this may cause a single retry operation to have pauses of different
|
||||
* intervals.
|
||||
* {@link #setMaxBackOffPeriod(long)} during execution from multiple threads, however this
|
||||
* may cause a single retry operation to have pauses of different intervals.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<UniformRandomBackOffPolicy> {
|
||||
public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
implements SleepingBackOffPolicy<UniformRandomBackOffPolicy> {
|
||||
|
||||
/**
|
||||
* Default min back off period - 500ms.
|
||||
@@ -51,12 +50,12 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implement
|
||||
|
||||
private Sleeper sleeper = new ThreadWaitSleeper();
|
||||
|
||||
public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) {
|
||||
UniformRandomBackOffPolicy res = new UniformRandomBackOffPolicy();
|
||||
res.setMinBackOffPeriod(minBackOffPeriod);
|
||||
res.setSleeper(sleeper);
|
||||
return res;
|
||||
}
|
||||
public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) {
|
||||
UniformRandomBackOffPolicy res = new UniformRandomBackOffPolicy();
|
||||
res.setMinBackOffPeriod(minBackOffPeriod);
|
||||
res.setSleeper(sleeper);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Sleeper} strategy.
|
||||
@@ -67,9 +66,8 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 500ms.
|
||||
*
|
||||
* Set the minimum back off period in milliseconds. Cannot be < 1. Default value is
|
||||
* 500ms.
|
||||
* @param backOffPeriod the backoff period
|
||||
*/
|
||||
public void setMinBackOffPeriod(long backOffPeriod) {
|
||||
@@ -85,8 +83,8 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 1500ms.
|
||||
* Set the maximum back off period in milliseconds. Cannot be < 1. Default value is
|
||||
* 1500ms.
|
||||
* @param backOffPeriod the back off period
|
||||
*/
|
||||
public void setMaxBackOffPeriod(long backOffPeriod) {
|
||||
@@ -107,15 +105,18 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implement
|
||||
*/
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
long delta = maxBackOffPeriod==minBackOffPeriod ? 0 : random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
|
||||
sleeper.sleep(minBackOffPeriod + delta );
|
||||
long delta = maxBackOffPeriod == minBackOffPeriod ? 0
|
||||
: random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
|
||||
sleeper.sleep(minBackOffPeriod + delta);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "RandomBackOffPolicy[backOffPeriod=" + minBackOffPeriod + ", " + maxBackOffPeriod + "]";
|
||||
}
|
||||
public String toString() {
|
||||
return "RandomBackOffPolicy[backOffPeriod=" + minBackOffPeriod + ", "
|
||||
+ maxBackOffPeriod + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ import org.springframework.retry.RetryPolicy;
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RetryContextSupport extends AttributeAccessorSupport implements RetryContext {
|
||||
public class RetryContextSupport extends AttributeAccessorSupport
|
||||
implements RetryContext {
|
||||
|
||||
private final RetryContext parent;
|
||||
|
||||
@@ -60,18 +61,16 @@ public class RetryContextSupport extends AttributeAccessorSupport implements Ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exception for the public interface {@link RetryContext}, and
|
||||
* also increment the retry count if the throwable is non-null.
|
||||
* Set the exception for the public interface {@link RetryContext}, and also increment
|
||||
* the retry count if the throwable is non-null.
|
||||
*
|
||||
* All {@link RetryPolicy} implementations should use this method when they
|
||||
* register the throwable. It should only be called once per retry attempt
|
||||
* because it increments a counter.
|
||||
* All {@link RetryPolicy} implementations should use this method when they register
|
||||
* the throwable. It should only be called once per retry attempt because it
|
||||
* increments a counter.
|
||||
*
|
||||
* Use of this method is not enforced by the framework - it is a service
|
||||
* provider contract for authors of policies.
|
||||
*
|
||||
* @param throwable the exception that caused the current retry attempt to
|
||||
* fail.
|
||||
* Use of this method is not enforced by the framework - it is a service provider
|
||||
* contract for authors of policies.
|
||||
* @param throwable the exception that caused the current retry attempt to fail.
|
||||
*/
|
||||
public void registerThrowable(Throwable throwable) {
|
||||
this.lastException = throwable;
|
||||
@@ -81,7 +80,8 @@ public class RetryContextSupport extends AttributeAccessorSupport implements Ret
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[RetryContext: count=%d, lastException=%s, exhausted=%b]", count, lastException, terminate);
|
||||
return String.format("[RetryContext: count=%d, lastException=%s, exhausted=%b]",
|
||||
count, lastException, terminate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
package org.springframework.retry.interceptor;
|
||||
|
||||
/**
|
||||
* Interface that allows method parameters to be identified and tagged by a
|
||||
* unique key.
|
||||
* Interface that allows method parameters to be identified and tagged by a unique key.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -25,9 +24,8 @@ package org.springframework.retry.interceptor;
|
||||
public interface MethodArgumentsKeyGenerator {
|
||||
|
||||
/**
|
||||
* Get a unique identifier for the item that can be used to cache it between
|
||||
* calls if necessary, and then identify it later.
|
||||
*
|
||||
* Get a unique identifier for the item that can be used to cache it between calls if
|
||||
* necessary, and then identify it later.
|
||||
* @param item the current method arguments (may be null if there are none).
|
||||
* @return a unique identifier.
|
||||
*/
|
||||
|
||||
@@ -16,25 +16,21 @@
|
||||
|
||||
package org.springframework.retry.interceptor;
|
||||
|
||||
|
||||
/**
|
||||
* Strategy interface for recovery action when processing of an item fails.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface MethodInvocationRecoverer<T> {
|
||||
|
||||
/**
|
||||
* Recover gracefully from an error. Clients can call this if processing of
|
||||
* the item throws an unexpected exception. Caller can use the return value
|
||||
* to decide whether to try more corrective action or perhaps throw an
|
||||
* exception.
|
||||
*
|
||||
* @param args
|
||||
* the arguments for the method invocation that failed.
|
||||
* @param cause
|
||||
* the cause of the failure that led to this recovery.
|
||||
* Recover gracefully from an error. Clients can call this if processing of the item
|
||||
* throws an unexpected exception. Caller can use the return value to decide whether
|
||||
* to try more corrective action or perhaps throw an exception.
|
||||
* @param args the arguments for the method invocation that failed.
|
||||
* @param cause the cause of the failure that led to this recovery.
|
||||
* @return the value to be returned to the caller
|
||||
*/
|
||||
T recover(Object[] args, Throwable cause);
|
||||
|
||||
}
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
package org.springframework.retry.interceptor;
|
||||
|
||||
/**
|
||||
* Strategy interface to distinguish new arguments from ones that have been
|
||||
* processed before, e.g. by examining a message flag.
|
||||
*
|
||||
* Strategy interface to distinguish new arguments from ones that have been processed
|
||||
* before, e.g. by examining a message flag.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface NewMethodArgumentsIdentifier {
|
||||
|
||||
/**
|
||||
* Inspect the arguments and determine if they have never been processed
|
||||
* before. The safest choice when the answer is indeterminate is 'false'.
|
||||
*
|
||||
* Inspect the arguments and determine if they have never been processed before. The
|
||||
* safest choice when the answer is indeterminate is 'false'.
|
||||
* @param args the current method arguments.
|
||||
* @return true if the item is known to have never been processed before.
|
||||
*/
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.springframework.util.Assert;
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @since 1.1
|
||||
*
|
||||
* @param <T> The type of {@link org.aopalliance.intercept.MethodInterceptor} returned by
|
||||
* the builder's {@link #build()} method.
|
||||
*/
|
||||
@@ -109,8 +108,8 @@ public abstract class RetryInterceptorBuilder<T extends MethodInterceptor> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the max attempts - a SimpleRetryPolicy will be used. Cannot be used if a custom retry operations
|
||||
* or retry policy has been set.
|
||||
* Apply the max attempts - a SimpleRetryPolicy will be used. Cannot be used if a
|
||||
* custom retry operations or retry policy has been set.
|
||||
* @param maxAttempts the max attempts (including the initial attempt).
|
||||
* @return this.
|
||||
*/
|
||||
@@ -345,7 +344,8 @@ public abstract class RetryInterceptorBuilder<T extends MethodInterceptor> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CircuitBreakerInterceptorBuilder keyGenerator(MethodArgumentsKeyGenerator keyGenerator) {
|
||||
public CircuitBreakerInterceptorBuilder keyGenerator(
|
||||
MethodArgumentsKeyGenerator keyGenerator) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
String name;
|
||||
if (StringUtils.hasText(label)) {
|
||||
name = label;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
name = invocation.getMethod().toGenericString();
|
||||
}
|
||||
final String label = name;
|
||||
@@ -77,7 +78,7 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
|
||||
|
||||
context.setAttribute(RetryContext.NAME, label);
|
||||
|
||||
/*
|
||||
@@ -88,7 +89,8 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
*/
|
||||
if (invocation instanceof ProxyMethodInvocation) {
|
||||
try {
|
||||
return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
|
||||
return ((ProxyMethodInvocation) invocation).invocableClone()
|
||||
.proceed();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
@@ -102,8 +104,8 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +
|
||||
"so please raise an issue if you see this exception");
|
||||
"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, "
|
||||
+ "so please raise an issue if you see this exception");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +134,8 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
/**
|
||||
* @param args the item that failed.
|
||||
*/
|
||||
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<?> recoverer) {
|
||||
private ItemRecovererCallback(Object[] args,
|
||||
MethodInvocationRecoverer<?> recoverer) {
|
||||
this.args = Arrays.asList(args).toArray();
|
||||
this.recoverer = recoverer;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,6 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
/**
|
||||
* Rollback classifier for the retry state. Default to null (meaning rollback for
|
||||
* all).
|
||||
*
|
||||
* @param rollbackClassifier the rollbackClassifier to set
|
||||
*/
|
||||
public void setRollbackClassifier(
|
||||
@@ -128,8 +127,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
/**
|
||||
* Set to true to use the raw key generated by the key generator. Should only be set
|
||||
* to true for cases where the key is guaranteed to be unique in all cases. When
|
||||
* false, a compound key is used, including invocation metadata.
|
||||
* Default: false.
|
||||
* false, a compound key is used, including invocation metadata. Default: false.
|
||||
* @param useRawKey the useRawKey to set.
|
||||
*/
|
||||
public void setUseRawKey(boolean useRawKey) {
|
||||
@@ -210,6 +208,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
implements RetryCallback<Object, Throwable> {
|
||||
|
||||
private final MethodInvocation invocation;
|
||||
|
||||
private String label;
|
||||
|
||||
private MethodInvocationRetryCallback(MethodInvocation invocation, String label) {
|
||||
@@ -238,6 +237,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,19 +22,22 @@ import org.springframework.retry.RetryListener;
|
||||
|
||||
/**
|
||||
* Empty method implementation of {@link RetryListener}.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RetryListenerSupport implements RetryListener {
|
||||
|
||||
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
|
||||
public <T, E extends Throwable> void close(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
|
||||
public <T, E extends Throwable> void onError(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
|
||||
public <T, E extends Throwable> boolean open(RetryContext context,
|
||||
RetryCallback<T, E> callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,18 +20,18 @@ import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that always permits a retry. Can also be used as a base
|
||||
* class for other policies, e.g. for test purposes as a stub.
|
||||
*
|
||||
* A {@link RetryPolicy} that always permits a retry. Can also be used as a base class for
|
||||
* other policies, e.g. for test purposes as a stub.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AlwaysRetryPolicy extends NeverRetryPolicy {
|
||||
|
||||
/**
|
||||
* Always returns true.
|
||||
*
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
|
||||
@@ -20,12 +20,11 @@ import org.springframework.classify.BinaryExceptionClassifier;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.context.RetryContextSupport;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A policy, that is based on {@link BinaryExceptionClassifier}.
|
||||
* Usually, binary classification is enough for retry purposes. If you need more flexible classification, use
|
||||
* {@link ExceptionClassifierRetryPolicy}.
|
||||
* A policy, that is based on {@link BinaryExceptionClassifier}. Usually, binary
|
||||
* classification is enough for retry purposes. If you need more flexible classification,
|
||||
* use {@link ExceptionClassifierRetryPolicy}.
|
||||
*
|
||||
* @author Aleksandr Shamukov
|
||||
*/
|
||||
@@ -34,7 +33,8 @@ public class BinaryExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
private final BinaryExceptionClassifier exceptionClassifier;
|
||||
|
||||
public BinaryExceptionClassifierRetryPolicy(BinaryExceptionClassifier exceptionClassifier) {
|
||||
public BinaryExceptionClassifierRetryPolicy(
|
||||
BinaryExceptionClassifier exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
@@ -62,4 +62,5 @@ public class BinaryExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new RetryContextSupport(parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
private static Log logger = LogFactory.getLog(CircuitBreakerRetryPolicy.class);
|
||||
|
||||
private final RetryPolicy delegate;
|
||||
|
||||
private long resetTimeout = 20000;
|
||||
|
||||
private long openTimeout = 5000;
|
||||
|
||||
public CircuitBreakerRetryPolicy() {
|
||||
@@ -53,7 +55,6 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
/**
|
||||
* Timeout for resetting circuit in milliseconds. After the circuit opens it will
|
||||
* re-close after this time has elapsed and the context will be restarted.
|
||||
*
|
||||
* @param timeout the timeout to set in milliseconds
|
||||
*/
|
||||
public void setResetTimeout(long timeout) {
|
||||
@@ -64,7 +65,6 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
* Timeout for tripping the open circuit. If the delegate policy cannot retry and the
|
||||
* time elapsed since the context was started is less than this window, then the
|
||||
* circuit is opened.
|
||||
*
|
||||
* @param timeout the timeout to set in milliseconds
|
||||
*/
|
||||
public void setOpenTimeout(long timeout) {
|
||||
@@ -104,11 +104,17 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
static class CircuitBreakerRetryContext extends RetryContextSupport {
|
||||
|
||||
private volatile RetryContext context;
|
||||
|
||||
private final RetryPolicy policy;
|
||||
|
||||
private volatile long start = System.currentTimeMillis();
|
||||
|
||||
private final long timeout;
|
||||
|
||||
private final long openWindow;
|
||||
|
||||
private final AtomicInteger shortCircuitCount = new AtomicInteger();
|
||||
|
||||
public CircuitBreakerRetryContext(RetryContext parent, RetryPolicy policy,
|
||||
|
||||
@@ -25,8 +25,8 @@ import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.context.RetryContextSupport;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that composes a list of other policies and delegates
|
||||
* calls to them in order.
|
||||
* A {@link RetryPolicy} that composes a list of other policies and delegates calls to
|
||||
* them in order.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Michael Minella
|
||||
@@ -36,11 +36,11 @@ import org.springframework.retry.context.RetryContextSupport;
|
||||
public class CompositeRetryPolicy implements RetryPolicy {
|
||||
|
||||
RetryPolicy[] policies = new RetryPolicy[0];
|
||||
|
||||
private boolean optimistic = false;
|
||||
|
||||
/**
|
||||
* Setter for optimistic.
|
||||
*
|
||||
* @param optimistic should this retry policy be optimistic
|
||||
*/
|
||||
public void setOptimistic(boolean optimistic) {
|
||||
@@ -49,18 +49,15 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* Setter for policies.
|
||||
*
|
||||
* @param policies the {@link RetryPolicy} policies
|
||||
* @param policies the {@link RetryPolicy} policies
|
||||
*/
|
||||
public void setPolicies(RetryPolicy[] policies) {
|
||||
this.policies = Arrays.asList(policies).toArray(new RetryPolicy[policies.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policies that were in operation when the context was
|
||||
* created. If any of them cannot retry then return false, otherwise return
|
||||
* true.
|
||||
*
|
||||
* Delegate to the policies that were in operation when the context was created. If
|
||||
* any of them cannot retry then return false, otherwise return true.
|
||||
* @param context the {@link RetryContext}
|
||||
* @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
@@ -71,7 +68,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
|
||||
boolean retryable = true;
|
||||
|
||||
if(this.optimistic) {
|
||||
if (this.optimistic) {
|
||||
retryable = false;
|
||||
for (int i = 0; i < contexts.length; i++) {
|
||||
if (policies[i].canRetry(contexts[i])) {
|
||||
@@ -91,9 +88,9 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policies that were in operation when the context was
|
||||
* created. If any of them fails to close the exception is propagated (and
|
||||
* those later in the chain are closed before re-throwing).
|
||||
* Delegate to the policies that were in operation when the context was created. If
|
||||
* any of them fails to close the exception is propagated (and those later in the
|
||||
* chain are closed before re-throwing).
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
|
||||
* @param context the {@link RetryContext}
|
||||
@@ -119,8 +116,8 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context that copies the existing policies and keeps a list
|
||||
* of the contexts from each one.
|
||||
* Creates a new context that copies the existing policies and keeps a list of the
|
||||
* contexts from each one.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
@@ -134,8 +131,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policies that were in operation when the context was
|
||||
* created.
|
||||
* Delegate to the policies that were in operation when the context was created.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
@@ -150,11 +146,13 @@ public class CompositeRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
private static class CompositeRetryContext extends RetryContextSupport {
|
||||
|
||||
RetryContext[] contexts;
|
||||
|
||||
RetryPolicy[] policies;
|
||||
|
||||
public CompositeRetryContext(RetryContext parent, List<RetryContext> contexts, RetryPolicy[] policies) {
|
||||
public CompositeRetryContext(RetryContext parent, List<RetryContext> contexts,
|
||||
RetryPolicy[] policies) {
|
||||
super(parent);
|
||||
this.contexts = contexts.toArray(new RetryContext[contexts.size()]);
|
||||
this.policies = policies;
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.springframework.retry.context.RetryContextSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that dynamically adapts to one of a set of injected
|
||||
* policies according to the value of the latest exception.
|
||||
* A {@link RetryPolicy} that dynamically adapts to one of a set of injected policies
|
||||
* according to the value of the latest exception.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -37,27 +37,28 @@ import org.springframework.util.Assert;
|
||||
@SuppressWarnings("serial")
|
||||
public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
private Classifier<Throwable, RetryPolicy> exceptionClassifier = new ClassifierSupport<Throwable, RetryPolicy>(new NeverRetryPolicy());
|
||||
private Classifier<Throwable, RetryPolicy> exceptionClassifier = new ClassifierSupport<Throwable, RetryPolicy>(
|
||||
new NeverRetryPolicy());
|
||||
|
||||
/**
|
||||
* Setter for policy map used to create a classifier. Either this property
|
||||
* or the exception classifier directly should be set, but not both.
|
||||
*
|
||||
* @param policyMap a map of Throwable class to {@link RetryPolicy} that
|
||||
* will be used to create a {@link Classifier} to locate a policy.
|
||||
* Setter for policy map used to create a classifier. Either this property or the
|
||||
* exception classifier directly should be set, but not both.
|
||||
* @param policyMap a map of Throwable class to {@link RetryPolicy} that will be used
|
||||
* to create a {@link Classifier} to locate a policy.
|
||||
*/
|
||||
public void setPolicyMap(Map<Class<? extends Throwable>, RetryPolicy> policyMap) {
|
||||
this.exceptionClassifier = new SubclassClassifier<Throwable, RetryPolicy>(policyMap, new NeverRetryPolicy());
|
||||
this.exceptionClassifier = new SubclassClassifier<Throwable, RetryPolicy>(
|
||||
policyMap, new NeverRetryPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for an exception classifier. The classifier is responsible for
|
||||
* translating exceptions to concrete retry policies. Either this property
|
||||
* or the policy map should be used, but not both.
|
||||
*
|
||||
* Setter for an exception classifier. The classifier is responsible for translating
|
||||
* exceptions to concrete retry policies. Either this property or the policy map
|
||||
* should be used, but not both.
|
||||
* @param exceptionClassifier ExceptionClassifier to use
|
||||
*/
|
||||
public void setExceptionClassifier(Classifier<Throwable, RetryPolicy> exceptionClassifier) {
|
||||
public void setExceptionClassifier(
|
||||
Classifier<Throwable, RetryPolicy> exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
@@ -82,13 +83,14 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an active context that proxies a retry policy by choosing a target
|
||||
* from the policy map.
|
||||
* Create an active context that proxies a retry policy by choosing a target from the
|
||||
* policy map.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new ExceptionClassifierRetryContext(parent, exceptionClassifier).open(parent);
|
||||
return new ExceptionClassifierRetryContext(parent, exceptionClassifier)
|
||||
.open(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +105,8 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
}
|
||||
|
||||
private static class ExceptionClassifierRetryContext extends RetryContextSupport implements RetryPolicy {
|
||||
private static class ExceptionClassifierRetryContext extends RetryContextSupport
|
||||
implements RetryPolicy {
|
||||
|
||||
final private Classifier<Throwable, RetryPolicy> exceptionClassifier;
|
||||
|
||||
@@ -138,7 +141,8 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
policy = exceptionClassifier.classify(throwable);
|
||||
Assert.notNull(policy, "Could not locate policy for exception=[" + throwable + "].");
|
||||
Assert.notNull(policy,
|
||||
"Could not locate policy for exception=[" + throwable + "].");
|
||||
this.context = getContext(policy, context.getParent());
|
||||
policy.registerThrowable(this.context, throwable);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import org.springframework.retry.RetryContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Subclass of {@link SimpleRetryPolicy} that delegates to super.canRetry() and,
|
||||
* if true, further evaluates an expression against the last thrown exception.
|
||||
* Subclass of {@link SimpleRetryPolicy} that delegates to super.canRetry() and, if true,
|
||||
* further evaluates an expression against the last thrown exception.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
@@ -60,7 +60,8 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact
|
||||
*/
|
||||
public ExpressionRetryPolicy(String expressionString) {
|
||||
Assert.notNull(expressionString, "'expressionString' cannot be null");
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString, PARSER_CONTEXT);
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString,
|
||||
PARSER_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +71,8 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact
|
||||
* @param traverseCauses true to examine causes
|
||||
* @param expression the expression
|
||||
*/
|
||||
public ExpressionRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
public ExpressionRetryPolicy(int maxAttempts,
|
||||
Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses, Expression expression) {
|
||||
super(maxAttempts, retryableExceptions, traverseCauses);
|
||||
Assert.notNull(expression, "'expression' cannot be null");
|
||||
@@ -85,11 +87,13 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact
|
||||
* @param expressionString the expression.
|
||||
* @param defaultValue the default action
|
||||
*/
|
||||
public ExpressionRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
public ExpressionRetryPolicy(int maxAttempts,
|
||||
Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses, String expressionString, boolean defaultValue) {
|
||||
super(maxAttempts, retryableExceptions, traverseCauses, defaultValue);
|
||||
Assert.notNull(expressionString, "'expressionString' cannot be null");
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString, PARSER_CONTEXT);
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString,
|
||||
PARSER_CONTEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,8 +113,8 @@ public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFact
|
||||
return super.canRetry(context);
|
||||
}
|
||||
else {
|
||||
return super.canRetry(context)
|
||||
&& this.expression.getValue(this.evaluationContext, lastThrowable, Boolean.class);
|
||||
return super.canRetry(context) && this.expression
|
||||
.getValue(this.evaluationContext, lastThrowable, Boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,21 +22,22 @@ import java.util.Map;
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Map-based implementation of {@link RetryContextCache}. The map backing the
|
||||
* cache of contexts is synchronized.
|
||||
*
|
||||
* Map-based implementation of {@link RetryContextCache}. The map backing the cache of
|
||||
* contexts is synchronized.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class MapRetryContextCache implements RetryContextCache {
|
||||
|
||||
/**
|
||||
* Default value for maximum capacity of the cache. This is set to a
|
||||
* reasonably low value (4096) to avoid users inadvertently filling the
|
||||
* cache with item keys that are inconsistent.
|
||||
* Default value for maximum capacity of the cache. This is set to a reasonably low
|
||||
* value (4096) to avoid users inadvertently filling the cache with item keys that are
|
||||
* inconsistent.
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 4096;
|
||||
|
||||
private Map<Object, RetryContext> map = Collections.synchronizedMap(new HashMap<Object, RetryContext>());
|
||||
private Map<Object, RetryContext> map = Collections
|
||||
.synchronizedMap(new HashMap<Object, RetryContext>());
|
||||
|
||||
private int capacity;
|
||||
|
||||
@@ -56,12 +57,10 @@ public class MapRetryContextCache implements RetryContextCache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the capacity. Prevents the cache from growing
|
||||
* unboundedly if items that fail are misidentified and two references to an
|
||||
* identical item actually do not have the same key. This can happen when
|
||||
* users implement equals and hashCode based on mutable fields, for
|
||||
* instance.
|
||||
*
|
||||
* Public setter for the capacity. Prevents the cache from growing unboundedly if
|
||||
* items that fail are misidentified and two references to an identical item actually
|
||||
* do not have the same key. This can happen when users implement equals and hashCode
|
||||
* based on mutable fields, for instance.
|
||||
* @param capacity the capacity to set
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
@@ -78,9 +77,10 @@ public class MapRetryContextCache implements RetryContextCache {
|
||||
|
||||
public void put(Object key, RetryContext context) {
|
||||
if (map.size() >= capacity) {
|
||||
throw new RetryCacheCapacityExceededException("Retry cache capacity limit breached. "
|
||||
+ "Do you need to re-consider the implementation of the key generator, "
|
||||
+ "or the equals and hashCode of the items that failed?");
|
||||
throw new RetryCacheCapacityExceededException(
|
||||
"Retry cache capacity limit breached. "
|
||||
+ "Do you need to re-consider the implementation of the key generator, "
|
||||
+ "or the equals and hashCode of the items that failed?");
|
||||
}
|
||||
map.put(key, context);
|
||||
}
|
||||
@@ -88,4 +88,5 @@ public class MapRetryContextCache implements RetryContextCache {
|
||||
public void remove(Object key) {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,16 +22,16 @@ import org.springframework.retry.context.RetryContextSupport;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* Simple retry policy that is aware only about attempt count and retries a fixed number of times.
|
||||
* The number of attempts includes the initial try.
|
||||
* Simple retry policy that is aware only about attempt count and retries a fixed number
|
||||
* of times. The number of attempts includes the initial try.
|
||||
* <p>
|
||||
* It is not recommended to use it directly, because usually exception classification is strongly
|
||||
* recommended (to not retry on OutOfMemoryError, for example).
|
||||
* It is not recommended to use it directly, because usually exception classification is
|
||||
* strongly recommended (to not retry on OutOfMemoryError, for example).
|
||||
* <p>
|
||||
* For daily usage see {@link RetryTemplate#newBuilder()}
|
||||
* <p>
|
||||
* Volatility of maxAttempts allows concurrent modification and does not require safe publication
|
||||
* of new instance after construction.
|
||||
* Volatility of maxAttempts allows concurrent modification and does not require safe
|
||||
* publication of new instance after construction.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
@@ -44,8 +44,8 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
private volatile int maxAttempts;
|
||||
|
||||
/**
|
||||
* Create a {@link MaxAttemptsRetryPolicy} with the default number of retry
|
||||
* attempts, retrying all throwables.
|
||||
* Create a {@link MaxAttemptsRetryPolicy} with the default number of retry attempts,
|
||||
* retrying all throwables.
|
||||
*/
|
||||
public MaxAttemptsRetryPolicy() {
|
||||
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
|
||||
@@ -63,7 +63,6 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
* Set the number of attempts before retries are exhausted. Includes the initial
|
||||
* attempt before the retries begin so, generally, will be {@code >= 1}. For example
|
||||
* setting this property to 3 means 3 attempts total (initial + 2 retries).
|
||||
*
|
||||
* @param maxAttempts the maximum number of attempts including the initial attempt.
|
||||
*/
|
||||
public void setMaxAttempts(int maxAttempts) {
|
||||
@@ -72,7 +71,6 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* The maximum number of attempts before failure.
|
||||
*
|
||||
* @return the maximum number of attempts
|
||||
*/
|
||||
public int getMaxAttempts() {
|
||||
@@ -83,9 +81,8 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
* Test for retryable operation based on the status.
|
||||
*
|
||||
* @see RetryPolicy#canRetry(RetryContext)
|
||||
*
|
||||
* @return true if the last exception was retryable and the number of
|
||||
* attempts so far is less than the limit.
|
||||
* @return true if the last exception was retryable and the number of attempts so far
|
||||
* is less than the limit.
|
||||
*/
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
@@ -107,9 +104,8 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a status object that can be used to track the current operation
|
||||
* according to this policy. Has to be aware of the latest exception and the
|
||||
* number of attempts.
|
||||
* Get a status object that can be used to track the current operation according to
|
||||
* this policy. Has to be aware of the latest exception and the number of attempts.
|
||||
*
|
||||
* @see RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
@@ -117,4 +113,5 @@ public class MaxAttemptsRetryPolicy implements RetryPolicy {
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new RetryContextSupport(parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,20 +21,19 @@ import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.context.RetryContextSupport;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that allows the first attempt but never permits a
|
||||
* retry. Also be used as a base class for other policies, e.g. for test
|
||||
* purposes as a stub.
|
||||
*
|
||||
* A {@link RetryPolicy} that allows the first attempt but never permits a retry. Also be
|
||||
* used as a base class for other policies, e.g. for test purposes as a stub.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class NeverRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* Returns false after the first exception. So there is always one try, and
|
||||
* then the retry is prevented.
|
||||
*
|
||||
* Returns false after the first exception. So there is always one try, and then the
|
||||
* retry is prevented.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
@@ -43,7 +42,7 @@ public class NeverRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
public void close(RetryContext context) {
|
||||
@@ -51,9 +50,9 @@ public class NeverRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a context that can respond to early termination requests, but does
|
||||
* nothing else.
|
||||
*
|
||||
* Return a context that can respond to early termination requests, but does nothing
|
||||
* else.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
@@ -71,17 +70,17 @@ public class NeverRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Special context object for {@link NeverRetryPolicy}. Implements a flag
|
||||
* with a similar function to {@link RetryContext#isExhaustedOnly()}, but
|
||||
* kept separate so that if subclasses of {@link NeverRetryPolicy} need to
|
||||
* they can modify the behaviour of
|
||||
* {@link NeverRetryPolicy#canRetry(RetryContext)} without affecting
|
||||
* Special context object for {@link NeverRetryPolicy}. Implements a flag with a
|
||||
* similar function to {@link RetryContext#isExhaustedOnly()}, but kept separate so
|
||||
* that if subclasses of {@link NeverRetryPolicy} need to they can modify the
|
||||
* behaviour of {@link NeverRetryPolicy#canRetry(RetryContext)} without affecting
|
||||
* {@link RetryContext#isExhaustedOnly()}.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
private static class NeverRetryContext extends RetryContextSupport {
|
||||
|
||||
private boolean finished = false;
|
||||
|
||||
public NeverRetryContext(RetryContext parent) {
|
||||
@@ -95,6 +94,7 @@ public class NeverRetryPolicy implements RetryPolicy {
|
||||
public void setFinished() {
|
||||
this.finished = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,22 +18,20 @@ package org.springframework.retry.policy;
|
||||
import org.springframework.retry.RetryException;
|
||||
|
||||
/**
|
||||
* Exception that indicates that a cache limit was exceeded. This is often a
|
||||
* sign of badly or inconsistently implemented hashCode, equals in failed items.
|
||||
* Items can then fail repeatedly and appear different to the cache, so they get
|
||||
* added over and over again until a limit is reached and this exception is
|
||||
* thrown. Consult the documentation of the {@link RetryContextCache} in use to
|
||||
* determine how to increase the limit if appropriate.
|
||||
*
|
||||
* Exception that indicates that a cache limit was exceeded. This is often a sign of badly
|
||||
* or inconsistently implemented hashCode, equals in failed items. Items can then fail
|
||||
* repeatedly and appear different to the cache, so they get added over and over again
|
||||
* until a limit is reached and this exception is thrown. Consult the documentation of the
|
||||
* {@link RetryContextCache} in use to determine how to increase the limit if appropriate.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RetryCacheCapacityExceededException extends RetryException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a message.
|
||||
*
|
||||
* @param message the message sent when creating the exception
|
||||
*/
|
||||
public RetryCacheCapacityExceededException(String message) {
|
||||
@@ -42,7 +40,6 @@ public class RetryCacheCapacityExceededException extends RetryException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a message and nested exception.
|
||||
*
|
||||
* @param msg the exception message.
|
||||
* @param nested the nested exception
|
||||
*/
|
||||
|
||||
@@ -23,11 +23,10 @@ import org.springframework.retry.RetryContext;
|
||||
* retrieving {@link RetryContext} instances. A null key should never be passed in by the
|
||||
* caller, but if it is then implementations are free to discard the context instead of
|
||||
* saving it (null key means "no information").
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see MapRetryContextCache
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface RetryContextCache {
|
||||
|
||||
|
||||
@@ -25,9 +25,8 @@ import org.springframework.retry.context.RetryContextSupport;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Simple retry policy that retries a fixed number of times for a set of named
|
||||
* exceptions (and subclasses). The number of attempts includes the initial try,
|
||||
* so e.g.
|
||||
* Simple retry policy that retries a fixed number of times for a set of named exceptions
|
||||
* (and subclasses). The number of attempts includes the initial try, so e.g.
|
||||
*
|
||||
* <pre>
|
||||
* retryTemplate = new RetryTemplate(new SimpleRetryPolicy(3));
|
||||
@@ -36,16 +35,16 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* will execute the callback at least once, and as many as 3 times.
|
||||
*
|
||||
* Since version 1.3 it is not necessary to use this class. The same behaviour
|
||||
* can be achieved by constructing a {@link CompositeRetryPolicy} with {@link MaxAttemptsRetryPolicy}
|
||||
* and {@link BinaryExceptionClassifierRetryPolicy} inside, that is actually performed by:
|
||||
* <pre> {@code:
|
||||
* Since version 1.3 it is not necessary to use this class. The same behaviour can be
|
||||
* achieved by constructing a {@link CompositeRetryPolicy} with
|
||||
* {@link MaxAttemptsRetryPolicy} and {@link BinaryExceptionClassifierRetryPolicy} inside,
|
||||
* that is actually performed by: <pre> {@code:
|
||||
* RetryTemplate.newBuilder()
|
||||
* .maxAttempts(3)
|
||||
* .retryOn(Exception.class)
|
||||
* .build();
|
||||
* }</pre>
|
||||
* or by {@link org.springframework.retry.support.RetryTemplate#newDefaultInstance()}
|
||||
* }</pre> or by
|
||||
* {@link org.springframework.retry.support.RetryTemplate#newDefaultInstance()}
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Rob Harrop
|
||||
@@ -62,74 +61,74 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
|
||||
private volatile int maxAttempts;
|
||||
|
||||
private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);
|
||||
private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(
|
||||
false);
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the default number of retry
|
||||
* attempts, retrying all exceptions.
|
||||
* Create a {@link SimpleRetryPolicy} with the default number of retry attempts,
|
||||
* retrying all exceptions.
|
||||
*/
|
||||
public SimpleRetryPolicy() {
|
||||
this(DEFAULT_MAX_ATTEMPTS, BinaryExceptionClassifier.newDefaultClassifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts, retrying all exceptions.
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry attempts,
|
||||
* retrying all exceptions.
|
||||
*/
|
||||
public SimpleRetryPolicy(int maxAttempts) {
|
||||
this(maxAttempts, BinaryExceptionClassifier.newDefaultClassifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts.
|
||||
*
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry attempts.
|
||||
* @param maxAttempts the maximum number of attempts
|
||||
* @param retryableExceptions the map of exceptions that are retryable
|
||||
*/
|
||||
public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
|
||||
public SimpleRetryPolicy(int maxAttempts,
|
||||
Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
|
||||
this(maxAttempts, retryableExceptions, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts. If traverseCauses is true, the exception causes will be traversed until
|
||||
* a match is found.
|
||||
*
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry attempts. If
|
||||
* traverseCauses is true, the exception causes will be traversed until a match is
|
||||
* found.
|
||||
* @param maxAttempts the maximum number of attempts
|
||||
* @param retryableExceptions the map of exceptions that are retryable based on the
|
||||
* map value (true/false).
|
||||
* @param traverseCauses is this clause traversable
|
||||
*/
|
||||
public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
public SimpleRetryPolicy(int maxAttempts,
|
||||
Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses) {
|
||||
this(maxAttempts, retryableExceptions, traverseCauses, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts. If traverseCauses is true, the exception causes will be traversed until
|
||||
* a match is found. The default value indicates whether to retry or not for exceptions
|
||||
* (or super classes) are not found in the map.
|
||||
*
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry attempts. If
|
||||
* traverseCauses is true, the exception causes will be traversed until a match is
|
||||
* found. The default value indicates whether to retry or not for exceptions (or super
|
||||
* classes) are not found in the map.
|
||||
* @param maxAttempts the maximum number of attempts
|
||||
* @param retryableExceptions the map of exceptions that are retryable based on the
|
||||
* map value (true/false).
|
||||
* @param traverseCauses is this clause traversable
|
||||
* @param defaultValue the default action.
|
||||
*/
|
||||
public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
public SimpleRetryPolicy(int maxAttempts,
|
||||
Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses, boolean defaultValue) {
|
||||
super();
|
||||
this.maxAttempts = maxAttempts;
|
||||
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions, defaultValue);
|
||||
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions,
|
||||
defaultValue);
|
||||
this.retryableClassifier.setTraverseCauses(traverseCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts and provided exception classifier.
|
||||
*
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry attempts and
|
||||
* provided exception classifier.
|
||||
* @param maxAttempts the maximum number of attempts
|
||||
* @param classifier custom exception classifier
|
||||
*/
|
||||
@@ -143,7 +142,6 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
* Set the number of attempts before retries are exhausted. Includes the initial
|
||||
* attempt before the retries begin so, generally, will be {@code >= 1}. For example
|
||||
* setting this property to 3 means 3 attempts total (initial + 2 retries).
|
||||
*
|
||||
* @param maxAttempts the maximum number of attempts including the initial attempt.
|
||||
*/
|
||||
public void setMaxAttempts(int maxAttempts) {
|
||||
@@ -152,7 +150,6 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* The maximum number of attempts before failure.
|
||||
*
|
||||
* @return the maximum number of attempts
|
||||
*/
|
||||
public int getMaxAttempts() {
|
||||
@@ -163,14 +160,14 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
* Test for retryable operation based on the status.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
|
||||
*
|
||||
* @return true if the last exception was retryable and the number of
|
||||
* attempts so far is less than the limit.
|
||||
* @return true if the last exception was retryable and the number of attempts so far
|
||||
* is less than the limit.
|
||||
*/
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
Throwable t = context.getLastThrowable();
|
||||
return (t == null || retryForException(t)) && context.getRetryCount() < maxAttempts;
|
||||
return (t == null || retryForException(t))
|
||||
&& context.getRetryCount() < maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,9 +189,8 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a status object that can be used to track the current operation
|
||||
* according to this policy. Has to be aware of the latest exception and the
|
||||
* number of attempts.
|
||||
* Get a status object that can be used to track the current operation according to
|
||||
* this policy. Has to be aware of the latest exception and the number of attempts.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
@@ -204,17 +200,17 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
private static class SimpleRetryContext extends RetryContextSupport {
|
||||
|
||||
public SimpleRetryContext(RetryContext parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to an exception classifier.
|
||||
*
|
||||
* @param ex
|
||||
* @return true if this exception or its ancestors have been registered as
|
||||
* retryable.
|
||||
* @return true if this exception or its ancestors have been registered as retryable.
|
||||
*/
|
||||
private boolean retryForException(Throwable ex) {
|
||||
return retryableClassifier.classify(ex);
|
||||
@@ -224,4 +220,5 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + maxAttempts + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,20 +23,19 @@ import java.util.Map;
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Map-based implementation of {@link RetryContextCache}. The map backing the
|
||||
* cache of contexts is synchronized and its entries are soft-referenced, so may
|
||||
* be garbage collected under pressure.
|
||||
*
|
||||
* Map-based implementation of {@link RetryContextCache}. The map backing the cache of
|
||||
* contexts is synchronized and its entries are soft-referenced, so may be garbage
|
||||
* collected under pressure.
|
||||
*
|
||||
* @see MapRetryContextCache for non-soft referenced version
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SoftReferenceMapRetryContextCache implements RetryContextCache {
|
||||
|
||||
/**
|
||||
* Default value for maximum capacity of the cache. This is set to a
|
||||
* reasonably low value (4096) to avoid users inadvertently filling the
|
||||
* cache with item keys that are inconsistent.
|
||||
* Default value for maximum capacity of the cache. This is set to a reasonably low
|
||||
* value (4096) to avoid users inadvertently filling the cache with item keys that are
|
||||
* inconsistent.
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 4096;
|
||||
|
||||
@@ -61,12 +60,10 @@ public class SoftReferenceMapRetryContextCache implements RetryContextCache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the capacity. Prevents the cache from growing
|
||||
* unboundedly if items that fail are misidentified and two references to an
|
||||
* identical item actually do not have the same key. This can happen when
|
||||
* users implement equals and hashCode based on mutable fields, for
|
||||
* instance.
|
||||
*
|
||||
* Public setter for the capacity. Prevents the cache from growing unboundedly if
|
||||
* items that fail are misidentified and two references to an identical item actually
|
||||
* do not have the same key. This can happen when users implement equals and hashCode
|
||||
* based on mutable fields, for instance.
|
||||
* @param capacity the capacity to set
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
@@ -90,9 +87,10 @@ public class SoftReferenceMapRetryContextCache implements RetryContextCache {
|
||||
|
||||
public void put(Object key, RetryContext context) {
|
||||
if (map.size() >= capacity) {
|
||||
throw new RetryCacheCapacityExceededException("Retry cache capacity limit breached. "
|
||||
+ "Do you need to re-consider the implementation of the key generator, "
|
||||
+ "or the equals and hashCode of the items that failed?");
|
||||
throw new RetryCacheCapacityExceededException(
|
||||
"Retry cache capacity limit breached. "
|
||||
+ "Do you need to re-consider the implementation of the key generator, "
|
||||
+ "or the equals and hashCode of the items that failed?");
|
||||
}
|
||||
map.put(key, new SoftReference<RetryContext>(context));
|
||||
}
|
||||
@@ -100,4 +98,5 @@ public class SoftReferenceMapRetryContextCache implements RetryContextCache {
|
||||
public void remove(Object key) {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.context.RetryContextSupport;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that allows a retry only if it hasn't timed out. The
|
||||
* clock is started on a call to {@link #open(RetryContext)}.
|
||||
*
|
||||
* A {@link RetryPolicy} that allows a retry only if it hasn't timed out. The clock is
|
||||
* started on a call to {@link #open(RetryContext)}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
@@ -44,10 +44,9 @@ public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The value of the timeout.
|
||||
*
|
||||
* @return the timeout in milliseconds
|
||||
*/
|
||||
public long getTimeout() {
|
||||
@@ -55,9 +54,9 @@ public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Only permits a retry if the timeout has not expired. Does not check the
|
||||
* exception at all.
|
||||
*
|
||||
* Only permits a retry if the timeout has not expired. Does not check the exception
|
||||
* at all.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
@@ -77,6 +76,7 @@ public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
private static class TimeoutRetryContext extends RetryContextSupport {
|
||||
|
||||
private long timeout;
|
||||
|
||||
private long start;
|
||||
@@ -90,6 +90,7 @@ public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
public boolean isAlive() {
|
||||
return (System.currentTimeMillis() - start) <= timeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,13 +26,19 @@ import org.springframework.retry.RetryStatistics;
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class DefaultRetryStatistics extends AttributeAccessorSupport implements RetryStatistics, MutableRetryStatistics {
|
||||
public class DefaultRetryStatistics extends AttributeAccessorSupport
|
||||
implements RetryStatistics, MutableRetryStatistics {
|
||||
|
||||
private String name;
|
||||
|
||||
private AtomicInteger startedCount = new AtomicInteger();
|
||||
|
||||
private AtomicInteger completeCount = new AtomicInteger();
|
||||
|
||||
private AtomicInteger recoveryCount = new AtomicInteger();
|
||||
|
||||
private AtomicInteger errorCount = new AtomicInteger();
|
||||
|
||||
private AtomicInteger abortCount = new AtomicInteger();
|
||||
|
||||
DefaultRetryStatistics() {
|
||||
|
||||
@@ -23,7 +23,7 @@ package org.springframework.retry.stats;
|
||||
public class DefaultRetryStatisticsFactory implements RetryStatisticsFactory {
|
||||
|
||||
private long window = 15000;
|
||||
|
||||
|
||||
/**
|
||||
* Window in milliseconds for exponential decay factor in rolling averages.
|
||||
* @param window the window to set
|
||||
@@ -34,7 +34,8 @@ public class DefaultRetryStatisticsFactory implements RetryStatisticsFactory {
|
||||
|
||||
@Override
|
||||
public MutableRetryStatistics create(String name) {
|
||||
ExponentialAverageRetryStatistics stats = new ExponentialAverageRetryStatistics(name);
|
||||
ExponentialAverageRetryStatistics stats = new ExponentialAverageRetryStatistics(
|
||||
name);
|
||||
stats.setWindow(window);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.retry.RetryStatistics;
|
||||
public class DefaultStatisticsRepository implements StatisticsRepository {
|
||||
|
||||
private ConcurrentMap<String, MutableRetryStatistics> map = new ConcurrentHashMap<String, MutableRetryStatistics>();
|
||||
|
||||
private RetryStatisticsFactory factory = new DefaultRetryStatisticsFactory();
|
||||
|
||||
public void setRetryStatisticsFactory(RetryStatisticsFactory factory) {
|
||||
|
||||
@@ -26,15 +26,15 @@ public class ExponentialAverageRetryStatistics extends DefaultRetryStatistics {
|
||||
private long window = 15000;
|
||||
|
||||
private ExponentialAverage started;
|
||||
|
||||
|
||||
private ExponentialAverage error;
|
||||
|
||||
|
||||
private ExponentialAverage complete;
|
||||
|
||||
|
||||
private ExponentialAverage recovery;
|
||||
|
||||
|
||||
private ExponentialAverage abort;
|
||||
|
||||
|
||||
public ExponentialAverageRetryStatistics(String name) {
|
||||
super(name);
|
||||
init();
|
||||
@@ -50,7 +50,6 @@ public class ExponentialAverageRetryStatistics extends DefaultRetryStatistics {
|
||||
|
||||
/**
|
||||
* Window in milliseconds for exponential decay factor in rolling average.
|
||||
*
|
||||
* @param window the window to set
|
||||
*/
|
||||
public void setWindow(long window) {
|
||||
@@ -65,32 +64,32 @@ public class ExponentialAverageRetryStatistics extends DefaultRetryStatistics {
|
||||
public int getRollingErrorCount() {
|
||||
return (int) Math.round(error.getValue());
|
||||
}
|
||||
|
||||
|
||||
public int getRollingAbortCount() {
|
||||
return (int) Math.round(abort.getValue());
|
||||
}
|
||||
|
||||
|
||||
public int getRollingRecoveryCount() {
|
||||
return (int) Math.round(recovery.getValue());
|
||||
}
|
||||
|
||||
|
||||
public int getRollingCompleteCount() {
|
||||
return (int) Math.round(complete.getValue());
|
||||
}
|
||||
|
||||
|
||||
public double getRollingErrorRate() {
|
||||
if (Math.round(started.getValue())==0) {
|
||||
if (Math.round(started.getValue()) == 0) {
|
||||
return 0.;
|
||||
}
|
||||
return (abort.getValue() + recovery.getValue()) / started.getValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void incrementStartedCount() {
|
||||
super.incrementStartedCount();
|
||||
started.increment();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void incrementCompleteCount() {
|
||||
super.incrementCompleteCount();
|
||||
@@ -118,9 +117,9 @@ public class ExponentialAverageRetryStatistics extends DefaultRetryStatistics {
|
||||
private class ExponentialAverage {
|
||||
|
||||
private final double alpha;
|
||||
|
||||
|
||||
private volatile long lastTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
private volatile double value = 0;
|
||||
|
||||
public ExponentialAverage(long window) {
|
||||
@@ -129,13 +128,13 @@ public class ExponentialAverageRetryStatistics extends DefaultRetryStatistics {
|
||||
|
||||
public synchronized void increment() {
|
||||
long time = System.currentTimeMillis();
|
||||
value = value * Math.exp(-alpha*(time - lastTime)) + 1;
|
||||
value = value * Math.exp(-alpha * (time - lastTime)) + 1;
|
||||
lastTime = time;
|
||||
}
|
||||
|
||||
|
||||
public double getValue() {
|
||||
long time = System.currentTimeMillis();
|
||||
return value * Math.exp(-alpha*(time - lastTime));
|
||||
return value * Math.exp(-alpha * (time - lastTime));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ package org.springframework.retry.stats;
|
||||
*
|
||||
*/
|
||||
public interface RetryStatisticsFactory {
|
||||
|
||||
|
||||
MutableRetryStatistics create(String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -105,4 +105,5 @@ public class StatisticsListener extends RetryListenerSupport {
|
||||
private String getName(RetryContext context) {
|
||||
return (String) context.getAttribute(RetryContext.NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ import org.springframework.retry.RetryStatistics;
|
||||
public interface StatisticsRepository {
|
||||
|
||||
RetryStatistics findOne(String name);
|
||||
|
||||
|
||||
Iterable<RetryStatistics> findAll();
|
||||
|
||||
void addStarted(String name);
|
||||
|
||||
|
||||
void addError(String name);
|
||||
|
||||
void addRecovery(String name);
|
||||
|
||||
@@ -22,9 +22,8 @@ import org.springframework.retry.RetryOperations;
|
||||
import org.springframework.retry.RetryState;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class DefaultRetryState implements RetryState {
|
||||
|
||||
@@ -35,21 +34,18 @@ public class DefaultRetryState implements RetryState {
|
||||
final private Classifier<? super Throwable, Boolean> rollbackClassifier;
|
||||
|
||||
/**
|
||||
* Create a {@link DefaultRetryState} representing the state for a new retry
|
||||
* attempt.
|
||||
*
|
||||
* Create a {@link DefaultRetryState} representing the state for a new retry attempt.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RetryState)
|
||||
* @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
|
||||
*
|
||||
* @param key the key for the state to allow this retry attempt to be
|
||||
* recognised
|
||||
* @param forceRefresh true if the attempt is known to be a brand new state
|
||||
* (could not have previously failed)
|
||||
* @param rollbackClassifier the rollback classifier to set. The rollback
|
||||
* classifier answers true if the exception provided should cause a
|
||||
* rollback.
|
||||
* @param key the key for the state to allow this retry attempt to be recognised
|
||||
* @param forceRefresh true if the attempt is known to be a brand new state (could not
|
||||
* have previously failed)
|
||||
* @param rollbackClassifier the rollback classifier to set. The rollback classifier
|
||||
* answers true if the exception provided should cause a rollback.
|
||||
*/
|
||||
public DefaultRetryState(Object key, boolean forceRefresh, Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
public DefaultRetryState(Object key, boolean forceRefresh,
|
||||
Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
this.key = key;
|
||||
this.forceRefresh = forceRefresh;
|
||||
this.rollbackClassifier = rollbackClassifier;
|
||||
@@ -61,7 +57,8 @@ public class DefaultRetryState implements RetryState {
|
||||
* @param key the key
|
||||
* @param rollbackClassifier the rollback {@link Classifier}
|
||||
*/
|
||||
public DefaultRetryState(Object key, Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
public DefaultRetryState(Object key,
|
||||
Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
this(key, false, rollbackClassifier);
|
||||
}
|
||||
|
||||
@@ -76,9 +73,7 @@ public class DefaultRetryState implements RetryState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults the force refresh flag (to false) and the rollback classifier
|
||||
* (to null).
|
||||
*
|
||||
* Defaults the force refresh flag (to false) and the rollback classifier (to null).
|
||||
* @param key the key to use
|
||||
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
|
||||
*/
|
||||
@@ -88,7 +83,7 @@ public class DefaultRetryState implements RetryState {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see org.springframework.batch.retry.IRetryState#getKey()
|
||||
*/
|
||||
public Object getKey() {
|
||||
@@ -97,7 +92,7 @@ public class DefaultRetryState implements RetryState {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see org.springframework.batch.retry.IRetryState#isForceRefresh()
|
||||
*/
|
||||
public boolean isForceRefresh() {
|
||||
@@ -106,10 +101,8 @@ public class DefaultRetryState implements RetryState {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.retry.RetryState#rollbackFor(java.lang.Throwable
|
||||
* )
|
||||
*
|
||||
* @see org.springframework.batch.retry.RetryState#rollbackFor(java.lang.Throwable )
|
||||
*/
|
||||
public boolean rollbackFor(Throwable exception) {
|
||||
if (rollbackClassifier == null) {
|
||||
@@ -120,6 +113,8 @@ public class DefaultRetryState implements RetryState {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s: key=%s, forceRefresh=%b]", getClass().getSimpleName(), key, forceRefresh);
|
||||
return String.format("[%s: key=%s, forceRefresh=%b]", getClass().getSimpleName(),
|
||||
key, forceRefresh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ public class RetrySimulation {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a list of all the unique sleep values which were executed within all simulations.
|
||||
* @return Returns a list of all the unique sleep values which were executed within
|
||||
* all simulations.
|
||||
*/
|
||||
public List<Double> getPercentiles() {
|
||||
List<Double> res = new ArrayList<Double>();
|
||||
@@ -77,6 +78,7 @@ public class RetrySimulation {
|
||||
}
|
||||
|
||||
public static class SleepSequence {
|
||||
|
||||
private final List<Long> sleeps;
|
||||
|
||||
private final long longestSleep;
|
||||
@@ -111,5 +113,7 @@ public class RetrySimulation {
|
||||
public String toString() {
|
||||
return "totalSleep=" + totalSleep + ": " + sleeps.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,94 +28,102 @@ import org.springframework.retry.backoff.SleepingBackOffPolicy;
|
||||
/**
|
||||
* A {@link RetrySimulator} is a tool for exercising retry + backoff operations.
|
||||
*
|
||||
* When calibrating a set of retry + backoff pairs, it is useful to know the behaviour
|
||||
* of the retry for various scenarios.
|
||||
* When calibrating a set of retry + backoff pairs, it is useful to know the behaviour of
|
||||
* the retry for various scenarios.
|
||||
*
|
||||
* Things you may want to know:
|
||||
* - Does a 'maxInterval' of 5000 ms in my backoff even matter?
|
||||
* (This is often the case when retry counts are low -- so why set the max interval
|
||||
* at something that cannot be achieved?)
|
||||
* - What are the typical sleep durations for threads in a retry
|
||||
* - What was the longest sleep duration for any retry sequence
|
||||
* Things you may want to know: - Does a 'maxInterval' of 5000 ms in my backoff even
|
||||
* matter? (This is often the case when retry counts are low -- so why set the max
|
||||
* interval at something that cannot be achieved?) - What are the typical sleep durations
|
||||
* for threads in a retry - What was the longest sleep duration for any retry sequence
|
||||
*
|
||||
* The simulator provides this information by executing a retry + backoff pair until failure
|
||||
* (that is all retries are exhausted). The information about each retry is provided
|
||||
* as part of the {@link RetrySimulation}.
|
||||
* The simulator provides this information by executing a retry + backoff pair until
|
||||
* failure (that is all retries are exhausted). The information about each retry is
|
||||
* provided as part of the {@link RetrySimulation}.
|
||||
*
|
||||
* Note that the impetus for this class was to expose the timings which are possible with
|
||||
* {@link org.springframework.retry.backoff.ExponentialRandomBackOffPolicy}, which provides
|
||||
* random values and must be looked at over a series of trials.
|
||||
* {@link org.springframework.retry.backoff.ExponentialRandomBackOffPolicy}, which
|
||||
* provides random values and must be looked at over a series of trials.
|
||||
*
|
||||
* @author Jon Travis
|
||||
*/
|
||||
public class RetrySimulator {
|
||||
|
||||
private final SleepingBackOffPolicy<?> backOffPolicy;
|
||||
private final RetryPolicy retryPolicy;
|
||||
|
||||
public RetrySimulator(SleepingBackOffPolicy<?> backOffPolicy, RetryPolicy retryPolicy) {
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
this.retryPolicy = retryPolicy;
|
||||
}
|
||||
private final RetryPolicy retryPolicy;
|
||||
|
||||
/**
|
||||
* Execute the simulator for a give # of iterations.
|
||||
*
|
||||
* @param numSimulations Number of simulations to run
|
||||
* @return the outcome of all simulations
|
||||
*/
|
||||
public RetrySimulation executeSimulation(int numSimulations) {
|
||||
RetrySimulation simulation = new RetrySimulation();
|
||||
public RetrySimulator(SleepingBackOffPolicy<?> backOffPolicy,
|
||||
RetryPolicy retryPolicy) {
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
this.retryPolicy = retryPolicy;
|
||||
}
|
||||
|
||||
for (int i=0; i<numSimulations; i++) {
|
||||
simulation.addSequence(executeSingleSimulation());
|
||||
}
|
||||
return simulation;
|
||||
}
|
||||
/**
|
||||
* Execute the simulator for a give # of iterations.
|
||||
* @param numSimulations Number of simulations to run
|
||||
* @return the outcome of all simulations
|
||||
*/
|
||||
public RetrySimulation executeSimulation(int numSimulations) {
|
||||
RetrySimulation simulation = new RetrySimulation();
|
||||
|
||||
/**
|
||||
* Execute a single simulation
|
||||
* @return The sleeps which occurred within the single simulation.
|
||||
*/
|
||||
public List<Long> executeSingleSimulation() {
|
||||
StealingSleeper stealingSleeper = new StealingSleeper();
|
||||
SleepingBackOffPolicy<?> stealingBackoff = backOffPolicy.withSleeper(stealingSleeper);
|
||||
for (int i = 0; i < numSimulations; i++) {
|
||||
simulation.addSequence(executeSingleSimulation());
|
||||
}
|
||||
return simulation;
|
||||
}
|
||||
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setBackOffPolicy(stealingBackoff);
|
||||
template.setRetryPolicy(retryPolicy);
|
||||
/**
|
||||
* Execute a single simulation
|
||||
* @return The sleeps which occurred within the single simulation.
|
||||
*/
|
||||
public List<Long> executeSingleSimulation() {
|
||||
StealingSleeper stealingSleeper = new StealingSleeper();
|
||||
SleepingBackOffPolicy<?> stealingBackoff = backOffPolicy
|
||||
.withSleeper(stealingSleeper);
|
||||
|
||||
try {
|
||||
template.execute(new FailingRetryCallback());
|
||||
} catch(FailingRetryException e) {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setBackOffPolicy(stealingBackoff);
|
||||
template.setRetryPolicy(retryPolicy);
|
||||
|
||||
} catch(Throwable e) {
|
||||
throw new RuntimeException("Unexpected exception", e);
|
||||
}
|
||||
try {
|
||||
template.execute(new FailingRetryCallback());
|
||||
}
|
||||
catch (FailingRetryException e) {
|
||||
|
||||
return stealingSleeper.getSleeps();
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new RuntimeException("Unexpected exception", e);
|
||||
}
|
||||
|
||||
static class FailingRetryCallback implements RetryCallback<Object, Exception> {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
throw new FailingRetryException();
|
||||
}
|
||||
}
|
||||
return stealingSleeper.getSleeps();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class FailingRetryCallback implements RetryCallback<Object, Exception> {
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
throw new FailingRetryException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class FailingRetryException extends Exception {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class StealingSleeper implements Sleeper {
|
||||
private final List<Long> sleeps = new ArrayList<Long>();
|
||||
|
||||
public void sleep(long backOffPeriod) throws InterruptedException {
|
||||
sleeps.add(backOffPeriod);
|
||||
}
|
||||
private final List<Long> sleeps = new ArrayList<Long>();
|
||||
|
||||
public void sleep(long backOffPeriod) throws InterruptedException {
|
||||
sleeps.add(backOffPeriod);
|
||||
}
|
||||
|
||||
public List<Long> getSleeps() {
|
||||
return sleeps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Long> getSleeps() {
|
||||
return sleeps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,27 +21,26 @@ import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryOperations;
|
||||
|
||||
/**
|
||||
* Global variable support for retry clients. Normally it is not necessary for
|
||||
* clients to be aware of the surrounding environment because a
|
||||
* {@link RetryCallback} can always use the context it is passed by the
|
||||
* enclosing {@link RetryOperations}. But occasionally it might be helpful to
|
||||
* have lower level access to the ongoing {@link RetryContext} so we provide a
|
||||
* global accessor here. The mutator methods ({@link #clear()} and
|
||||
* Global variable support for retry clients. Normally it is not necessary for clients to
|
||||
* be aware of the surrounding environment because a {@link RetryCallback} can always use
|
||||
* the context it is passed by the enclosing {@link RetryOperations}. But occasionally it
|
||||
* might be helpful to have lower level access to the ongoing {@link RetryContext} so we
|
||||
* provide a global accessor here. The mutator methods ({@link #clear()} and
|
||||
* {@link #register(RetryContext)} should not be used except internally by
|
||||
* {@link RetryOperations} implementations.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public final class RetrySynchronizationManager {
|
||||
|
||||
private RetrySynchronizationManager() {}
|
||||
private RetrySynchronizationManager() {
|
||||
}
|
||||
|
||||
private static final ThreadLocal<RetryContext> context = new ThreadLocal<RetryContext>();
|
||||
|
||||
/**
|
||||
* Public accessor for the locally enclosing {@link RetryContext}.
|
||||
*
|
||||
* @return the current retry context, or null if there isn't one
|
||||
*/
|
||||
public static RetryContext getContext() {
|
||||
@@ -50,10 +49,9 @@ public final class RetrySynchronizationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for registering a context - should only be used by
|
||||
* {@link RetryOperations} implementations to ensure that
|
||||
* {@link #getContext()} always returns the correct value.
|
||||
*
|
||||
* Method for registering a context - should only be used by {@link RetryOperations}
|
||||
* implementations to ensure that {@link #getContext()} always returns the correct
|
||||
* value.
|
||||
* @param context the new context to register
|
||||
* @return the old context if there was one
|
||||
*/
|
||||
@@ -66,7 +64,6 @@ public final class RetrySynchronizationManager {
|
||||
/**
|
||||
* Clear the current context at the end of a batch - should only be used by
|
||||
* {@link RetryOperations} implementations.
|
||||
*
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static RetryContext clear() {
|
||||
|
||||
@@ -57,14 +57,12 @@ import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
* properties. The {@link org.springframework.retry.backoff.BackOffPolicy} controls how
|
||||
* long the pause is between each individual retry attempt.
|
||||
* <p>
|
||||
* A new instance can be fluently configured via {@link #newBuilder}, e.g:
|
||||
* <pre> {@code
|
||||
* A new instance can be fluently configured via {@link #newBuilder}, e.g: <pre> {@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .maxAttempts(10)
|
||||
* .fixedBackoff(1000)
|
||||
* .build();
|
||||
* }</pre>
|
||||
* See {@link RetryTemplateBuilder} for more examples and details.
|
||||
* }</pre> See {@link RetryTemplateBuilder} for more examples and details.
|
||||
* <p>
|
||||
* This class is thread-safe and suitable for concurrent access when executing operations
|
||||
* and when performing configuration changes. As such, it is possible to change the number
|
||||
@@ -99,11 +97,10 @@ public class RetryTemplate implements RetryOperations {
|
||||
private boolean throwLastExceptionOnExhausted;
|
||||
|
||||
/**
|
||||
* Main entry point to configure RetryTemplate using fluent API.
|
||||
* See {@link RetryTemplateBuilder} for usage examples and details.
|
||||
*
|
||||
* @return a new instance of RetryTemplateBuilder with preset default behaviour, that can be overwritten during
|
||||
* manual configuration
|
||||
* Main entry point to configure RetryTemplate using fluent API. See
|
||||
* {@link RetryTemplateBuilder} for usage examples and details.
|
||||
* @return a new instance of RetryTemplateBuilder with preset default behaviour, that
|
||||
* can be overwritten during manual configuration
|
||||
* @since 1.3
|
||||
*/
|
||||
public static RetryTemplateBuilder newBuilder() {
|
||||
@@ -111,9 +108,8 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new default instance. The properties of default instance are described in {@link RetryTemplateBuilder}
|
||||
* documentation.
|
||||
*
|
||||
* Creates a new default instance. The properties of default instance are described in
|
||||
* {@link RetryTemplateBuilder} documentation.
|
||||
* @return a new instance of RetryTemplate with default behaviour
|
||||
* @since 1.3
|
||||
*/
|
||||
@@ -130,7 +126,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
*
|
||||
* @param retryContextCache the {@link RetryContextCache} to set.
|
||||
*/
|
||||
public void setRetryContextCache(RetryContextCache retryContextCache) {
|
||||
@@ -140,7 +135,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
/**
|
||||
* Setter for listeners. The listeners are executed before and after a retry block
|
||||
* (i.e. before and after all the attempts), and on an error (every attempt).
|
||||
*
|
||||
* @param listeners the {@link RetryListener}s
|
||||
* @see RetryListener
|
||||
*/
|
||||
@@ -151,7 +145,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
/**
|
||||
* Register an additional listener.
|
||||
*
|
||||
* @param listener the {@link RetryListener}
|
||||
* @see #setListeners(RetryListener[])
|
||||
*/
|
||||
@@ -164,7 +157,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
/**
|
||||
* Setter for {@link BackOffPolicy}.
|
||||
*
|
||||
* @param backOffPolicy the {@link BackOffPolicy}
|
||||
*/
|
||||
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
|
||||
@@ -173,7 +165,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
/**
|
||||
* Setter for {@link RetryPolicy}.
|
||||
*
|
||||
* @param retryPolicy the {@link RetryPolicy}
|
||||
*/
|
||||
public void setRetryPolicy(RetryPolicy retryPolicy) {
|
||||
@@ -187,7 +178,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback)
|
||||
* @param retryCallback the {@link RetryCallback}
|
||||
*
|
||||
* @throws TerminatedRetryException if the retry has been manually terminated by a
|
||||
* listener.
|
||||
*/
|
||||
@@ -399,7 +389,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
* Decide whether to proceed with the ongoing retry attempt. This method is called
|
||||
* before the {@link RetryCallback} is executed, but after the backoff and open
|
||||
* interceptors.
|
||||
*
|
||||
* @param retryPolicy the policy to apply
|
||||
* @param context the current retry context
|
||||
* @return true if we can continue with the attempt
|
||||
@@ -411,7 +400,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
/**
|
||||
* Clean up the cache if necessary and close the context provided (if the flag
|
||||
* indicates that processing was successful).
|
||||
*
|
||||
* @param retryPolicy the {@link RetryPolicy}
|
||||
* @param context the {@link RetryContext}
|
||||
* @param state the {@link RetryState}
|
||||
@@ -459,10 +447,8 @@ public class RetryTemplate implements RetryOperations {
|
||||
/**
|
||||
* Delegate to the {@link RetryPolicy} having checked in the cache for an existing
|
||||
* value if the state is not null.
|
||||
*
|
||||
* @param state a {@link RetryState}
|
||||
* @param retryPolicy a {@link RetryPolicy} to delegate the context creation
|
||||
*
|
||||
* @return a retry context, either a new one or the one used last time the same state
|
||||
* was encountered
|
||||
*/
|
||||
@@ -524,7 +510,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
* Actions to take after final attempt has failed. If there is state clean up the
|
||||
* cache. If there is a recovery callback, execute that and return its result.
|
||||
* Otherwise throw an exception.
|
||||
*
|
||||
* @param recoveryCallback the callback for recovery (might be null)
|
||||
* @param context the current retry context
|
||||
* @param state the {@link RetryState}
|
||||
@@ -570,7 +555,6 @@ public class RetryTemplate implements RetryOperations {
|
||||
* Extension point for subclasses to decide on behaviour after catching an exception
|
||||
* in a {@link RetryCallback}. Normal stateless behaviour is not to rethrow, and if
|
||||
* there is state we rethrow.
|
||||
*
|
||||
* @param retryPolicy the retry policy
|
||||
* @param context the current context
|
||||
* @param state the current retryState
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.springframework.retry.support;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -22,12 +21,11 @@ import org.springframework.retry.policy.TimeoutRetryPolicy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Fluent API to configure new instance of RetryTemplate.
|
||||
* For detailed description of each builder method - see it's doc.
|
||||
* Fluent API to configure new instance of RetryTemplate. For detailed description of each
|
||||
* builder method - see it's doc.
|
||||
*
|
||||
* <p>
|
||||
* Examples:
|
||||
* <pre>{@code
|
||||
* Examples: <pre>{@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .maxAttempts(10)
|
||||
* .exponentialBackoff(100, 2, 10000)
|
||||
@@ -50,15 +48,15 @@ import org.springframework.util.Assert;
|
||||
* <p>
|
||||
* The builder provides the following defaults:
|
||||
* <ul>
|
||||
* <li> retry policy: max attempts = 3 (initial + 2 retries) </li>
|
||||
* <li> backoff policy: no backoff (retry immediately) </li>
|
||||
* <li> exception classification: retry only on {@link Exception} and it's subclasses,
|
||||
* without traversing of causes </li>
|
||||
* <li>retry policy: max attempts = 3 (initial + 2 retries)</li>
|
||||
* <li>backoff policy: no backoff (retry immediately)</li>
|
||||
* <li>exception classification: retry only on {@link Exception} and it's subclasses,
|
||||
* without traversing of causes</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The builder supports only widely used properties of {@link RetryTemplate}. More specific
|
||||
* properties can be configured directly (after building).
|
||||
* The builder supports only widely used properties of {@link RetryTemplate}. More
|
||||
* specific properties can be configured directly (after building).
|
||||
*
|
||||
* <p>
|
||||
* Not thread safe. Building should be performed in a single thread. Also, there is no
|
||||
@@ -72,353 +70,329 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class RetryTemplateBuilder {
|
||||
|
||||
private RetryPolicy baseRetryPolicy;
|
||||
private BackOffPolicy backOffPolicy;
|
||||
private List<RetryListener> listeners;
|
||||
private BinaryExceptionClassifierBuilder classifierBuilder;
|
||||
private RetryPolicy baseRetryPolicy;
|
||||
|
||||
/* ---------------- Configure retry policy -------------- */
|
||||
private BackOffPolicy backOffPolicy;
|
||||
|
||||
/**
|
||||
* Limits maximum number of attempts to provided value.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
*
|
||||
* @param maxAttempts includes initial attempt and all retries. E.g: maxAttempts = 3 means
|
||||
* one initial attempt and two retries.
|
||||
* @see MaxAttemptsRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder maxAttempts(int maxAttempts) {
|
||||
Assert.isTrue( maxAttempts > 0, "Number of attempts should be positive");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = new MaxAttemptsRetryPolicy(maxAttempts);
|
||||
return this;
|
||||
}
|
||||
private List<RetryListener> listeners;
|
||||
|
||||
/**
|
||||
* Allows retry if there is no more than {@code timeout} millis since first attempt.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
*
|
||||
* @param timeout whole execution timeout in milliseconds
|
||||
* @see TimeoutRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder withinMillis(long timeout) {
|
||||
Assert.isTrue(timeout > 0, "Timeout should be positive");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
|
||||
timeoutRetryPolicy.setTimeout(timeout);
|
||||
this.baseRetryPolicy = timeoutRetryPolicy;
|
||||
return this;
|
||||
}
|
||||
private BinaryExceptionClassifierBuilder classifierBuilder;
|
||||
|
||||
/**
|
||||
* Allows infinite retry, do not limit attempts by number or time.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
*
|
||||
* @see TimeoutRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder infiniteRetry() {
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = new AlwaysRetryPolicy();
|
||||
return this;
|
||||
}
|
||||
/* ---------------- Configure retry policy -------------- */
|
||||
|
||||
/**
|
||||
* If flexibility of this builder is not enough for you, you can provide your own
|
||||
* {@link RetryPolicy} via this method.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
*
|
||||
* @param policy will be directly set to resulting {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplateBuilder customPolicy(RetryPolicy policy) {
|
||||
Assert.notNull(policy, "Policy should not be null");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Limits maximum number of attempts to provided value.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
* @param maxAttempts includes initial attempt and all retries. E.g: maxAttempts = 3
|
||||
* means one initial attempt and two retries.
|
||||
* @see MaxAttemptsRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder maxAttempts(int maxAttempts) {
|
||||
Assert.isTrue(maxAttempts > 0, "Number of attempts should be positive");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = new MaxAttemptsRetryPolicy(maxAttempts);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows retry if there is no more than {@code timeout} millis since first attempt.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
* @param timeout whole execution timeout in milliseconds
|
||||
* @see TimeoutRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder withinMillis(long timeout) {
|
||||
Assert.isTrue(timeout > 0, "Timeout should be positive");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
|
||||
timeoutRetryPolicy.setTimeout(timeout);
|
||||
this.baseRetryPolicy = timeoutRetryPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/* ---------------- Configure backoff policy -------------- */
|
||||
/**
|
||||
* Allows infinite retry, do not limit attempts by number or time.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
*
|
||||
* @see TimeoutRetryPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder infiniteRetry() {
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = new AlwaysRetryPolicy();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use exponential backoff policy.
|
||||
* The formula of backoff period:
|
||||
* <p>
|
||||
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
|
||||
* <p>
|
||||
* (for first attempt retryNum = 0)
|
||||
*
|
||||
* @param initialInterval in milliseconds
|
||||
* @param multiplier see the formula above
|
||||
* @param maxInterval in milliseconds
|
||||
* @see ExponentialBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder exponentialBackoff(long initialInterval, double multiplier, long maxInterval) {
|
||||
return exponentialBackoff(initialInterval, multiplier, maxInterval, false);
|
||||
}
|
||||
/**
|
||||
* If flexibility of this builder is not enough for you, you can provide your own
|
||||
* {@link RetryPolicy} via this method.
|
||||
* <p>
|
||||
* Invocation of this method does not discard default exception classification rule,
|
||||
* that is "retry only on {@link Exception} and it's subclasses".
|
||||
* @param policy will be directly set to resulting {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplateBuilder customPolicy(RetryPolicy policy) {
|
||||
Assert.notNull(policy, "Policy should not be null");
|
||||
Assert.isNull(baseRetryPolicy, "You have already selected another retry policy");
|
||||
baseRetryPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use exponential backoff policy.
|
||||
* The formula of backoff period (without randomness):
|
||||
* <p>
|
||||
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
|
||||
* <p>
|
||||
* (for first attempt retryNum = 0)
|
||||
*
|
||||
* @param initialInterval in milliseconds
|
||||
* @param multiplier see the formula above
|
||||
* @param maxInterval in milliseconds
|
||||
* @param withRandom adds some randomness to backoff intervals. For details, see
|
||||
* {@link ExponentialRandomBackOffPolicy}
|
||||
* @see ExponentialBackOffPolicy
|
||||
* @see ExponentialRandomBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder exponentialBackoff(long initialInterval, double multiplier, long maxInterval,
|
||||
boolean withRandom) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(initialInterval >= 1, "Initial interval should be >= 1");
|
||||
Assert.isTrue(multiplier > 1, "Multiplier should be > 1");
|
||||
Assert.isTrue(maxInterval > initialInterval,
|
||||
"Max interval should be > than initial interval");
|
||||
ExponentialBackOffPolicy policy = withRandom
|
||||
? new ExponentialRandomBackOffPolicy()
|
||||
: new ExponentialBackOffPolicy();
|
||||
policy.setInitialInterval(initialInterval);
|
||||
policy.setMultiplier(multiplier);
|
||||
policy.setMaxInterval(maxInterval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
/* ---------------- Configure backoff policy -------------- */
|
||||
|
||||
/**
|
||||
* Perform each retry after fixed amount of time.
|
||||
*
|
||||
* @param interval fixed interval in milliseconds
|
||||
* @see FixedBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder fixedBackoff(long interval) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(interval >= 1, "Interval should be >= 1");
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(interval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Use exponential backoff policy. The formula of backoff period:
|
||||
* <p>
|
||||
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
|
||||
* <p>
|
||||
* (for first attempt retryNum = 0)
|
||||
* @param initialInterval in milliseconds
|
||||
* @param multiplier see the formula above
|
||||
* @param maxInterval in milliseconds
|
||||
* @see ExponentialBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder exponentialBackoff(long initialInterval,
|
||||
double multiplier, long maxInterval) {
|
||||
return exponentialBackoff(initialInterval, multiplier, maxInterval, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link UniformRandomBackOffPolicy}, see it's doc for details.
|
||||
*
|
||||
* @param minInterval in milliseconds
|
||||
* @param maxInterval in milliseconds
|
||||
* @see UniformRandomBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder uniformRandomBackoff(long minInterval, long maxInterval) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(minInterval >= 1, "Min interval should be >= 1");
|
||||
Assert.isTrue(maxInterval >= 1, "Max interval should be >= 1");
|
||||
Assert.isTrue(maxInterval > minInterval,
|
||||
"Max interval should be > than min interval");
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(minInterval);
|
||||
policy.setMaxBackOffPeriod(maxInterval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Use exponential backoff policy. The formula of backoff period (without randomness):
|
||||
* <p>
|
||||
* {@code currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)}
|
||||
* <p>
|
||||
* (for first attempt retryNum = 0)
|
||||
* @param initialInterval in milliseconds
|
||||
* @param multiplier see the formula above
|
||||
* @param maxInterval in milliseconds
|
||||
* @param withRandom adds some randomness to backoff intervals. For details, see
|
||||
* {@link ExponentialRandomBackOffPolicy}
|
||||
* @see ExponentialBackOffPolicy
|
||||
* @see ExponentialRandomBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder exponentialBackoff(long initialInterval,
|
||||
double multiplier, long maxInterval, boolean withRandom) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(initialInterval >= 1, "Initial interval should be >= 1");
|
||||
Assert.isTrue(multiplier > 1, "Multiplier should be > 1");
|
||||
Assert.isTrue(maxInterval > initialInterval,
|
||||
"Max interval should be > than initial interval");
|
||||
ExponentialBackOffPolicy policy = withRandom
|
||||
? new ExponentialRandomBackOffPolicy() : new ExponentialBackOffPolicy();
|
||||
policy.setInitialInterval(initialInterval);
|
||||
policy.setMultiplier(multiplier);
|
||||
policy.setMaxInterval(maxInterval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not pause between attempts, retry immediately.
|
||||
*
|
||||
* @see NoBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder noBackoff() {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
backOffPolicy = new NoBackOffPolicy();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Perform each retry after fixed amount of time.
|
||||
* @param interval fixed interval in milliseconds
|
||||
* @see FixedBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder fixedBackoff(long interval) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(interval >= 1, "Interval should be >= 1");
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(interval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can provide your own {@link BackOffPolicy} via this method.
|
||||
*
|
||||
* @param backOffPolicy will be directly set to resulting {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplateBuilder customBackoff(BackOffPolicy backOffPolicy) {
|
||||
Assert.isNull(this.backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.notNull(backOffPolicy, "You should provide non null custom policy");
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Use {@link UniformRandomBackOffPolicy}, see it's doc for details.
|
||||
* @param minInterval in milliseconds
|
||||
* @param maxInterval in milliseconds
|
||||
* @see UniformRandomBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder uniformRandomBackoff(long minInterval, long maxInterval) {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.isTrue(minInterval >= 1, "Min interval should be >= 1");
|
||||
Assert.isTrue(maxInterval >= 1, "Max interval should be >= 1");
|
||||
Assert.isTrue(maxInterval > minInterval,
|
||||
"Max interval should be > than min interval");
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(minInterval);
|
||||
policy.setMaxBackOffPeriod(maxInterval);
|
||||
backOffPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not pause between attempts, retry immediately.
|
||||
*
|
||||
* @see NoBackOffPolicy
|
||||
*/
|
||||
public RetryTemplateBuilder noBackoff() {
|
||||
Assert.isNull(backOffPolicy, "You have already selected backoff policy");
|
||||
backOffPolicy = new NoBackOffPolicy();
|
||||
return this;
|
||||
}
|
||||
|
||||
/* ---------------- Configure exception classifier -------------- */
|
||||
/**
|
||||
* You can provide your own {@link BackOffPolicy} via this method.
|
||||
* @param backOffPolicy will be directly set to resulting {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplateBuilder customBackoff(BackOffPolicy backOffPolicy) {
|
||||
Assert.isNull(this.backOffPolicy, "You have already selected backoff policy");
|
||||
Assert.notNull(backOffPolicy, "You should provide non null custom policy");
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a throwable to the while list of retryable exceptions.
|
||||
* <p>
|
||||
* Warn: touching this method drops default {@code retryOn(Exception.class)}
|
||||
* and you should configure whole classifier from scratch.
|
||||
* <p>
|
||||
* You should select the way you want to configure exception classifier:
|
||||
* white list or black list. If you choose white list - use this method,
|
||||
* if black - use {@link #notRetryOn(Class)}
|
||||
*
|
||||
* @param throwable to be retryable (with it's subclasses)
|
||||
* @see BinaryExceptionClassifierBuilder#retryOn
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder retryOn(Class<? extends Throwable> throwable) {
|
||||
classifierBuilder().retryOn(throwable);
|
||||
return this;
|
||||
}
|
||||
/* ---------------- Configure exception classifier -------------- */
|
||||
|
||||
/**
|
||||
* Add a throwable to the while list of retryable exceptions.
|
||||
* <p>
|
||||
* Warn: touching this method drops default {@code retryOn(Exception.class)} and you
|
||||
* should configure whole classifier from scratch.
|
||||
* <p>
|
||||
* You should select the way you want to configure exception classifier: white list or
|
||||
* black list. If you choose white list - use this method, if black - use
|
||||
* {@link #notRetryOn(Class)}
|
||||
* @param throwable to be retryable (with it's subclasses)
|
||||
* @see BinaryExceptionClassifierBuilder#retryOn
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder retryOn(Class<? extends Throwable> throwable) {
|
||||
classifierBuilder().retryOn(throwable);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a throwable to the black list of retryable exceptions.
|
||||
* <p>
|
||||
* Warn: touching this method drops default {@code retryOn(Exception.class)}
|
||||
* and you should configure whole classifier from scratch.
|
||||
* <p>
|
||||
* You should select the way you want to configure exception classifier:
|
||||
* white list or black list. If you choose black list - use this method,
|
||||
* if white - use {@link #retryOn(Class)}
|
||||
*
|
||||
* @param throwable to be not retryable (with it's subclasses)
|
||||
* @see BinaryExceptionClassifierBuilder#notRetryOn
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder notRetryOn(Class<? extends Throwable> throwable) {
|
||||
classifierBuilder().notRetryOn(throwable);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Add a throwable to the black list of retryable exceptions.
|
||||
* <p>
|
||||
* Warn: touching this method drops default {@code retryOn(Exception.class)} and you
|
||||
* should configure whole classifier from scratch.
|
||||
* <p>
|
||||
* You should select the way you want to configure exception classifier: white list or
|
||||
* black list. If you choose black list - use this method, if white - use
|
||||
* {@link #retryOn(Class)}
|
||||
* @param throwable to be not retryable (with it's subclasses)
|
||||
* @see BinaryExceptionClassifierBuilder#notRetryOn
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder notRetryOn(Class<? extends Throwable> throwable) {
|
||||
classifierBuilder().notRetryOn(throwable);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose throwing a {@code new MyLogicException(new IOException())}.
|
||||
* This template will not retry on it:
|
||||
* <pre>{@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .retryOn(IOException.class)
|
||||
* .build()
|
||||
* }</pre>
|
||||
* but this will retry:
|
||||
* <pre>{@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .retryOn(IOException.class)
|
||||
* .traversingCauses()
|
||||
* .build()
|
||||
* }</pre>
|
||||
*
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder traversingCauses() {
|
||||
classifierBuilder().traversingCauses();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Suppose throwing a {@code new MyLogicException(new IOException())}. This template
|
||||
* will not retry on it: <pre>{@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .retryOn(IOException.class)
|
||||
* .build()
|
||||
* }</pre> but this will retry: <pre>{@code
|
||||
* RetryTemplate.newBuilder()
|
||||
* .retryOn(IOException.class)
|
||||
* .traversingCauses()
|
||||
* .build()
|
||||
* }</pre>
|
||||
*
|
||||
* @see BinaryExceptionClassifier
|
||||
*/
|
||||
public RetryTemplateBuilder traversingCauses() {
|
||||
classifierBuilder().traversingCauses();
|
||||
return this;
|
||||
}
|
||||
|
||||
/* ---------------- Add listeners -------------- */
|
||||
|
||||
/* ---------------- Add listeners -------------- */
|
||||
/**
|
||||
* Appends provided {@code listener} to {@link RetryTemplate}'s listener list.
|
||||
* @param listener to be appended
|
||||
* @see RetryTemplate
|
||||
* @see RetryListener
|
||||
*/
|
||||
public RetryTemplateBuilder withListener(RetryListener listener) {
|
||||
Assert.notNull(listener, "Listener should not be null");
|
||||
listenersList().add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends provided {@code listener} to {@link RetryTemplate}'s listener list.
|
||||
*
|
||||
* @param listener to be appended
|
||||
* @see RetryTemplate
|
||||
* @see RetryListener
|
||||
*/
|
||||
public RetryTemplateBuilder withListener(RetryListener listener) {
|
||||
Assert.notNull(listener, "Listener should not be null");
|
||||
listenersList().add(listener);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Appends all provided {@code listeners} to {@link RetryTemplate}'s listener list.
|
||||
* @param listeners to be appended
|
||||
* @see RetryTemplate
|
||||
* @see RetryListener
|
||||
*/
|
||||
public RetryTemplateBuilder withListeners(List<RetryListener> listeners) {
|
||||
for (final RetryListener listener : listeners) {
|
||||
Assert.notNull(listener, "Listener should not be null");
|
||||
}
|
||||
listenersList().addAll(listeners);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all provided {@code listeners} to {@link RetryTemplate}'s listener list.
|
||||
*
|
||||
* @param listeners to be appended
|
||||
* @see RetryTemplate
|
||||
* @see RetryListener
|
||||
*/
|
||||
public RetryTemplateBuilder withListeners(List<RetryListener> listeners) {
|
||||
for (final RetryListener listener : listeners) {
|
||||
Assert.notNull(listener, "Listener should not be null");
|
||||
}
|
||||
listenersList().addAll(listeners);
|
||||
return this;
|
||||
}
|
||||
/* ---------------- Building -------------- */
|
||||
|
||||
/**
|
||||
* Finish configuration and build resulting {@link RetryTemplate}. For default
|
||||
* behaviour and concurrency note see class-level doc of {@link RetryTemplateBuilder}.
|
||||
*
|
||||
* @implNote The {@code retryPolicy} of the returned {@link RetryTemplate} is always
|
||||
* an instance of {@link CompositeRetryPolicy}, that consists of one base policy, and
|
||||
* of {@link BinaryExceptionClassifierRetryPolicy}. The motivation is: whatever base
|
||||
* policy we use, exception classification is extremely recommended.
|
||||
* @return new instance of {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplate build() {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
/* ---------------- Building -------------- */
|
||||
// Exception classifier
|
||||
|
||||
/**
|
||||
* Finish configuration and build resulting {@link RetryTemplate}.
|
||||
* For default behaviour and concurrency note see class-level doc of {@link RetryTemplateBuilder}.
|
||||
*
|
||||
* @implNote
|
||||
* The {@code retryPolicy} of the returned {@link RetryTemplate} is always
|
||||
* an instance of {@link CompositeRetryPolicy}, that consists of one base
|
||||
* policy, and of {@link BinaryExceptionClassifierRetryPolicy}.
|
||||
* The motivation is: whatever base policy we use, exception classification
|
||||
* is extremely recommended.
|
||||
*
|
||||
* @return new instance of {@link RetryTemplate}
|
||||
*/
|
||||
public RetryTemplate build() {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BinaryExceptionClassifier exceptionClassifier = classifierBuilder != null
|
||||
? classifierBuilder.build()
|
||||
: BinaryExceptionClassifier.newDefaultClassifier();
|
||||
|
||||
// Exception classifier
|
||||
// Retry policy
|
||||
|
||||
BinaryExceptionClassifier exceptionClassifier = classifierBuilder != null
|
||||
? classifierBuilder.build()
|
||||
: BinaryExceptionClassifier.newDefaultClassifier();
|
||||
if (baseRetryPolicy == null) {
|
||||
baseRetryPolicy = new MaxAttemptsRetryPolicy();
|
||||
}
|
||||
|
||||
// Retry policy
|
||||
CompositeRetryPolicy finalPolicy = new CompositeRetryPolicy();
|
||||
finalPolicy.setPolicies(new RetryPolicy[] { baseRetryPolicy,
|
||||
new BinaryExceptionClassifierRetryPolicy(exceptionClassifier) });
|
||||
retryTemplate.setRetryPolicy(finalPolicy);
|
||||
|
||||
if (baseRetryPolicy == null) {
|
||||
baseRetryPolicy = new MaxAttemptsRetryPolicy();
|
||||
}
|
||||
// Backoff policy
|
||||
|
||||
CompositeRetryPolicy finalPolicy = new CompositeRetryPolicy();
|
||||
finalPolicy.setPolicies(new RetryPolicy[] {
|
||||
baseRetryPolicy,
|
||||
new BinaryExceptionClassifierRetryPolicy(exceptionClassifier)
|
||||
});
|
||||
retryTemplate.setRetryPolicy(finalPolicy);
|
||||
if (backOffPolicy == null) {
|
||||
backOffPolicy = new NoBackOffPolicy();
|
||||
}
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
|
||||
// Backoff policy
|
||||
// Listeners
|
||||
|
||||
if (backOffPolicy == null) {
|
||||
backOffPolicy = new NoBackOffPolicy();
|
||||
}
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
if (listeners != null) {
|
||||
retryTemplate.setListeners(listeners.toArray(new RetryListener[0]));
|
||||
}
|
||||
|
||||
// Listeners
|
||||
return retryTemplate;
|
||||
}
|
||||
|
||||
if (listeners != null) {
|
||||
retryTemplate.setListeners(listeners.toArray(new RetryListener[0]));
|
||||
}
|
||||
/* ---------------- Private utils -------------- */
|
||||
|
||||
return retryTemplate;
|
||||
}
|
||||
private BinaryExceptionClassifierBuilder classifierBuilder() {
|
||||
if (classifierBuilder == null) {
|
||||
classifierBuilder = new BinaryExceptionClassifierBuilder();
|
||||
}
|
||||
return classifierBuilder;
|
||||
}
|
||||
|
||||
private List<RetryListener> listenersList() {
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<RetryListener>();
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
|
||||
/* ---------------- Private utils -------------- */
|
||||
|
||||
private BinaryExceptionClassifierBuilder classifierBuilder() {
|
||||
if (classifierBuilder == null) {
|
||||
classifierBuilder = new BinaryExceptionClassifierBuilder();
|
||||
}
|
||||
return classifierBuilder;
|
||||
}
|
||||
|
||||
private List<RetryListener> listenersList() {
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<RetryListener>();
|
||||
}
|
||||
return listeners;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,67 +26,59 @@ import org.junit.Test;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* @author Aleksandr Shamukov
|
||||
* @author Aleksandr Shamukov
|
||||
*/
|
||||
public class BinaryExceptionClassifierBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testWhiteList() {
|
||||
RetryTemplate.newBuilder()
|
||||
.infiniteRetry()
|
||||
.retryOn(IOException.class)
|
||||
.uniformRandomBackoff(1000, 3000)
|
||||
.build();
|
||||
@Test
|
||||
public void testWhiteList() {
|
||||
RetryTemplate.newBuilder().infiniteRetry().retryOn(IOException.class)
|
||||
.uniformRandomBackoff(1000, 3000).build();
|
||||
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.retryOn(IOException.class)
|
||||
.retryOn(TimeoutException.class)
|
||||
.build();
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.retryOn(IOException.class).retryOn(TimeoutException.class).build();
|
||||
|
||||
Assert.assertTrue(classifier.classify(new IOException()));
|
||||
// should not retry due to traverseCauses=fasle
|
||||
Assert.assertFalse(classifier.classify(new RuntimeException(new IOException())));
|
||||
Assert.assertTrue(classifier.classify(new StreamCorruptedException()));
|
||||
Assert.assertFalse(classifier.classify(new OutOfMemoryError()));
|
||||
}
|
||||
Assert.assertTrue(classifier.classify(new IOException()));
|
||||
// should not retry due to traverseCauses=fasle
|
||||
Assert.assertFalse(classifier.classify(new RuntimeException(new IOException())));
|
||||
Assert.assertTrue(classifier.classify(new StreamCorruptedException()));
|
||||
Assert.assertFalse(classifier.classify(new OutOfMemoryError()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWhiteListWithTraverseCauses() {
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.retryOn(IOException.class)
|
||||
.retryOn(TimeoutException.class)
|
||||
.traversingCauses()
|
||||
.build();
|
||||
@Test
|
||||
public void testWhiteListWithTraverseCauses() {
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.retryOn(IOException.class).retryOn(TimeoutException.class)
|
||||
.traversingCauses().build();
|
||||
|
||||
Assert.assertTrue(classifier.classify(new IOException()));
|
||||
// should retry due to traverseCauses=true
|
||||
Assert.assertTrue(classifier.classify(new RuntimeException(new IOException())));
|
||||
Assert.assertTrue(classifier.classify(new StreamCorruptedException()));
|
||||
// should retry due to FileNotFoundException is a subclass of TimeoutException
|
||||
Assert.assertTrue(classifier.classify(new FileNotFoundException()));
|
||||
Assert.assertFalse(classifier.classify(new RuntimeException()));
|
||||
}
|
||||
Assert.assertTrue(classifier.classify(new IOException()));
|
||||
// should retry due to traverseCauses=true
|
||||
Assert.assertTrue(classifier.classify(new RuntimeException(new IOException())));
|
||||
Assert.assertTrue(classifier.classify(new StreamCorruptedException()));
|
||||
// should retry due to FileNotFoundException is a subclass of TimeoutException
|
||||
Assert.assertTrue(classifier.classify(new FileNotFoundException()));
|
||||
Assert.assertFalse(classifier.classify(new RuntimeException()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlackList() {
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.notRetryOn(Error.class)
|
||||
.notRetryOn(InterruptedException.class)
|
||||
.traversingCauses()
|
||||
.build();
|
||||
@Test
|
||||
public void testBlackList() {
|
||||
BinaryExceptionClassifier classifier = BinaryExceptionClassifier.newBuilder()
|
||||
.notRetryOn(Error.class).notRetryOn(InterruptedException.class)
|
||||
.traversingCauses().build();
|
||||
|
||||
// should not retry due to OutOfMemoryError is a subclass of Error
|
||||
Assert.assertFalse(classifier.classify(new OutOfMemoryError()));
|
||||
Assert.assertFalse(classifier.classify(new InterruptedException()));
|
||||
Assert.assertTrue(classifier.classify(new Throwable()));
|
||||
// should retry due to traverseCauses=true
|
||||
Assert.assertFalse(classifier.classify(new RuntimeException(new InterruptedException())));
|
||||
}
|
||||
// should not retry due to OutOfMemoryError is a subclass of Error
|
||||
Assert.assertFalse(classifier.classify(new OutOfMemoryError()));
|
||||
Assert.assertFalse(classifier.classify(new InterruptedException()));
|
||||
Assert.assertTrue(classifier.classify(new Throwable()));
|
||||
// should retry due to traverseCauses=true
|
||||
Assert.assertFalse(
|
||||
classifier.classify(new RuntimeException(new InterruptedException())));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFailOnNotationMix() {
|
||||
BinaryExceptionClassifier.newBuilder().retryOn(IOException.class)
|
||||
.notRetryOn(OutOfMemoryError.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFailOnNotationMix() {
|
||||
BinaryExceptionClassifier.newBuilder()
|
||||
.retryOn(IOException.class)
|
||||
.notRetryOn(OutOfMemoryError.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,26 +56,31 @@ public class BinaryExceptionClassifierTests {
|
||||
@Test
|
||||
public void testClassifyExactMatch() {
|
||||
Collection<Class<? extends Throwable>> set = Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class);
|
||||
assertTrue(new BinaryExceptionClassifier(set).classify(new IllegalStateException("Foo")));
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class);
|
||||
assertTrue(new BinaryExceptionClassifier(set)
|
||||
.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyExactMatchInCause() {
|
||||
Collection<Class<? extends Throwable>> set = Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(set);
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(
|
||||
set);
|
||||
binaryExceptionClassifier.setTraverseCauses(true);
|
||||
assertTrue(binaryExceptionClassifier.classify(new RuntimeException(new IllegalStateException("Foo"))));
|
||||
assertTrue(binaryExceptionClassifier
|
||||
.classify(new RuntimeException(new IllegalStateException("Foo"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifySubclassMatchInCause() {
|
||||
Collection<Class<? extends Throwable>> set = Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(set);
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(
|
||||
set);
|
||||
binaryExceptionClassifier.setTraverseCauses(true);
|
||||
assertTrue(binaryExceptionClassifier.classify(new RuntimeException(new FooException("Foo"))));
|
||||
assertTrue(binaryExceptionClassifier
|
||||
.classify(new RuntimeException(new FooException("Foo"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,33 +88,38 @@ public class BinaryExceptionClassifierTests {
|
||||
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>();
|
||||
map.put(IllegalStateException.class, true);
|
||||
map.put(BarException.class, false);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(map, true);
|
||||
BinaryExceptionClassifier binaryExceptionClassifier = new BinaryExceptionClassifier(
|
||||
map, true);
|
||||
binaryExceptionClassifier.setTraverseCauses(true);
|
||||
assertTrue(binaryExceptionClassifier.classify(new RuntimeException(new FooException("Foo", new BarException()))));
|
||||
assertTrue(((Map<?, ?>) new DirectFieldAccessor(binaryExceptionClassifier).getPropertyValue("classified"))
|
||||
.containsKey(FooException.class));
|
||||
assertTrue(binaryExceptionClassifier.classify(
|
||||
new RuntimeException(new FooException("Foo", new BarException()))));
|
||||
assertTrue(((Map<?, ?>) new DirectFieldAccessor(binaryExceptionClassifier)
|
||||
.getPropertyValue("classified")).containsKey(FooException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypesProvidedInConstructor() {
|
||||
classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class));
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class));
|
||||
assertTrue(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypesProvidedInConstructorWithNonDefault() {
|
||||
classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class), false);
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class),
|
||||
false);
|
||||
assertFalse(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypesProvidedInConstructorWithNonDefaultInCause() {
|
||||
classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class), false);
|
||||
.<Class<? extends Throwable>>singleton(IllegalStateException.class),
|
||||
false);
|
||||
classifier.setTraverseCauses(true);
|
||||
assertFalse(classifier.classify(new RuntimeException(new RuntimeException(new IllegalStateException("Foo")))));
|
||||
assertFalse(classifier.classify(new RuntimeException(
|
||||
new RuntimeException(new IllegalStateException("Foo")))));
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@@ -133,4 +143,5 @@ public class BinaryExceptionClassifierTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ClassifierAdapterTests {
|
||||
|
||||
@@ -68,9 +68,11 @@ public class ClassifierAdapterTests {
|
||||
public Integer getValue(String key) {
|
||||
return Integer.parseInt(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void doNothing(String key) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String doNothing(String key, int value) {
|
||||
return "foo";
|
||||
@@ -102,7 +104,7 @@ public class ClassifierAdapterTests {
|
||||
assertEquals(23, adapter.classify("23").intValue());
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testClassifyWithWrongType() {
|
||||
adapter.setDelegate(new Object() {
|
||||
@Classifier
|
||||
@@ -116,11 +118,12 @@ public class ClassifierAdapterTests {
|
||||
@SuppressWarnings("serial")
|
||||
@Test
|
||||
public void testClassifyWithClassifier() {
|
||||
adapter.setDelegate(new org.springframework.classify.Classifier<String, Integer>() {
|
||||
public Integer classify(String classifiable) {
|
||||
return Integer.valueOf(classifiable);
|
||||
}
|
||||
});
|
||||
adapter.setDelegate(
|
||||
new org.springframework.classify.Classifier<String, Integer>() {
|
||||
public Integer classify(String classifiable) {
|
||||
return Integer.valueOf(classifiable);
|
||||
}
|
||||
});
|
||||
assertEquals(23, adapter.classify("23").intValue());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,17 @@ public class ClassifierSupportTests {
|
||||
|
||||
@Test
|
||||
public void testClassifyNullIsDefault() {
|
||||
ClassifierSupport<String,String> classifier = new ClassifierSupport<String,String>("foo");
|
||||
ClassifierSupport<String, String> classifier = new ClassifierSupport<String, String>(
|
||||
"foo");
|
||||
assertEquals(classifier.classify(null), "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyRandomException() {
|
||||
ClassifierSupport<Throwable,String> classifier = new ClassifierSupport<Throwable,String>("foo");
|
||||
assertEquals(classifier.classify(new IllegalStateException("Foo")), classifier.classify(null));
|
||||
ClassifierSupport<Throwable, String> classifier = new ClassifierSupport<Throwable, String>(
|
||||
"foo");
|
||||
assertEquals(classifier.classify(new IllegalStateException("Foo")),
|
||||
classifier.classify(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.classify.PatternMatchingClassifier;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class PatternMatchingClassifierTests {
|
||||
|
||||
@@ -52,7 +52,7 @@ public class PatternMatchingClassifierTests {
|
||||
public void testCreateFromMap() {
|
||||
classifier = new PatternMatchingClassifier<String>(map);
|
||||
assertEquals("bar", classifier.classify("foo"));
|
||||
assertEquals("spam", classifier.classify("bucket"));
|
||||
assertEquals("spam", classifier.classify("bucket"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,23 +55,27 @@ public class SubclassExceptionClassifierTests {
|
||||
|
||||
@Test
|
||||
public void testClassifyExactMatch() {
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
classifier
|
||||
.setTypeMap(Collections.<Class<? extends Throwable>, String>singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifySubclassMatch() {
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(RuntimeException.class,
|
||||
"foo"));
|
||||
classifier
|
||||
.setTypeMap(Collections.<Class<? extends Throwable>, String>singletonMap(
|
||||
RuntimeException.class, "foo"));
|
||||
assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifySuperclassDoesNotMatch() {
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
assertEquals(classifier.getDefault(), classifier.classify(new RuntimeException("Foo")));
|
||||
classifier
|
||||
.setTypeMap(Collections.<Class<? extends Throwable>, String>singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
assertEquals(classifier.getDefault(),
|
||||
classifier.classify(new RuntimeException("Foo")));
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@@ -108,4 +112,5 @@ public class SubclassExceptionClassifierTests {
|
||||
public static class SubConnectException extends ConnectException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,4 +37,5 @@ public abstract class AbstractExceptionTests {
|
||||
public abstract Exception getException(String msg) throws Exception;
|
||||
|
||||
public abstract Exception getException(String msg, Throwable t) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ import org.junit.rules.ExpectedException;
|
||||
*
|
||||
*/
|
||||
public class AnyThrowTests {
|
||||
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
|
||||
@Test
|
||||
public void testRuntimeException() throws Throwable {
|
||||
expected.expect(RuntimeException.class);
|
||||
@@ -46,7 +46,7 @@ public class AnyThrowTests {
|
||||
expected.expect(Exception.class);
|
||||
AnyThrow.throwAny(new Exception("planned"));
|
||||
}
|
||||
|
||||
|
||||
private static class AnyThrow {
|
||||
|
||||
private static void throwUnchecked(Throwable e) {
|
||||
@@ -55,8 +55,9 @@ public class AnyThrowTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable> void throwAny(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
throw (E) e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +31,5 @@ public class BackOffInterruptedExceptionTests extends AbstractExceptionTests {
|
||||
public void testNothing() throws Exception {
|
||||
// fool coverage tools...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +31,5 @@ public class ExhaustedRetryExceptionTests extends AbstractExceptionTests {
|
||||
public void testNothing() throws Exception {
|
||||
// fool coverage tools...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
@SuppressWarnings("serial")
|
||||
public class ResourcelessTransactionManager extends AbstractPlatformTransactionManager {
|
||||
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition)
|
||||
throws TransactionException {
|
||||
((ResourcelessTransaction) transaction).begin();
|
||||
}
|
||||
|
||||
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
logger.debug("Committing resourceless transaction on [" + status.getTransaction() + "]");
|
||||
logger.debug("Committing resourceless transaction on [" + status.getTransaction()
|
||||
+ "]");
|
||||
}
|
||||
|
||||
protected Object doGetTransaction() throws TransactionException {
|
||||
@@ -28,32 +30,39 @@ public class ResourcelessTransactionManager extends AbstractPlatformTransactionM
|
||||
}
|
||||
else {
|
||||
@SuppressWarnings("unchecked")
|
||||
Stack<Object> stack = (Stack<Object>) TransactionSynchronizationManager.getResource(this);
|
||||
Stack<Object> stack = (Stack<Object>) TransactionSynchronizationManager
|
||||
.getResource(this);
|
||||
resources = stack;
|
||||
}
|
||||
resources.push(transaction);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
|
||||
logger.debug("Rolling back resourceless transaction on [" + status.getTransaction() + "]");
|
||||
protected void doRollback(DefaultTransactionStatus status)
|
||||
throws TransactionException {
|
||||
logger.debug("Rolling back resourceless transaction on ["
|
||||
+ status.getTransaction() + "]");
|
||||
}
|
||||
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
protected boolean isExistingTransaction(Object transaction)
|
||||
throws TransactionException {
|
||||
if (TransactionSynchronizationManager.hasResource(this)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Stack<Object> stack = (Stack<Object>) TransactionSynchronizationManager.getResource(this);
|
||||
Stack<Object> stack = (Stack<Object>) TransactionSynchronizationManager
|
||||
.getResource(this);
|
||||
return stack.size() > 1;
|
||||
}
|
||||
return ((ResourcelessTransaction) transaction).isActive();
|
||||
}
|
||||
|
||||
protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
|
||||
protected void doSetRollbackOnly(DefaultTransactionStatus status)
|
||||
throws TransactionException {
|
||||
}
|
||||
|
||||
protected void doCleanupAfterCompletion(Object transaction) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Stack<Object> list = (Stack<Object>) TransactionSynchronizationManager.getResource(this);
|
||||
Stack<Object> list = (Stack<Object>) TransactionSynchronizationManager
|
||||
.getResource(this);
|
||||
Stack<Object> resources = list;
|
||||
resources.clear();
|
||||
TransactionSynchronizationManager.unbindResource(this);
|
||||
|
||||
@@ -31,4 +31,5 @@ public class RetryExceptionTests extends AbstractExceptionTests {
|
||||
public void testNothing() throws Exception {
|
||||
// fool coverage tools...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +31,5 @@ public class TerminatedRetryExceptionTests extends AbstractExceptionTests {
|
||||
public void testNothing() throws Exception {
|
||||
// fool coverage tools...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,21 +56,24 @@ public class CircuitBreakerTests {
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
assertFalse((Boolean)service.getContext().getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
assertFalse((Boolean) service.getContext()
|
||||
.getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
try {
|
||||
service.service();
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
assertFalse((Boolean)service.getContext().getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
assertFalse((Boolean) service.getContext()
|
||||
.getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
try {
|
||||
service.service();
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
assertTrue((Boolean)service.getContext().getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
assertTrue((Boolean) service.getContext()
|
||||
.getAttribute(CircuitBreakerRetryPolicy.CIRCUIT_OPEN));
|
||||
assertEquals(3, service.getCount());
|
||||
try {
|
||||
service.service();
|
||||
@@ -84,17 +87,21 @@ public class CircuitBreakerTests {
|
||||
assertEquals(4, service.getCount());
|
||||
Advised advised = (Advised) service;
|
||||
Advisor advisor = advised.getAdvisors()[0];
|
||||
Map<?, ?> delegates = (Map<?, ?>) new DirectFieldAccessor(advisor).getPropertyValue("advice.delegates");
|
||||
Map<?, ?> delegates = (Map<?, ?>) new DirectFieldAccessor(advisor)
|
||||
.getPropertyValue("advice.delegates");
|
||||
assertTrue(delegates.size() == 1);
|
||||
Map<?, ?> methodMap = (Map<?, ?>) delegates.values().iterator().next();
|
||||
MethodInterceptor interceptor = (MethodInterceptor) methodMap
|
||||
.get(Service.class.getDeclaredMethod("expressionService"));
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(interceptor);
|
||||
assertEquals(8, accessor.getPropertyValue("retryOperations.retryPolicy.delegate.maxAttempts")) ;
|
||||
assertEquals(19000L, accessor.getPropertyValue("retryOperations.retryPolicy.openTimeout")) ;
|
||||
assertEquals(20000L, accessor.getPropertyValue("retryOperations.retryPolicy.resetTimeout")) ;
|
||||
assertEquals("#root instanceof RuntimeExpression",
|
||||
accessor.getPropertyValue("retryOperations.retryPolicy.delegate.expression.expression"));
|
||||
assertEquals(8, accessor
|
||||
.getPropertyValue("retryOperations.retryPolicy.delegate.maxAttempts"));
|
||||
assertEquals(19000L,
|
||||
accessor.getPropertyValue("retryOperations.retryPolicy.openTimeout"));
|
||||
assertEquals(20000L,
|
||||
accessor.getPropertyValue("retryOperations.retryPolicy.resetTimeout"));
|
||||
assertEquals("#root instanceof RuntimeExpression", accessor.getPropertyValue(
|
||||
"retryOperations.retryPolicy.delegate.expression.expression"));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -123,15 +130,11 @@ public class CircuitBreakerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@CircuitBreaker(maxAttemptsExpression = "#{2 * ${foo:4}}",
|
||||
openTimeoutExpression = "#{${bar:19}000}",
|
||||
resetTimeoutExpression = "#{${baz:20}000}",
|
||||
exceptionExpression = "#{#root instanceof RuntimeExpression}")
|
||||
@CircuitBreaker(maxAttemptsExpression = "#{2 * ${foo:4}}", openTimeoutExpression = "#{${bar:19}000}", resetTimeoutExpression = "#{${baz:20}000}", exceptionExpression = "#{#root instanceof RuntimeExpression}")
|
||||
public void expressionService() {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
|
||||
public RetryContext getContext() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,8 @@ public class EnableRetryTests {
|
||||
|
||||
@Test
|
||||
public void testImplementation() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
NotAnnotatedInterface service = context.getBean(NotAnnotatedInterface.class);
|
||||
service.service1();
|
||||
service.service2();
|
||||
@@ -198,7 +199,6 @@ public class EnableRetryTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExpression() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
@@ -243,7 +243,7 @@ public class EnableRetryTests {
|
||||
return target;
|
||||
}
|
||||
try {
|
||||
return target(((Advised)target).getTargetSource().getTarget());
|
||||
return target(((Advised) target).getTargetSource().getTarget());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
@@ -469,6 +469,7 @@ public class EnableRetryTests {
|
||||
protected static class ExcludesOnlyService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private RuntimeException exceptionToThrow;
|
||||
|
||||
@Retryable(exclude = IllegalStateException.class)
|
||||
@@ -485,6 +486,7 @@ public class EnableRetryTests {
|
||||
public void setExceptionToThrow(RuntimeException exceptionToThrow) {
|
||||
this.exceptionToThrow = exceptionToThrow;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class StatefulService {
|
||||
@@ -636,5 +638,4 @@ public class EnableRetryTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ public class EnableRetryWithBackoffTests {
|
||||
TestConfiguration.class);
|
||||
Service service = context.getBean(Service.class);
|
||||
service.service();
|
||||
assertEquals("[1000, 1000]", context.getBean(PeriodSleeper.class)
|
||||
.getPeriods().toString());
|
||||
assertEquals("[1000, 1000]",
|
||||
context.getBean(PeriodSleeper.class).getPeriods().toString());
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
@@ -67,8 +67,8 @@ public class EnableRetryWithBackoffTests {
|
||||
ExponentialService service = context.getBean(ExponentialService.class);
|
||||
service.service();
|
||||
assertEquals(3, service.getCount());
|
||||
assertEquals("[1000, 1100]", context.getBean(PeriodSleeper.class)
|
||||
.getPeriods().toString());
|
||||
assertEquals("[1000, 1100]",
|
||||
context.getBean(PeriodSleeper.class).getPeriods().toString());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ public class EnableRetryWithBackoffTests {
|
||||
service.service(1);
|
||||
assertEquals(3, service.getCount());
|
||||
List<Long> periods = context.getBean(PeriodSleeper.class).getPeriods();
|
||||
assertNotEquals("[1000, 1100]", context.getBean(PeriodSleeper.class)
|
||||
.getPeriods().toString());
|
||||
assertNotEquals("[1000, 1100]",
|
||||
context.getBean(PeriodSleeper.class).getPeriods().toString());
|
||||
assertTrue("Wrong periods: " + periods, periods.get(0) > 1000);
|
||||
assertTrue("Wrong periods: " + periods, periods.get(1) > 1100
|
||||
&& periods.get(1) < 1210);
|
||||
assertTrue("Wrong periods: " + periods,
|
||||
periods.get(1) > 1100 && periods.get(1) < 1210);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -204,4 +204,5 @@ public class EnableRetryWithBackoffTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public class EnableRetryWithListenersTests {
|
||||
public void overrideListener() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfigurationMultipleListeners.class);
|
||||
ServiceWithOverriddenListener service = context.getBean(ServiceWithOverriddenListener.class);
|
||||
ServiceWithOverriddenListener service = context
|
||||
.getBean(ServiceWithOverriddenListener.class);
|
||||
service.service();
|
||||
assertEquals(1, context.getBean(TestConfigurationMultipleListeners.class).count1);
|
||||
assertEquals(0, context.getBean(TestConfigurationMultipleListeners.class).count2);
|
||||
@@ -83,6 +84,7 @@ public class EnableRetryWithListenersTests {
|
||||
protected static class TestConfigurationMultipleListeners {
|
||||
|
||||
private int count1 = 0;
|
||||
|
||||
private int count2 = 0;
|
||||
|
||||
@Bean
|
||||
@@ -95,7 +97,7 @@ public class EnableRetryWithListenersTests {
|
||||
return new RetryListenerSupport() {
|
||||
@Override
|
||||
public <T, E extends Throwable> void close(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
count1++;
|
||||
}
|
||||
};
|
||||
@@ -106,7 +108,7 @@ public class EnableRetryWithListenersTests {
|
||||
return new RetryListenerSupport() {
|
||||
@Override
|
||||
public <T, E extends Throwable> void close(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
count2++;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,10 +39,10 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
@Test
|
||||
public void defaultRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new DefaultRecover(), ReflectionUtils.findMethod(DefaultRecover.class,
|
||||
"foo", String.class));
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
new DefaultRecover(),
|
||||
ReflectionUtils.findMethod(DefaultRecover.class, "foo", String.class));
|
||||
assertEquals(1, handler.recover(new Object[] { "Dave" },
|
||||
new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,8 +50,8 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new FewerArgs(), ReflectionUtils.findMethod(FewerArgs.class, "foo",
|
||||
String.class, int.class));
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
assertEquals(1, handler.recover(new Object[] { "Dave" },
|
||||
new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -66,8 +66,8 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
@Test
|
||||
public void noMatch() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new SpecificException(), ReflectionUtils.findMethod(
|
||||
SpecificException.class, "foo", String.class));
|
||||
new SpecificException(),
|
||||
ReflectionUtils.findMethod(SpecificException.class, "foo", String.class));
|
||||
expected.expect(ExhaustedRetryException.class);
|
||||
handler.recover(new Object[] { "Dave" }, new Error("Planned"));
|
||||
}
|
||||
@@ -75,60 +75,60 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
@Test
|
||||
public void specificRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new SpecificRecover(), ReflectionUtils.findMethod(SpecificRecover.class,
|
||||
"foo", String.class));
|
||||
assertEquals(2,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
new SpecificRecover(),
|
||||
ReflectionUtils.findMethod(SpecificRecover.class, "foo", String.class));
|
||||
assertEquals(2, handler.recover(new Object[] { "Dave" },
|
||||
new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inAccessibleRecoverMethods (){
|
||||
Method foo = ReflectionUtils.findMethod(InAccessibleRecover.class,
|
||||
"foo", String.class);
|
||||
public void inAccessibleRecoverMethods() {
|
||||
Method foo = ReflectionUtils.findMethod(InAccessibleRecover.class, "foo",
|
||||
String.class);
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new InAccessibleRecover(), foo);
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
assertEquals(1, handler.recover(new Object[] { "Dave" },
|
||||
new RuntimeException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specificReturnTypeRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> fooHandler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new InheritanceReturnTypeRecover(), ReflectionUtils.findMethod(InheritanceReturnTypeRecover.class,
|
||||
"foo", String.class));
|
||||
assertEquals(1,
|
||||
fooHandler.recover(new Object[] { "Aldo" }, new RuntimeException("Planned")));
|
||||
assertEquals(2,
|
||||
fooHandler.recover(new Object[] { "Aldo" }, new IllegalStateException("Planned")));
|
||||
new InheritanceReturnTypeRecover(), ReflectionUtils.findMethod(
|
||||
InheritanceReturnTypeRecover.class, "foo", String.class));
|
||||
assertEquals(1, fooHandler.recover(new Object[] { "Aldo" },
|
||||
new RuntimeException("Planned")));
|
||||
assertEquals(2, fooHandler.recover(new Object[] { "Aldo" },
|
||||
new IllegalStateException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parentReturnTypeRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> barHandler = new RecoverAnnotationRecoveryHandler<Double>(
|
||||
new InheritanceReturnTypeRecover(), ReflectionUtils.findMethod(InheritanceReturnTypeRecover.class,
|
||||
"bar", String.class));
|
||||
assertEquals(3,
|
||||
barHandler.recover(new Object[] { "Aldo" }, new RuntimeException("Planned")));
|
||||
new InheritanceReturnTypeRecover(), ReflectionUtils.findMethod(
|
||||
InheritanceReturnTypeRecover.class, "bar", String.class));
|
||||
assertEquals(3, barHandler.recover(new Object[] { "Aldo" },
|
||||
new RuntimeException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleQualifyingRecoverMethods(){
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecovers.class,
|
||||
"foo", String.class);
|
||||
public void multipleQualifyingRecoverMethods() {
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecovers.class, "foo",
|
||||
String.class);
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new MultipleQualifyingRecovers(), foo);
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Randell" }, new RuntimeException("Planned")));
|
||||
assertEquals(1, handler.recover(new Object[] { "Randell" },
|
||||
new RuntimeException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleQualifyingRecoverMethodsWithNull(){
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecovers.class,
|
||||
"foo", String.class);
|
||||
public void multipleQualifyingRecoverMethodsWithNull() {
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecovers.class, "foo",
|
||||
String.class);
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new MultipleQualifyingRecovers(), foo);
|
||||
assertEquals(1,
|
||||
@@ -137,40 +137,41 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleQualifyingRecoverMethodsReOrdered(){
|
||||
public void multipleQualifyingRecoverMethodsReOrdered() {
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecoversReOrdered.class,
|
||||
"foo", String.class);
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new MultipleQualifyingRecoversReOrdered(), foo);
|
||||
assertEquals(3,
|
||||
handler.recover(new Object[] { "Randell" }, new RuntimeException("Planned")));
|
||||
assertEquals(3, handler.recover(new Object[] { "Randell" },
|
||||
new RuntimeException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleQualifyingRecoverMethodsExtendsThrowable(){
|
||||
Method foo = ReflectionUtils.findMethod(MultipleQualifyingRecoversExtendsThrowable.class,
|
||||
"foo", String.class);
|
||||
|
||||
@Test
|
||||
public void multipleQualifyingRecoverMethodsExtendsThrowable() {
|
||||
Method foo = ReflectionUtils.findMethod(
|
||||
MultipleQualifyingRecoversExtendsThrowable.class, "foo", String.class);
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new MultipleQualifyingRecoversExtendsThrowable(), foo);
|
||||
assertEquals(2,
|
||||
handler.recover(new Object[] { "Kevin" }, new IllegalArgumentException("Planned")));
|
||||
assertEquals(3,
|
||||
handler.recover(new Object[] { "Kevin" }, new UnsupportedOperationException("Planned")));
|
||||
assertEquals(2, handler.recover(new Object[] { "Kevin" },
|
||||
new IllegalArgumentException("Planned")));
|
||||
assertEquals(3, handler.recover(new Object[] { "Kevin" },
|
||||
new UnsupportedOperationException("Planned")));
|
||||
|
||||
}
|
||||
|
||||
private static class InAccessibleRecover {
|
||||
|
||||
@Retryable
|
||||
private int foo (String n) {
|
||||
private int foo(String n) {
|
||||
throw new RuntimeException("error trying to foo('" + n + "')");
|
||||
}
|
||||
|
||||
@Recover
|
||||
private int bar(String n){
|
||||
return 1 ;
|
||||
private int bar(String n) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class DefaultRecover {
|
||||
@@ -184,9 +185,11 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
public int bar(String name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class NoArgs {
|
||||
|
||||
private Throwable cause;
|
||||
|
||||
@Retryable
|
||||
@@ -201,9 +204,11 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class SpecificRecover {
|
||||
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
return 0;
|
||||
@@ -222,6 +227,7 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
}
|
||||
|
||||
protected static class FewerArgs {
|
||||
|
||||
@Retryable
|
||||
public int foo(String name, int value) {
|
||||
return 0;
|
||||
@@ -235,6 +241,7 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
}
|
||||
|
||||
protected static class SpecificException {
|
||||
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
return 0;
|
||||
@@ -323,8 +330,8 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class MultipleQualifyingRecoversExtendsThrowable {
|
||||
|
||||
protected static class MultipleQualifyingRecoversExtendsThrowable {
|
||||
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
@@ -335,8 +342,8 @@ public class RecoverAnnotationRecoveryHandlerTests {
|
||||
public int fooRecover(IllegalArgumentException e, String name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Recover
|
||||
|
||||
@Recover
|
||||
public int barRecover(IllegalArgumentException e, String name) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple {@link Sleeper} implementation that just waits on a local Object.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class DummySleeper implements Sleeper {
|
||||
@@ -35,21 +35,22 @@ public class DummySleeper implements Sleeper {
|
||||
* @return the lastBackOff
|
||||
*/
|
||||
public long getLastBackOff() {
|
||||
return backOffs.get(backOffs.size()-1).longValue();
|
||||
return backOffs.get(backOffs.size() - 1).longValue();
|
||||
}
|
||||
|
||||
|
||||
public long[] getBackOffs() {
|
||||
long[] result = new long[backOffs.size()];
|
||||
int i = 0;
|
||||
for (Iterator<Long> iterator = backOffs.iterator(); iterator.hasNext();) {
|
||||
Long value = iterator.next();
|
||||
result[i++] =value.longValue();
|
||||
result[i++] = value.longValue();
|
||||
}
|
||||
return result ;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.retry.backoff.Sleeper#sleep(long)
|
||||
*/
|
||||
public void sleep(long backOffPeriod) throws InterruptedException {
|
||||
|
||||
@@ -63,7 +63,8 @@ public class ExponentialBackOffPolicyTests {
|
||||
strategy.setSleeper(sleeper);
|
||||
BackOffContext context = strategy.start(null);
|
||||
strategy.backOff(context);
|
||||
assertEquals(ExponentialBackOffPolicy.DEFAULT_INITIAL_INTERVAL, sleeper.getLastBackOff());
|
||||
assertEquals(ExponentialBackOffPolicy.DEFAULT_INITIAL_INTERVAL,
|
||||
sleeper.getLastBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -32,53 +32,60 @@ import org.springframework.retry.support.RetrySimulator;
|
||||
*
|
||||
*/
|
||||
public class ExponentialRandomBackOffPolicyTests {
|
||||
static final int NUM_TRIALS = 10000;
|
||||
static final int MAX_RETRIES = 6;
|
||||
|
||||
private ExponentialBackOffPolicy makeBackoffPolicy() {
|
||||
ExponentialBackOffPolicy policy = new ExponentialRandomBackOffPolicy();
|
||||
policy.setInitialInterval(50);
|
||||
policy.setMultiplier(2.0);
|
||||
policy.setMaxInterval(3000);
|
||||
return policy;
|
||||
}
|
||||
static final int NUM_TRIALS = 10000;
|
||||
static final int MAX_RETRIES = 6;
|
||||
|
||||
private SimpleRetryPolicy makeRetryPolicy() {
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||
retryPolicy.setMaxAttempts(MAX_RETRIES);
|
||||
return retryPolicy;
|
||||
}
|
||||
private ExponentialBackOffPolicy makeBackoffPolicy() {
|
||||
ExponentialBackOffPolicy policy = new ExponentialRandomBackOffPolicy();
|
||||
policy.setInitialInterval(50);
|
||||
policy.setMultiplier(2.0);
|
||||
policy.setMaxInterval(3000);
|
||||
return policy;
|
||||
}
|
||||
|
||||
private SimpleRetryPolicy makeRetryPolicy() {
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||
retryPolicy.setMaxAttempts(MAX_RETRIES);
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleBackoff() throws Exception {
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(1);
|
||||
public void testSingleBackoff() throws Exception {
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(1);
|
||||
|
||||
List<Long> sleeps = simulation.getLongestTotalSleepSequence().getSleeps();
|
||||
System.out.println("Single trial of " + backOffPolicy + ": sleeps=" + sleeps);
|
||||
assertEquals(MAX_RETRIES - 1, sleeps.size());
|
||||
long initialInterval = backOffPolicy.getInitialInterval();
|
||||
for (int i=0; i<sleeps.size(); i++) {
|
||||
long expectedMaxValue = 2*(long) (initialInterval + initialInterval * Math.max(1, Math.pow(backOffPolicy.getMultiplier(), i)));
|
||||
assertTrue("Found a sleep [" + sleeps.get(i) + "] which exceeds our max expected value of " + expectedMaxValue + " at interval " + i,
|
||||
sleeps.get(i) < expectedMaxValue);
|
||||
}
|
||||
}
|
||||
List<Long> sleeps = simulation.getLongestTotalSleepSequence().getSleeps();
|
||||
System.out.println("Single trial of " + backOffPolicy + ": sleeps=" + sleeps);
|
||||
assertEquals(MAX_RETRIES - 1, sleeps.size());
|
||||
long initialInterval = backOffPolicy.getInitialInterval();
|
||||
for (int i = 0; i < sleeps.size(); i++) {
|
||||
long expectedMaxValue = 2 * (long) (initialInterval + initialInterval
|
||||
* Math.max(1, Math.pow(backOffPolicy.getMultiplier(), i)));
|
||||
assertTrue(
|
||||
"Found a sleep [" + sleeps.get(i)
|
||||
+ "] which exceeds our max expected value of "
|
||||
+ expectedMaxValue + " at interval " + i,
|
||||
sleeps.get(i) < expectedMaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiBackOff() throws Exception {
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(NUM_TRIALS);
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(NUM_TRIALS);
|
||||
|
||||
System.out.println("Ran " + NUM_TRIALS + " backoff trials. Each trial retried " + MAX_RETRIES + " times");
|
||||
System.out.println("Policy: " + backOffPolicy);
|
||||
System.out.println("All generated backoffs:");
|
||||
System.out.println(" " + simulation.getPercentiles());
|
||||
System.out.println("Ran " + NUM_TRIALS + " backoff trials. Each trial retried "
|
||||
+ MAX_RETRIES + " times");
|
||||
System.out.println("Policy: " + backOffPolicy);
|
||||
System.out.println("All generated backoffs:");
|
||||
System.out.println(" " + simulation.getPercentiles());
|
||||
|
||||
System.out.println("Backoff frequencies:");
|
||||
System.out.print(" " + simulation.getPercentiles());
|
||||
System.out.println("Backoff frequencies:");
|
||||
System.out.print(" " + simulation.getPercentiles());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user