Initial port from Spring Batch - no tests
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
target
|
||||
.springBeans
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
|
||||
156
pom.xml
Normal file
156
pom.xml
Normal file
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>Spring Retry</name>
|
||||
<url>http://www.springsource.org</url>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<maven.test.failure.ignore>true</maven.test.failure.ignore>
|
||||
<spring.framework.version>3.0.5.RELEASE</spring.framework.version>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>strict</id>
|
||||
<properties>
|
||||
<maven.test.failure.ignore>false</maven.test.failure.ignore>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>fast</id>
|
||||
<properties>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>staging</id>
|
||||
<distributionManagement>
|
||||
<site>
|
||||
<id>spring-site-staging</id>
|
||||
<url>file:///${java.io.tmpdir}/spring-amqp/docs</url>
|
||||
</site>
|
||||
<repository>
|
||||
<id>spring-milestone-staging</id>
|
||||
<url>file:///${java.io.tmpdir}/spring-amqp/milestone</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>spring-snapshot-staging</id>
|
||||
<url>file:///${java.io.tmpdir}/spring-amqp/snapshot</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<distributionManagement>
|
||||
<!-- see 'staging' profile for dry-run deployment settings -->
|
||||
<downloadUrl>http://www.springsource.com/download/community
|
||||
</downloadUrl>
|
||||
<site>
|
||||
<id>spring-docs</id>
|
||||
<url>scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-retry/docs/${project.version}
|
||||
</url>
|
||||
</site>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<name>Spring Milestone Repository</name>
|
||||
<url>s3://maven.springframework.org/milestone</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>spring-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>s3://maven.springframework.org/snapshot</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.framework.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring.framework.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<inherited>false</inherited>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>project</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<!--forkMode>pertest</forkMode-->
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/Abstract*.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.springsource.bundlor</groupId>
|
||||
<artifactId>com.springsource.bundlor.maven</artifactId>
|
||||
<version>1.0.0.RELEASE</version>
|
||||
<inherited>true</inherited>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundlor</id>
|
||||
<goals>
|
||||
<goal>bundlor</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.classify;
|
||||
|
||||
import java.util.Collection;
|
||||
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).
|
||||
*
|
||||
* @see SubclassClassifier
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boolean> {
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the provided default value.
|
||||
*
|
||||
* @param defaultValue defaults to false
|
||||
*/
|
||||
public BinaryExceptionClassifier(boolean defaultValue) {
|
||||
super(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @param 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>();
|
||||
for (Class<? extends Throwable> type : exceptionClasses) {
|
||||
map.put(type, !getDefault());
|
||||
}
|
||||
setTypeMap(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the default value false and
|
||||
* value mapping true for the provided classes and their subclasses.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param typeMap
|
||||
*/
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap) {
|
||||
this(typeMap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier using the given classification map
|
||||
* and a default classification of false.
|
||||
*
|
||||
* @param typeMap
|
||||
*/
|
||||
public BinaryExceptionClassifier(Map<Class<? extends Throwable>, Boolean> typeMap, boolean defaultValue) {
|
||||
super(typeMap, defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.classify;
|
||||
|
||||
/**
|
||||
* Interface for a classifier. At its simplest a {@link Classifier} is just a
|
||||
* map from objects of one type to objects of another type.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface Classifier<C, T> {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
T classify(C classifiable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.classify;
|
||||
|
||||
/**
|
||||
* Base class for {@link Classifier} implementations. Provides default behaviour
|
||||
* and some convenience members, like constants.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ClassifierSupport<C, T> implements Classifier<C, T> {
|
||||
|
||||
final private T defaultValue;
|
||||
|
||||
/**
|
||||
* @param defaultValue
|
||||
*/
|
||||
public ClassifierSupport(T defaultValue) {
|
||||
super();
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the default value. This is the main extension point for
|
||||
* subclasses, so it must be able to classify null.
|
||||
*
|
||||
* @see org.springframework.commons.classify.Classifier#classify(Object)
|
||||
*/
|
||||
public T classify(C throwable) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.classify;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* 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 vale for that
|
||||
* key is returned. Otherwise returns the default value which is null by
|
||||
* default.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
private Map<Class<? extends T>, C> classified = new HashMap<Class<? extends T>, C>();
|
||||
|
||||
private C defaultValue = null;
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with null default value.
|
||||
*
|
||||
*/
|
||||
public SubclassClassifier() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue
|
||||
*/
|
||||
public SubclassClassifier(C defaultValue) {
|
||||
this(new HashMap<Class<? extends T>, C>(), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue
|
||||
*/
|
||||
public SubclassClassifier(Map<Class<? extends T>, C> typeMap, C defaultValue) {
|
||||
super();
|
||||
this.classified = new HashMap<Class<? extends T>, C>(typeMap);
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.classified = new HashMap<Class<? extends T>, C>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value from the type map whose key is the class of the given
|
||||
* Throwable, or its nearest ancestor if a subclass.
|
||||
*
|
||||
*/
|
||||
public C classify(T classifiable) {
|
||||
|
||||
if (classifiable == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends T> exceptionClass = (Class<? extends T>) classifiable.getClass();
|
||||
if (classified.containsKey(exceptionClass)) {
|
||||
return classified.get(exceptionClass);
|
||||
}
|
||||
|
||||
// check for subclasses
|
||||
Set<Class<? extends T>> classes = new TreeSet<Class<? extends T>>(new ClassComparator());
|
||||
classes.addAll(classified.keySet());
|
||||
for (Class<? extends T> cls : classes) {
|
||||
if (cls.isAssignableFrom(exceptionClass)) {
|
||||
C value = classified.get(cls);
|
||||
this.classified.put(exceptionClass, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default value supplied in the constructor (default false).
|
||||
*/
|
||||
final public C getDefault() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for classes to order by inheritance.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ClassComparator implements Comparator<Class<?>>, Serializable {
|
||||
/**
|
||||
* @return 1 if arg0 is assignable from arg1, -1 otherwise
|
||||
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public int compare(Class<?> arg0, Class<?> arg1) {
|
||||
if (arg0.isAssignableFrom(arg1)) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for batch operations. Many simple processes will be able
|
||||
* to use off-the-shelf implementations of this interface, enabling the
|
||||
* application developer to concentrate on business logic.
|
||||
*
|
||||
* @see RepeatOperations
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatCallback {
|
||||
|
||||
/**
|
||||
* Implementations return true if they can continue processing - e.g. there
|
||||
* is a data source that is not yet exhausted. Exceptions are not necessarily
|
||||
* fatal - processing might continue depending on the Exception type and the
|
||||
* implementation of the caller.
|
||||
*
|
||||
* @param context the current context passed in by the caller.
|
||||
* @return an {@link RepeatStatus} which is continuable if there is (or may
|
||||
* be) more data to process.
|
||||
* @throws Exception if there is a problem with the processing.
|
||||
*/
|
||||
RepeatStatus doInIteration(RepeatContext context) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.repeat;
|
||||
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
|
||||
/**
|
||||
* Base interface for context which controls the state and completion /
|
||||
* termination of a batch step. A new context is created for each call to the
|
||||
* {@link RepeatOperations}. Within a batch callback code can communicate via
|
||||
* the {@link AttributeAccessor} interface.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see RepeatOperations#iterate(RepeatCallback)
|
||||
*
|
||||
*/
|
||||
public interface RepeatContext extends AttributeAccessor {
|
||||
|
||||
/**
|
||||
* If batches are nested, then the inner batch will be created with the
|
||||
* outer one as a parent. This is an accessor for the parent if it exists.
|
||||
*
|
||||
* @return the parent context or null if there is none
|
||||
*/
|
||||
RepeatContext getParent();
|
||||
|
||||
/**
|
||||
* Public access to a counter for the number of operations attempted.
|
||||
*
|
||||
* @return the number of batch operations started.
|
||||
*/
|
||||
int getStartedCount();
|
||||
|
||||
/**
|
||||
* Signal to the framework that the current batch should complete normally,
|
||||
* independent of the current {@link CompletionPolicy}.
|
||||
*/
|
||||
void setCompleteOnly();
|
||||
|
||||
/**
|
||||
* Public accessor for the complete flag.
|
||||
*/
|
||||
boolean isCompleteOnly();
|
||||
|
||||
/**
|
||||
* Signal to the framework that the current batch should complete
|
||||
* abnormally, independent of the current {@link CompletionPolicy}.
|
||||
*/
|
||||
void setTerminateOnly();
|
||||
|
||||
/**
|
||||
* Public accessor for the termination flag. If this flag is set then the
|
||||
* complete flag will also be.
|
||||
*/
|
||||
boolean isTerminateOnly();
|
||||
|
||||
/**
|
||||
* Register a callback to be executed on close, associated with the
|
||||
* attribute having the given name. The {@link Runnable} callback should not
|
||||
* throw any exceptions.
|
||||
*
|
||||
* @param name the name of the attribute to associated this callback with.
|
||||
* If this attribute is removed the callback should never be called.
|
||||
* @param callback a {@link Runnable} to execute when the context is closed.
|
||||
*/
|
||||
void registerDestructionCallback(String name, Runnable callback);
|
||||
|
||||
/**
|
||||
* Allow resources to be cleared, especially in destruction callbacks.
|
||||
* Implementations should ensure that any registered destruction callbacks
|
||||
* are executed here, as long as the corresponding attribute is still
|
||||
* available.
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.repeat;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
public class RepeatException extends NestedRuntimeException {
|
||||
|
||||
public RepeatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public RepeatException(String msg, Throwable t) {
|
||||
super(msg, t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.repeat;
|
||||
|
||||
|
||||
/**
|
||||
* The main interface providing access to batch operations. The batch client is
|
||||
* the {@link RepeatCallback}, where a single item or record is processed. The
|
||||
* batch behaviour, boundary conditions, transactions etc, are dealt with by the
|
||||
* {@link RepeatOperations} in such as way that the client does not need to know
|
||||
* about them. The client may have access to framework abstractions, like
|
||||
* template data sources, but these should work the same whether they are in a
|
||||
* batch or not.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface RepeatOperations {
|
||||
|
||||
/**
|
||||
* Execute the callback repeatedly, until a decision can be made to
|
||||
* complete. The decision about how many times to execute or when to
|
||||
* complete, and what to do in the case of an error is delegated to a
|
||||
* {@link CompletionPolicy}.
|
||||
*
|
||||
* @param callback the batch callback.
|
||||
* @return the aggregate of the result of all the callback operations. An
|
||||
* indication of whether the {@link RepeatOperations} can continue
|
||||
* processing if this method is called again.
|
||||
*/
|
||||
RepeatStatus iterate(RepeatCallback callback) throws RepeatException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.repeat;
|
||||
|
||||
public enum RepeatStatus {
|
||||
|
||||
/**
|
||||
* Indicates that processing can continue.
|
||||
*/
|
||||
CONTINUABLE(true),
|
||||
/**
|
||||
* Indicates that processing is finished (either successful or unsuccessful)
|
||||
*/
|
||||
FINISHED(false);
|
||||
|
||||
private final boolean continuable;
|
||||
|
||||
private RepeatStatus(boolean continuable) {
|
||||
this.continuable = continuable;
|
||||
}
|
||||
|
||||
public static RepeatStatus continueIf(boolean continuable) {
|
||||
return continuable ? CONTINUABLE : FINISHED;
|
||||
}
|
||||
|
||||
public boolean isContinuable() {
|
||||
return this == CONTINUABLE;
|
||||
}
|
||||
|
||||
public RepeatStatus and(boolean value) {
|
||||
return value && continuable ? CONTINUABLE : FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
public class ExhaustedRetryException extends RetryException {
|
||||
|
||||
public ExhaustedRetryException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public ExhaustedRetryException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
/**
|
||||
* Callback for stateful retry after all tries are exhausted.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface RecoveryCallback<T> {
|
||||
|
||||
/**
|
||||
* @param context the current retry context
|
||||
* @return an Object that can be used to replace the callback result that
|
||||
* failed
|
||||
* @throws Exception
|
||||
*/
|
||||
T recover(RetryContext context) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
/**
|
||||
* Callback interface for an operation that can be retried using a
|
||||
* {@link RetryOperations}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface RetryCallback<T> {
|
||||
|
||||
/**
|
||||
* Execute an operation with retry semantics. Operations should generally be
|
||||
* 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 Exception if processing fails
|
||||
*/
|
||||
T doWithRetry(RetryContext context) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Signal to the framework that no more attempts should be made to try or
|
||||
* retry the current {@link RetryCallback}.
|
||||
*/
|
||||
void setExhaustedOnly();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
Throwable getLastThrowable();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
public class RetryException extends NestedRuntimeException {
|
||||
|
||||
public RetryException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public RetryException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @param context the current {@link RetryContext}.
|
||||
* @param callback the current {@link RetryCallback}.
|
||||
* @return true if the retry should proceed.
|
||||
*/
|
||||
<T> boolean open(RetryContext context, RetryCallback<T> 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.
|
||||
*
|
||||
* @param context the current {@link RetryContext}.
|
||||
* @param callback the current {@link RetryCallback}.
|
||||
* @param throwable the last exception that was thrown by the callback.
|
||||
*/
|
||||
<T> void close(RetryContext context, RetryCallback<T> 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.
|
||||
*/
|
||||
<T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
import org.springframework.commons.retry.support.DefaultRetryState;
|
||||
|
||||
/**
|
||||
* 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 Exception any {@link Exception} raised by the
|
||||
* {@link RetryCallback} upon unsuccessful retry.
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
|
||||
|
||||
/**
|
||||
* 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 Exception any {@link Exception} raised by the
|
||||
* {@link RecoveryCallback} upon unsuccessful retry.
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception;
|
||||
|
||||
/**
|
||||
* 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.<br/><br/>
|
||||
*
|
||||
* 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 Exception any {@link Exception} raised by the
|
||||
* {@link RecoveryCallback}.
|
||||
* @throws ExhaustedRetryException if the last attempt for this state has
|
||||
* already been reached
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception, 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}.
|
||||
*
|
||||
* @see #execute(RetryCallback, RetryState)
|
||||
*
|
||||
* @return the value returned by the {@link RetryCallback} upon successful
|
||||
* invocation, and that returned by the {@link RecoveryCallback} otherwise.
|
||||
* @throws Exception any {@link Exception} raised by the
|
||||
* {@link RecoveryCallback} upon unsuccessful retry.
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
|
||||
throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
public interface RetryPolicy {
|
||||
|
||||
/**
|
||||
* @param context the current retry status
|
||||
* @return true if the operation can proceed
|
||||
*/
|
||||
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.
|
||||
* @param parent the parent context if we are in a nested retry.
|
||||
*
|
||||
* @return a {@link RetryContext} object specific to this manager.
|
||||
*
|
||||
*/
|
||||
RetryContext open(RetryContext parent);
|
||||
|
||||
/**
|
||||
* @param context a retry status created by the
|
||||
* {@link #open(RetryContext)} method of this manager.
|
||||
*/
|
||||
void close(RetryContext context);
|
||||
|
||||
/**
|
||||
* Called once per retry attempt, after the callback fails.
|
||||
*
|
||||
* @param context the current status object.
|
||||
*
|
||||
*/
|
||||
void registerThrowable(RetryContext context, Throwable throwable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @param exception the exception that caused a retry attempt to fail
|
||||
* @return true if this exception should cause a rollback
|
||||
*/
|
||||
boolean rollbackFor(Throwable exception);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* @return the number of completed retry attempts (successful or not).
|
||||
*/
|
||||
int getCompleteCount();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the number of errors detected.
|
||||
*/
|
||||
int getErrorCount();
|
||||
|
||||
/**
|
||||
* 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 an identifier for the retry block for reporting purposes.
|
||||
*
|
||||
* @return an identifier for the block.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry;
|
||||
|
||||
public class TerminatedRetryException extends RetryException {
|
||||
|
||||
public TerminatedRetryException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public TerminatedRetryException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface BackOffContext {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
import org.springframework.commons.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)}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @since 2.1
|
||||
*/
|
||||
public class BackOffInterruptedException extends RetryException {
|
||||
|
||||
public BackOffInterruptedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public BackOffInterruptedException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Strategy interface to control back off between attempts in a single
|
||||
* {@link org.springframework.commons.retry.support.RetryTemplate retry operation}.
|
||||
* <p/> 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.
|
||||
* <p/> 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. <p/> Each back off process is handled via a call to
|
||||
* {@link #backOff}. The
|
||||
* {@link org.springframework.commons.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.
|
||||
*
|
||||
* @param context the current retry context, which might contain information
|
||||
* that we can use to decide how to proceed.
|
||||
* @return the implementation-specific {@link BackOffContext} or '<code>null</code>'.
|
||||
*/
|
||||
BackOffContext start(RetryContext context);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that increases the back off period
|
||||
* for each retry attempt in a given set using the {@link Math#exp(double)
|
||||
* exponential} function.
|
||||
* <p/>
|
||||
* This implementation is thread-safe and suitable for concurrent access.
|
||||
* Modifications to the configuration do not affect any retry sets that are
|
||||
* already in progress.
|
||||
* <p/>
|
||||
* The {@link #setInitialInterval(long)} property controls the initial value
|
||||
* passed to {@link Math#exp(double)} and the {@link #setMultiplier(double)}
|
||||
* property controls by how much this value is increased for each subsequent
|
||||
* attempt.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class ExponentialBackOffPolicy implements BackOffPolicy {
|
||||
|
||||
/**
|
||||
* The default 'initialInterval' value - 100 millisecs. Coupled with the
|
||||
* default 'multiplier' value this gives a useful initial spread of pauses
|
||||
* for 1-5 retries.
|
||||
*/
|
||||
public static final long DEFAULT_INITIAL_INTERVAL = 100L;
|
||||
|
||||
/**
|
||||
* The default maximum backoff time (30 seconds).
|
||||
*/
|
||||
public static final long DEFAULT_MAX_INTERVAL = 30000L;
|
||||
|
||||
/**
|
||||
* The default 'multiplier' value - value 2 (100% increase per backoff).
|
||||
*/
|
||||
public static final double DEFAULT_MULTIPLIER = 2;
|
||||
|
||||
/**
|
||||
* The initial sleep interval.
|
||||
*/
|
||||
private volatile long initialInterval = DEFAULT_INITIAL_INTERVAL;
|
||||
|
||||
/**
|
||||
* The maximum value of the backoff period in milliseconds.
|
||||
*/
|
||||
private volatile long maxInterval = DEFAULT_MAX_INTERVAL;
|
||||
|
||||
/**
|
||||
* The value to increment the exp seed with for each retry attempt.
|
||||
*/
|
||||
private volatile double multiplier = DEFAULT_MULTIPLIER;
|
||||
|
||||
private Sleeper sleeper = new ObjectWaitSleeper();
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Sleeper} strategy.
|
||||
* @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}.
|
||||
*/
|
||||
public void setSleeper(Sleeper sleeper) {
|
||||
this.sleeper = sleeper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial sleep interval value. Default is <code>100</code>
|
||||
* millisecond. Cannot be set to a value less than one.
|
||||
*/
|
||||
public void setInitialInterval(long initialInterval) {
|
||||
this.initialInterval = (initialInterval > 1 ? initialInterval : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the multiplier value. Default is '<code>2.0</code>'. Hint: do not use
|
||||
* values much in excess of 1.0 (or the backoff will get very long very
|
||||
* fast).
|
||||
*/
|
||||
public void setMultiplier(double multiplier) {
|
||||
this.multiplier = (multiplier > 1.0 ? multiplier : 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for maximum back off period. Default is 30000 (30 seconds). the
|
||||
* value will 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) {
|
||||
this.maxInterval = maxInterval > 0 ? maxInterval : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial period to sleep on the first backoff.
|
||||
* @return the initial interval
|
||||
*/
|
||||
public long getInitialInterval() {
|
||||
return initialInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum interval to sleep for. Defaults to 30 seconds.
|
||||
*
|
||||
* @return the maximum interval.
|
||||
*/
|
||||
public long getMaxInterval() {
|
||||
return maxInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The multiplier to use to generate the next backoff interval from the
|
||||
* last.
|
||||
*
|
||||
* @return the multiplier in use
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link BackOffContext} configured with the
|
||||
* 'expSeed' and 'increment' values.
|
||||
*/
|
||||
public BackOffContext start(RetryContext context) {
|
||||
return new ExponentialBackOffContext(this.initialInterval, this.multiplier, this.maxInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause for a length of time equal to '
|
||||
* <code>exp(backOffContext.expSeed)</code>'.
|
||||
*/
|
||||
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
|
||||
ExponentialBackOffContext context = (ExponentialBackOffContext) backOffContext;
|
||||
try {
|
||||
sleeper.sleep(context.getSleepAndIncrement());
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExponentialBackOffContext implements BackOffContext {
|
||||
|
||||
private final double multiplier;
|
||||
|
||||
private long interval;
|
||||
|
||||
private long maxInterval;
|
||||
|
||||
public ExponentialBackOffContext(long expSeed, double multiplier, long maxInterval) {
|
||||
this.interval = expSeed;
|
||||
this.multiplier = multiplier;
|
||||
this.maxInterval = maxInterval;
|
||||
}
|
||||
|
||||
public synchronized long getSleepAndIncrement() {
|
||||
long sleep = this.interval;
|
||||
if (sleep > maxInterval) {
|
||||
sleep = (long) maxInterval;
|
||||
}
|
||||
else {
|
||||
this.interval *= this.multiplier;
|
||||
}
|
||||
return sleep;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(getClass()) + "[initialInterval=" + initialInterval + ", multiplier="
|
||||
+ multiplier + ", maxInterval=" + maxInterval + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a fixed period of
|
||||
* time before continuing. A pause is implemented using {@link Thread#sleep(long)}.
|
||||
* <p/> {@link #setBackOffPeriod(long)} is thread-safe and it is safe to call
|
||||
* {@link #setBackOffPeriod} 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 FixedBackOffPolicy extends StatelessBackOffPolicy {
|
||||
|
||||
/**
|
||||
* Default back off period - 1000ms.
|
||||
*/
|
||||
private static final long DEFAULT_BACK_OFF_PERIOD = 1000L;
|
||||
|
||||
/**
|
||||
* The back off period in milliseconds. Defaults to 1000ms.
|
||||
*/
|
||||
private volatile long backOffPeriod = DEFAULT_BACK_OFF_PERIOD;
|
||||
|
||||
|
||||
private Sleeper sleeper = new ObjectWaitSleeper();
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Sleeper} strategy.
|
||||
* @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}.
|
||||
*/
|
||||
public void setSleeper(Sleeper sleeper) {
|
||||
this.sleeper = sleeper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 1000ms.
|
||||
*/
|
||||
public void setBackOffPeriod(long backOffPeriod) {
|
||||
this.backOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The backoff period in milliseconds.
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getBackOffPeriod() {
|
||||
return backOffPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause for the {@link #setBackOffPeriod(long)}.
|
||||
* @throws BackOffInterruptedException if interrupted during sleep.
|
||||
*/
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
sleeper.sleep(backOffPeriod);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.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.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @since 2.1
|
||||
*/
|
||||
public class NoBackOffPolicy extends StatelessBackOffPolicy {
|
||||
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
/**
|
||||
* Simple {@link Sleeper} implementation that just waits on a local Object.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ObjectWaitSleeper implements Sleeper {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.commons.retry.backoff.Sleeper#sleep(long)
|
||||
*/
|
||||
public void sleep(long backOffPeriod) throws InterruptedException {
|
||||
Object mutex = new Object();
|
||||
synchronized (mutex) {
|
||||
mutex.wait(backOffPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
/**
|
||||
* Strategy interface for backoff policies to delegate the pausing of execution.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface Sleeper {
|
||||
|
||||
/**
|
||||
* Pause for the specified period using whatever means available.
|
||||
*
|
||||
* @param backOffPeriod
|
||||
*/
|
||||
void sleep(long backOffPeriod) throws InterruptedException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.backoff;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public final void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
|
||||
doBackOff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns '<code>null</code>'. Subclasses can add behaviour, e.g.
|
||||
* initial sleep before first attempt.
|
||||
*/
|
||||
public BackOffContext start(RetryContext status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-classes should implement this method to perform the actual back off.
|
||||
*/
|
||||
protected abstract void doBackOff() throws BackOffInterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.context;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.core.AttributeAccessorSupport;
|
||||
|
||||
public class RetryContextSupport extends AttributeAccessorSupport implements RetryContext {
|
||||
|
||||
private boolean terminate = false;
|
||||
|
||||
private int count;
|
||||
|
||||
private Throwable lastException;
|
||||
|
||||
private RetryContext parent;
|
||||
|
||||
public RetryContextSupport(RetryContext parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public RetryContext getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public boolean isExhaustedOnly() {
|
||||
return terminate;
|
||||
}
|
||||
|
||||
public void setExhaustedOnly() {
|
||||
terminate = true;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public Throwable getLastThrowable() {
|
||||
return lastException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exception for the public interface {@link RetryContext}, and
|
||||
* also increment the retry count if the throwable is non-null.<br/>
|
||||
*
|
||||
* 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.<br/>
|
||||
*
|
||||
* 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;
|
||||
if (throwable != null)
|
||||
count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[RetryContext: count=%d, lastException=%s, exhausted=%b]", count, lastException, terminate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.interceptor;
|
||||
|
||||
/**
|
||||
* Interface that allows method parameters to be identified and tagged by a
|
||||
* unique key.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param item the current item.
|
||||
* @return a unique identifier.
|
||||
*/
|
||||
Object getKey(Object[] item);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.interceptor;
|
||||
|
||||
|
||||
/**
|
||||
* Strategy interface for recovery action when processing of an item fails.<br/>
|
||||
*
|
||||
* @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.
|
||||
* @return the value to be returned to the caller
|
||||
*/
|
||||
T recover(Object[] args, Throwable cause);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.interceptor;
|
||||
|
||||
/**
|
||||
* 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'.
|
||||
*
|
||||
* @param args the current method arguments.
|
||||
* @return true if the item is known to have never been processed before.
|
||||
*/
|
||||
boolean isNew(Object[] args);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.interceptor;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.ProxyMethodInvocation;
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryOperations;
|
||||
import org.springframework.commons.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to
|
||||
* a method on a service if it fails. The injected {@link RetryOperations} is
|
||||
* used to control the number of retries. By default it will retry a fixed
|
||||
* number of times, according to the defaults in {@link RetryTemplate}.<br/>
|
||||
*
|
||||
* Hint about transaction boundaries. If you want to retry a failed transaction
|
||||
* you need to make sure that the transaction boundary is inside the retry,
|
||||
* otherwise the successful attempt will roll back with the whole transaction.
|
||||
* If the method being intercepted is also transactional, then use the ordering
|
||||
* hints in the advice declarations to ensure that this one is before the
|
||||
* transaction interceptor in the advice chain.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
|
||||
private RetryOperations retryOperations = new RetryTemplate();
|
||||
|
||||
public void setRetryOperations(RetryOperations retryTemplate) {
|
||||
Assert.notNull(retryTemplate, "'retryOperations' cannot be null.");
|
||||
this.retryOperations = retryTemplate;
|
||||
}
|
||||
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
|
||||
return this.retryOperations.execute(new RetryCallback<Object>() {
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
|
||||
/*
|
||||
* If we don't copy the invocation carefully it won't keep a
|
||||
* reference to the other interceptors in the chain. We don't
|
||||
* have a choice here but to specialise to
|
||||
* ReflectiveMethodInvocation (but how often would another
|
||||
* implementation come along?).
|
||||
*/
|
||||
if (invocation instanceof ProxyMethodInvocation) {
|
||||
try {
|
||||
return ((ProxyMethodInvocation) invocation)
|
||||
.invocableClone().proceed();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.interceptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.commons.retry.ExhaustedRetryException;
|
||||
import org.springframework.commons.retry.RecoveryCallback;
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryOperations;
|
||||
import org.springframework.commons.retry.RetryState;
|
||||
import org.springframework.commons.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.commons.retry.support.DefaultRetryState;
|
||||
import org.springframework.commons.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to
|
||||
* a method on a service if it fails. The argument to the service method is
|
||||
* treated as an item to be remembered in case the call fails. So the retry
|
||||
* operation is stateful, and the item that failed is tracked by its unique key
|
||||
* (via {@link MethodArgumentsKeyGenerator}) until the retry is exhausted, at
|
||||
* which point the {@link MethodInvocationRecoverer} is called.<br/>
|
||||
*
|
||||
* The main use case for this is where the service is transactional, via a
|
||||
* transaction interceptor on the interceptor chain. In this case the retry (and
|
||||
* recovery on exhausted) always happens in a new transaction.<br/>
|
||||
*
|
||||
* The injected {@link RetryOperations} is used to control the number of
|
||||
* retries. By default it will retry a fixed number of times, according to the
|
||||
* defaults in {@link RetryTemplate}.<br/>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
|
||||
private transient Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private MethodArgumentsKeyGenerator keyGenerator;
|
||||
|
||||
private MethodInvocationRecoverer<? extends Object> recoverer;
|
||||
|
||||
private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
|
||||
|
||||
private RetryOperations retryOperations;
|
||||
|
||||
public void setRetryOperations(RetryOperations retryTemplate) {
|
||||
Assert.notNull(retryTemplate, "'retryOperations' cannot be null.");
|
||||
this.retryOperations = retryTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public StatefulRetryOperationsInterceptor() {
|
||||
super();
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new NeverRetryPolicy());
|
||||
retryOperations = retryTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link MethodInvocationRecoverer} to use if the
|
||||
* retry is exhausted. The recoverer should be able to return an object of
|
||||
* the same type as the target object because its return value will be used
|
||||
* to return to the caller in the case of a recovery.<br/>
|
||||
*
|
||||
* If no recoverer is set then an exhausted retry will result in an
|
||||
* {@link ExhaustedRetryException}.
|
||||
*
|
||||
* @param recoverer the {@link MethodInvocationRecoverer} to set
|
||||
*/
|
||||
public void setRecoverer(MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
this.recoverer = recoverer;
|
||||
}
|
||||
|
||||
public void setKeyGenerator(MethodArgumentsKeyGenerator keyGenerator) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this
|
||||
* if the arguments to the intercepted method can be inspected to find out
|
||||
* if they have never been processed before.
|
||||
* @param newMethodArgumentsIdentifier the
|
||||
* {@link NewMethodArgumentsIdentifier} to set
|
||||
*/
|
||||
public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the method invocation in a stateful retry with the policy and other
|
||||
* helpers provided. If there is a failure the exception will generally be
|
||||
* re-thrown. The only time it is not re-thrown is when retry is exhausted
|
||||
* and the recovery path is taken (though the
|
||||
* {@link MethodInvocationRecoverer} provided if there is one). In that case
|
||||
* the value returned from the method invocation will be the value returned
|
||||
* by the recoverer (so the return type for that should be the same as the
|
||||
* intercepted method).
|
||||
*
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
* @see MethodInvocationRecoverer#recover(Object[], Throwable)
|
||||
*
|
||||
* @throws ExhaustedRetryException if the retry is exhausted and no
|
||||
* {@link MethodInvocationRecoverer} is provided.
|
||||
*/
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
|
||||
logger.debug("Executing proxied method in stateful retry: " + invocation.getStaticPart() + "("
|
||||
+ ObjectUtils.getIdentityHexString(invocation) + ")");
|
||||
|
||||
Object[] args = invocation.getArguments();
|
||||
Assert.state(args.length > 0, "Stateful retry applied to method that takes no arguments: "
|
||||
+ invocation.getStaticPart());
|
||||
Object arg = args;
|
||||
if (args.length == 1) {
|
||||
arg = args[0];
|
||||
}
|
||||
final Object item = arg;
|
||||
|
||||
RetryState retryState = new DefaultRetryState(keyGenerator != null ? keyGenerator.getKey(args) : item,
|
||||
newMethodArgumentsIdentifier != null ? newMethodArgumentsIdentifier.isNew(args) : false);
|
||||
|
||||
Object result = retryOperations.execute(new MethodInvocationRetryCallback(invocation),
|
||||
new ItemRecovererCallback(args, recoverer), retryState);
|
||||
|
||||
logger.debug("Exiting proxied method in stateful retry with result: (" + result + ")");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static final class MethodInvocationRetryCallback implements RetryCallback<Object> {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final MethodInvocation invocation;
|
||||
|
||||
/**
|
||||
* @param invocation
|
||||
*/
|
||||
private MethodInvocationRetryCallback(MethodInvocation invocation) {
|
||||
this.invocation = invocation;
|
||||
}
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
try {
|
||||
return invocation.proceed();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Error e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static final class ItemRecovererCallback implements RecoveryCallback<Object> {
|
||||
|
||||
private final Object[] args;
|
||||
|
||||
private final MethodInvocationRecoverer<? extends Object> recoverer;
|
||||
|
||||
/**
|
||||
* @param args the item that failed.
|
||||
*/
|
||||
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
this.args = Arrays.asList(args).toArray();
|
||||
this.recoverer = recoverer;
|
||||
}
|
||||
|
||||
public Object recover(RetryContext context) {
|
||||
if (recoverer != null) {
|
||||
return recoverer.recover(args, context.getLastThrowable());
|
||||
}
|
||||
throw new ExhaustedRetryException("Retry was exhausted but there was no recovery path.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.listener;
|
||||
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryListener;
|
||||
|
||||
/**
|
||||
* Empty method implementation of {@link RetryListener}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RetryListenerSupport implements RetryListener {
|
||||
|
||||
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
public <T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AlwaysRetryPolicy extends NeverRetryPolicy {
|
||||
|
||||
/**
|
||||
* Always returns true.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.retry.context.RetryContextSupport;
|
||||
|
||||
/**
|
||||
* A {@link RetryPolicy} that composes a list of other policies and delegates
|
||||
* calls to them in order.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class CompositeRetryPolicy implements RetryPolicy {
|
||||
|
||||
RetryPolicy[] policies = new RetryPolicy[0];
|
||||
|
||||
/**
|
||||
* Setter for policies.
|
||||
*
|
||||
* @param 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, oetherwise return
|
||||
* true.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
|
||||
RetryPolicy[] policies = ((CompositeRetryContext) context).policies;
|
||||
for (int i = 0; i < contexts.length; i++) {
|
||||
if (!policies[i].canRetry(contexts[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public void close(RetryContext context) {
|
||||
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
|
||||
RetryPolicy[] policies = ((CompositeRetryContext) context).policies;
|
||||
RuntimeException exception = null;
|
||||
for (int i = 0; i < contexts.length; i++) {
|
||||
try {
|
||||
policies[i].close(contexts[i]);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (exception == null) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context that copies the existing policies and keeps a list
|
||||
* of the contexts from each one.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
List<RetryContext> list = new ArrayList<RetryContext>();
|
||||
for (int i = 0; i < policies.length; i++) {
|
||||
list.add(policies[i].open(parent));
|
||||
}
|
||||
return new CompositeRetryContext(parent, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policies that were in operation when the context was
|
||||
* created.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
|
||||
RetryPolicy[] policies = ((CompositeRetryContext) context).policies;
|
||||
for (int i = 0; i < contexts.length; i++) {
|
||||
policies[i].registerThrowable(contexts[i], throwable);
|
||||
}
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
}
|
||||
|
||||
private class CompositeRetryContext extends RetryContextSupport {
|
||||
RetryContext[] contexts;
|
||||
|
||||
RetryPolicy[] policies;
|
||||
|
||||
public CompositeRetryContext(RetryContext parent, List<RetryContext> contexts) {
|
||||
super(parent);
|
||||
this.contexts = contexts.toArray(new RetryContext[0]);
|
||||
this.policies = CompositeRetryPolicy.this.policies;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.commons.classify.Classifier;
|
||||
import org.springframework.commons.classify.ClassifierSupport;
|
||||
import org.springframework.commons.classify.SubclassClassifier;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
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.
|
||||
*/
|
||||
public void setPolicyMap(Map<Class<? extends Throwable>, RetryPolicy> policyMap) {
|
||||
SubclassClassifier<Throwable, RetryPolicy> subclassClassifier = new SubclassClassifier<Throwable, RetryPolicy>(
|
||||
policyMap, (RetryPolicy) new NeverRetryPolicy());
|
||||
this.exceptionClassifier = subclassClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policy currently activated in the context.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
RetryPolicy policy = (RetryPolicy) context;
|
||||
return policy.canRetry(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policy currently activated in the context.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public void close(RetryContext context) {
|
||||
RetryPolicy policy = (RetryPolicy) context;
|
||||
policy.close(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an active context that proxies a retry policy by chosing a target
|
||||
* from the policy map.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new ExceptionClassifierRetryContext(parent, exceptionClassifier).open(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the policy currently activated in the context.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#registerThrowable(org.springframework.commons.retry.RetryContext,
|
||||
* Throwable)
|
||||
*/
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
RetryPolicy policy = (RetryPolicy) context;
|
||||
policy.registerThrowable(context, throwable);
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
}
|
||||
|
||||
private static class ExceptionClassifierRetryContext extends RetryContextSupport implements RetryPolicy {
|
||||
|
||||
final private Classifier<Throwable, RetryPolicy> exceptionClassifier;
|
||||
|
||||
// Dynamic: depends on the latest exception:
|
||||
private RetryPolicy policy;
|
||||
|
||||
// Dynamic: depends on the policy:
|
||||
private RetryContext context;
|
||||
|
||||
final private Map<RetryPolicy, RetryContext> contexts = new HashMap<RetryPolicy, RetryContext>();
|
||||
|
||||
public ExceptionClassifierRetryContext(RetryContext parent,
|
||||
Classifier<Throwable, RetryPolicy> exceptionClassifier) {
|
||||
super(parent);
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
public boolean canRetry(RetryContext context) {
|
||||
if (this.context == null) {
|
||||
// there was no error yet
|
||||
return true;
|
||||
}
|
||||
return policy.canRetry(this.context);
|
||||
}
|
||||
|
||||
public void close(RetryContext context) {
|
||||
// Only close those policies that have been used (opened):
|
||||
for (RetryPolicy policy : contexts.keySet()) {
|
||||
policy.close(getContext(policy, context.getParent()));
|
||||
}
|
||||
}
|
||||
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
policy = exceptionClassifier.classify(throwable);
|
||||
Assert.notNull(policy, "Could not locate policy for exception=[" + throwable + "].");
|
||||
this.context = getContext(policy, context.getParent());
|
||||
policy.registerThrowable(this.context, throwable);
|
||||
}
|
||||
|
||||
private RetryContext getContext(RetryPolicy policy, RetryContext parent) {
|
||||
RetryContext context = contexts.get(policy);
|
||||
if (context == null) {
|
||||
context = policy.open(parent);
|
||||
contexts.put(policy, context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 4096;
|
||||
|
||||
private Map<Object, RetryContext> map = Collections.synchronizedMap(new HashMap<Object, RetryContext>());
|
||||
|
||||
private int capacity;
|
||||
|
||||
/**
|
||||
* Create a {@link MapRetryContextCache} with default capacity.
|
||||
*/
|
||||
public MapRetryContextCache() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultCapacity
|
||||
*/
|
||||
public MapRetryContextCache(int defaultCapacity) {
|
||||
super();
|
||||
this.capacity = defaultCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
public RetryContext get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
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?");
|
||||
}
|
||||
map.put(key, context);
|
||||
}
|
||||
|
||||
public void remove(Object key) {
|
||||
map.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class NeverRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* Returns false after the first exception. So there is always one try, and
|
||||
* then the retry is prevented.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
return !((NeverRetryContext) context).isFinished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public void close(RetryContext context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a context that can respond to early termination requests, but does
|
||||
* nothing else.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new NeverRetryContext(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the throwable available for downstream use through the context.
|
||||
* @see org.springframework.commons.retry.RetryPolicy#registerThrowable(org.springframework.commons.retry.RetryContext,
|
||||
* Throwable)
|
||||
*/
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
((NeverRetryContext) context).setFinished();
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public void setFinished() {
|
||||
this.finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import org.springframework.commons.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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RetryCacheCapacityExceededException extends RetryException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a message.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public RetryCacheCapacityExceededException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a message and nested exception.
|
||||
*
|
||||
* @param msg the exception message.
|
||||
*
|
||||
*/
|
||||
public RetryCacheCapacityExceededException(String msg, Throwable nested) {
|
||||
super(msg, nested);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Simple map-like abstraction for stateful retry policies to use when storing
|
||||
* and retrieving {@link RetryContext} instances.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @see MapRetryContextCache
|
||||
*
|
||||
*/
|
||||
public interface RetryContextCache {
|
||||
|
||||
RetryContext get(Object key);
|
||||
|
||||
void put(Object key, RetryContext context) throws RetryCacheCapacityExceededException;
|
||||
|
||||
void remove(Object key);
|
||||
|
||||
boolean containsKey(Object key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.commons.classify.BinaryExceptionClassifier;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.retry.context.RetryContextSupport;
|
||||
|
||||
/**
|
||||
*
|
||||
* 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));
|
||||
* retryTemplate.execute(callback);
|
||||
* </pre>
|
||||
*
|
||||
* will execute the callback at least once, and as many as 3 times.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Rob Harrop
|
||||
*
|
||||
*/
|
||||
public class SimpleRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* The default limit to the number of attempts for a new policy.
|
||||
*/
|
||||
public final static int DEFAULT_MAX_ATTEMPTS = 3;
|
||||
|
||||
private volatile int maxAttempts;
|
||||
|
||||
private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the default number of retry
|
||||
* attempts.
|
||||
*/
|
||||
public SimpleRetryPolicy() {
|
||||
this(DEFAULT_MAX_ATTEMPTS, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SimpleRetryPolicy} with the specified number of retry
|
||||
* attempts.
|
||||
*
|
||||
* @param maxAttempts
|
||||
* @param retryableExceptions
|
||||
*/
|
||||
public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
|
||||
super();
|
||||
this.maxAttempts = maxAttempts;
|
||||
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for retry attempts.
|
||||
*
|
||||
* @param retryAttempts the number of attempts before a retry becomes
|
||||
* impossible.
|
||||
*/
|
||||
public void setMaxAttempts(int retryAttempts) {
|
||||
this.maxAttempts = retryAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of retry attempts before failure.
|
||||
*
|
||||
* @return the maximum number of attempts
|
||||
*/
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for retryable operation based on the status.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*
|
||||
* @return true if the last exception was retryable and the number of
|
||||
* attempts so far is less than the limit.
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
Throwable t = context.getLastThrowable();
|
||||
return (t == null || retryForException(t)) && context.getRetryCount() < maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.commons.retry.RetryPolicy#close(RetryContext)
|
||||
*/
|
||||
public void close(RetryContext status) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status with another attempted retry and the latest exception.
|
||||
*
|
||||
* @see RetryPolicy#registerThrowable(RetryContext, Throwable)
|
||||
*/
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
SimpleRetryContext simpleContext = ((SimpleRetryContext) context);
|
||||
simpleContext.registerThrowable(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.commons.retry.RetryPolicy#open(RetryContext)
|
||||
*/
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new SimpleRetryContext(parent);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private boolean retryForException(Throwable ex) {
|
||||
return retryableClassifier.classify(ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.commons.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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 4096;
|
||||
|
||||
private Map<Object, SoftReference<RetryContext>> map = Collections
|
||||
.synchronizedMap(new HashMap<Object, SoftReference<RetryContext>>());
|
||||
|
||||
private int capacity;
|
||||
|
||||
/**
|
||||
* Create a {@link SoftReferenceMapRetryContextCache} with default capacity.
|
||||
*/
|
||||
public SoftReferenceMapRetryContextCache() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultCapacity
|
||||
*/
|
||||
public SoftReferenceMapRetryContextCache(int defaultCapacity) {
|
||||
super();
|
||||
this.capacity = defaultCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
if (!map.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
if (map.get(key).get() == null) {
|
||||
// our reference was garbage collected
|
||||
map.remove(key);
|
||||
}
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
public RetryContext get(Object key) {
|
||||
return map.get(key).get();
|
||||
}
|
||||
|
||||
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?");
|
||||
}
|
||||
map.put(key, new SoftReference<RetryContext>(context));
|
||||
}
|
||||
|
||||
public void remove(Object key) {
|
||||
map.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.policy;
|
||||
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.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)}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TimeoutRetryPolicy implements RetryPolicy {
|
||||
|
||||
/**
|
||||
* Default value for timeout (milliseconds).
|
||||
*/
|
||||
public static final long DEFAULT_TIMEOUT = 1000;
|
||||
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Setter for timeout in milliseconds. Default is {@link #DEFAULT_TIMEOUT}.
|
||||
* @param timeout
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the timeout.
|
||||
*
|
||||
* @return the timeout in milliseconds
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only permits a retry if the timeout has not expired. Does not check the
|
||||
* exception at all.
|
||||
*
|
||||
* @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
|
||||
*/
|
||||
public boolean canRetry(RetryContext context) {
|
||||
return ((TimeoutRetryContext) context).isAlive();
|
||||
}
|
||||
|
||||
public void close(RetryContext context) {
|
||||
}
|
||||
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new TimeoutRetryContext(parent, timeout);
|
||||
}
|
||||
|
||||
public void registerThrowable(RetryContext context, Throwable throwable) {
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
// otherwise no-op - we only time out, otherwise retry everything...
|
||||
}
|
||||
|
||||
private static class TimeoutRetryContext extends RetryContextSupport {
|
||||
private long timeout;
|
||||
|
||||
private long start;
|
||||
|
||||
public TimeoutRetryContext(RetryContext parent, long timeout) {
|
||||
super(parent);
|
||||
this.start = System.currentTimeMillis();
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return (System.currentTimeMillis() - start) <= timeout;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.commons.retry.support;
|
||||
|
||||
import org.springframework.commons.classify.Classifier;
|
||||
import org.springframework.commons.retry.RecoveryCallback;
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryOperations;
|
||||
import org.springframework.commons.retry.RetryState;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultRetryState implements RetryState {
|
||||
|
||||
final private Object key;
|
||||
|
||||
final private boolean forceRefresh;
|
||||
|
||||
final private Classifier<? super Throwable, Boolean> rollbackClassifier;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public DefaultRetryState(Object key, boolean forceRefresh, Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
this.key = key;
|
||||
this.forceRefresh = forceRefresh;
|
||||
this.rollbackClassifier = rollbackClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults the force refresh flag to false.
|
||||
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
|
||||
*/
|
||||
public DefaultRetryState(Object key, Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
this(key, false, rollbackClassifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults the rollback classifier to null.
|
||||
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
|
||||
*/
|
||||
public DefaultRetryState(Object key, boolean forceRefresh) {
|
||||
this(key, forceRefresh, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults the force refresh flag (to false) and the rollback classifier
|
||||
* (to null).
|
||||
*
|
||||
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
|
||||
*/
|
||||
public DefaultRetryState(Object key) {
|
||||
this(key, false, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.commons.retry.IRetryState#getKey()
|
||||
*/
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.commons.retry.IRetryState#isForceRefresh()
|
||||
*/
|
||||
public boolean isForceRefresh() {
|
||||
return forceRefresh;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.commons.retry.RetryState#rollbackFor(java.lang.Throwable
|
||||
* )
|
||||
*/
|
||||
public boolean rollbackFor(Throwable exception) {
|
||||
if (rollbackClassifier == null) {
|
||||
return true;
|
||||
}
|
||||
return rollbackClassifier.classify(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s: key=%s, forceRefresh=%b]", getClass().getSimpleName(), key, forceRefresh);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.support;
|
||||
|
||||
import org.springframework.commons.repeat.RepeatOperations;
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.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
|
||||
* {@link #register(RetryContext)} should not be used except internally by
|
||||
* {@link RetryOperations} implementations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public final class 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() {
|
||||
RetryContext result = (RetryContext) context.get();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static RetryContext register(RetryContext context) {
|
||||
RetryContext oldContext = getContext();
|
||||
RetrySynchronizationManager.context.set(context);
|
||||
return oldContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current context at the end of a batch - should only be used by
|
||||
* {@link RepeatOperations} implementations.
|
||||
*
|
||||
* @return the old value if there was one.
|
||||
*/
|
||||
public static RetryContext clear() {
|
||||
RetryContext value = getContext();
|
||||
RetryContext parent = value == null ? null : value.getParent();
|
||||
RetrySynchronizationManager.context.set(parent);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.commons.retry.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.commons.repeat.RepeatException;
|
||||
import org.springframework.commons.retry.ExhaustedRetryException;
|
||||
import org.springframework.commons.retry.RecoveryCallback;
|
||||
import org.springframework.commons.retry.RetryCallback;
|
||||
import org.springframework.commons.retry.RetryContext;
|
||||
import org.springframework.commons.retry.RetryException;
|
||||
import org.springframework.commons.retry.RetryListener;
|
||||
import org.springframework.commons.retry.RetryOperations;
|
||||
import org.springframework.commons.retry.RetryPolicy;
|
||||
import org.springframework.commons.retry.RetryState;
|
||||
import org.springframework.commons.retry.TerminatedRetryException;
|
||||
import org.springframework.commons.retry.backoff.BackOffContext;
|
||||
import org.springframework.commons.retry.backoff.BackOffInterruptedException;
|
||||
import org.springframework.commons.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.commons.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.commons.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.commons.retry.policy.RetryContextCache;
|
||||
import org.springframework.commons.retry.policy.SimpleRetryPolicy;
|
||||
|
||||
/**
|
||||
* Template class that simplifies the execution of operations with retry
|
||||
* semantics. <br/>
|
||||
* Retryable operations are encapsulated in implementations of the
|
||||
* {@link RetryCallback} interface and are executed using one of the supplied
|
||||
* execute methods. <br/>
|
||||
*
|
||||
* By default, an operation is retried if is throws any {@link Exception} or
|
||||
* subclass of {@link Exception}. This behaviour can be changed by using the
|
||||
* {@link #setRetryPolicy(RetryPolicy)} method. <br/>
|
||||
*
|
||||
* Also by default, each operation is retried for a maximum of three attempts
|
||||
* with no back off in between. This behaviour can be configured using the
|
||||
* {@link #setRetryPolicy(RetryPolicy)} and
|
||||
* {@link #setBackOffPolicy(BackOffPolicy)} properties. The
|
||||
* {@link org.springframework.commons.retry.backoff.BackOffPolicy} controls how
|
||||
* long the pause is between each individual retry attempt. <br/>
|
||||
*
|
||||
* 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 of retries on the fly, as well as the
|
||||
* {@link BackOffPolicy} used and no in progress retryable operations will be
|
||||
* affected.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class RetryTemplate implements RetryOperations {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();
|
||||
|
||||
private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
|
||||
|
||||
private volatile RetryListener[] listeners = new RetryListener[0];
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
*
|
||||
* @param retryContextCache the {@link RetryContextCache} to set.
|
||||
*/
|
||||
public void setRetryContextCache(RetryContextCache retryContextCache) {
|
||||
this.retryContextCache = retryContextCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @see RetryListener
|
||||
*/
|
||||
public void setListeners(RetryListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners).toArray(new RetryListener[listeners.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an additional listener.
|
||||
*
|
||||
* @param listener
|
||||
* @see #setListeners(RetryListener[])
|
||||
*/
|
||||
public void registerListener(RetryListener listener) {
|
||||
List<RetryListener> list = new ArrayList<RetryListener>(Arrays.asList(listeners));
|
||||
list.add(listener);
|
||||
listeners = list.toArray(new RetryListener[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link BackOffPolicy}.
|
||||
*
|
||||
* @param backOffPolicy
|
||||
*/
|
||||
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
|
||||
this.backOffPolicy = backOffPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for {@link RetryPolicy}.
|
||||
*
|
||||
* @param retryPolicy
|
||||
*/
|
||||
public void setRetryPolicy(RetryPolicy retryPolicy) {
|
||||
this.retryPolicy = retryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep executing the callback until it either succeeds or the policy
|
||||
* dictates that we stop, in which case the most recent exception thrown by
|
||||
* the callback will be rethrown.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback)
|
||||
*
|
||||
* @throws TerminatedRetryException if the retry has been manually
|
||||
* terminated by a listener.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback) throws Exception {
|
||||
return doExecute(retryCallback, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep executing the callback until it either succeeds or the policy
|
||||
* dictates that we stop, in which case the recovery callback will be
|
||||
* executed.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RecoveryCallback)
|
||||
*
|
||||
* @throws TerminatedRetryException if the retry has been manually
|
||||
* terminated by a listener.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception {
|
||||
return doExecute(retryCallback, recoveryCallback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing
|
||||
* any exception encountered so that clients can re-present the same task
|
||||
* later.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RetryState)
|
||||
*
|
||||
* @throws ExhaustedRetryException if the retry has been exhausted.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception,
|
||||
ExhaustedRetryException {
|
||||
return doExecute(retryCallback, null, retryState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing
|
||||
* any exception encountered so that clients can re-present the same task
|
||||
* later.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RetryState)
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
|
||||
RetryState retryState) throws Exception, ExhaustedRetryException {
|
||||
return doExecute(retryCallback, recoveryCallback, retryState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, otherwise
|
||||
* execute the recovery callback.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
|
||||
* @throws ExhaustedRetryException if the retry has been exhausted.
|
||||
*/
|
||||
protected <T> T doExecute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState state)
|
||||
throws Exception, ExhaustedRetryException {
|
||||
|
||||
RetryPolicy retryPolicy = this.retryPolicy;
|
||||
BackOffPolicy backOffPolicy = this.backOffPolicy;
|
||||
|
||||
// Allow the retry policy to initialise itself...
|
||||
RetryContext context = open(retryPolicy, state);
|
||||
logger.debug("RetryContext retrieved: " + context);
|
||||
|
||||
// Make sure the context is available globally for clients who need
|
||||
// it...
|
||||
RetrySynchronizationManager.register(context);
|
||||
|
||||
Throwable lastException = null;
|
||||
|
||||
try {
|
||||
|
||||
// Give clients a chance to enhance the context...
|
||||
boolean running = doOpenInterceptors(retryCallback, context);
|
||||
|
||||
if (!running) {
|
||||
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
|
||||
}
|
||||
|
||||
// Start the backoff context...
|
||||
BackOffContext backOffContext = backOffPolicy.start(context);
|
||||
|
||||
/*
|
||||
* We allow the whole loop to be skipped if the policy or context
|
||||
* already forbid the first try. This is used in the case of
|
||||
* external retry to allow a recovery in handleRetryExhausted
|
||||
* without the callback processing (which would throw an exception).
|
||||
*/
|
||||
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
|
||||
|
||||
try {
|
||||
logger.debug("Retry: count=" + context.getRetryCount());
|
||||
// Reset the last exception, so if we are successful
|
||||
// the close interceptors will not think we failed...
|
||||
lastException = null;
|
||||
return retryCallback.doWithRetry(context);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
|
||||
lastException = e;
|
||||
|
||||
doOnErrorInterceptors(retryCallback, context, e);
|
||||
|
||||
registerThrowable(retryPolicy, state, context, e);
|
||||
|
||||
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
|
||||
try {
|
||||
backOffPolicy.backOff(backOffContext);
|
||||
}
|
||||
catch (BackOffInterruptedException ex) {
|
||||
lastException = e;
|
||||
// back off was prevented by another thread - fail
|
||||
// the retry
|
||||
logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Checking for rethrow: count=" + context.getRetryCount());
|
||||
if (shouldRethrow(retryPolicy, context, state)) {
|
||||
logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
|
||||
throw wrapIfNecessary(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* A stateful attempt that can retry should have rethrown the
|
||||
* exception by now - i.e. we shouldn't get this far for a
|
||||
* stateful attempt if it can retry.
|
||||
*/
|
||||
}
|
||||
|
||||
logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
|
||||
|
||||
if (context.isExhaustedOnly()) {
|
||||
throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path.", context
|
||||
.getLastThrowable());
|
||||
}
|
||||
|
||||
return handleRetryExhausted(recoveryCallback, context, state);
|
||||
|
||||
}
|
||||
finally {
|
||||
close(retryPolicy, context, state, lastException == null);
|
||||
doCloseInterceptors(retryCallback, context, lastException);
|
||||
RetrySynchronizationManager.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
|
||||
return retryPolicy.canRetry(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the cache if necessary and close the context provided (if the
|
||||
* flag indicates that processing was successful).
|
||||
*
|
||||
* @param context
|
||||
* @param state
|
||||
* @param succeeded
|
||||
*/
|
||||
protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) {
|
||||
if (state != null) {
|
||||
if (succeeded) {
|
||||
retryContextCache.remove(state.getKey());
|
||||
retryPolicy.close(context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
retryPolicy.close(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param retryPolicy
|
||||
* @param state
|
||||
* @param context
|
||||
* @param e
|
||||
*/
|
||||
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, RetryContext context, Throwable e) {
|
||||
if (state != null) {
|
||||
Object key = state.getKey();
|
||||
if (context.getRetryCount() > 0 && !retryContextCache.containsKey(key)) {
|
||||
throw new RetryException("Inconsistent state for failed item key: cache key has changed. "
|
||||
+ "Consider whether equals() or hashCode() for the key might be inconsistent, "
|
||||
+ "or if you need to supply a better key");
|
||||
}
|
||||
retryContextCache.put(key, context);
|
||||
}
|
||||
retryPolicy.registerThrowable(context, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the {@link RetryPolicy} having checked in the cache for an
|
||||
* existing value if the state is not null.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
|
||||
|
||||
if (state == null) {
|
||||
return doOpenInternal(retryPolicy);
|
||||
}
|
||||
|
||||
Object key = state.getKey();
|
||||
if (state.isForceRefresh()) {
|
||||
return doOpenInternal(retryPolicy);
|
||||
}
|
||||
|
||||
// If there is no cache hit we can avoid the possible expense of the
|
||||
// cache re-hydration.
|
||||
if (!retryContextCache.containsKey(key)) {
|
||||
// The cache is only used if there is a failure.
|
||||
return doOpenInternal(retryPolicy);
|
||||
}
|
||||
|
||||
RetryContext context = retryContextCache.get(key);
|
||||
if (context == null) {
|
||||
if (retryContextCache.containsKey(key)) {
|
||||
throw new RetryException("Inconsistent state for failed item: no history found. "
|
||||
+ "Consider whether equals() or hashCode() for the item might be inconsistent, "
|
||||
+ "or if you need to supply a better ItemKeyGenerator");
|
||||
}
|
||||
// The cache could have been expired in between calls to
|
||||
// containsKey(), so we have to live with this:
|
||||
return doOpenInternal(retryPolicy);
|
||||
}
|
||||
|
||||
return context;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param retryPolicy
|
||||
* @return
|
||||
*/
|
||||
private RetryContext doOpenInternal(RetryPolicy retryPolicy) {
|
||||
return retryPolicy.open(RetrySynchronizationManager.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws Exception if the callback does, and if there is no callback and
|
||||
* the state is null then the last exception from the context
|
||||
* @throws ExhaustedRetryException if the state is not null and there is no
|
||||
* recovery callback
|
||||
*/
|
||||
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context, RetryState state)
|
||||
throws Exception {
|
||||
if (state != null) {
|
||||
retryContextCache.remove(state.getKey());
|
||||
}
|
||||
if (recoveryCallback != null) {
|
||||
return recoveryCallback.recover(context);
|
||||
}
|
||||
if (state != null) {
|
||||
logger.debug("Retry exhausted after last attempt with no recovery path.");
|
||||
throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path", context
|
||||
.getLastThrowable());
|
||||
}
|
||||
throw wrapIfNecessary(context.getLastThrowable());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param context the current context
|
||||
*
|
||||
* @return true if the state is not null but subclasses might choose
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, RetryState state) {
|
||||
if (state == null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return state.rollbackFor(context.getLastThrowable());
|
||||
}
|
||||
}
|
||||
|
||||
private <T> boolean doOpenInterceptors(RetryCallback<T> callback, RetryContext context) {
|
||||
|
||||
boolean result = true;
|
||||
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
result = result && listeners[i].open(context, callback);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private <T> void doCloseInterceptors(RetryCallback<T> callback, RetryContext context, Throwable lastException) {
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
listeners[i].close(context, callback, lastException);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void doOnErrorInterceptors(RetryCallback<T> callback, RetryContext context, Throwable throwable) {
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
listeners[i].onError(context, callback, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked
|
||||
* exceptions into {@link RepeatException}.
|
||||
*/
|
||||
private static Exception wrapIfNecessary(Throwable throwable) {
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
else if (throwable instanceof Exception) {
|
||||
return (Exception) throwable;
|
||||
}
|
||||
else {
|
||||
return new RetryException("Exception in batch process", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
src/site/site.xml
Normal file
25
src/site/site.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project name="Spring Batch: ${project.name}">
|
||||
|
||||
<bannerLeft>
|
||||
<name>Spring Batch: ${project.name}</name>
|
||||
<href>index.html</href>
|
||||
</bannerLeft>
|
||||
|
||||
<skin>
|
||||
<groupId>org.springframework.maven.skins</groupId>
|
||||
<artifactId>maven-spring-skin</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</skin>
|
||||
|
||||
<body>
|
||||
|
||||
<links>
|
||||
<item name="${project.name}" href="index.html"/>
|
||||
</links>
|
||||
|
||||
<menu ref="reports"/>
|
||||
|
||||
</body>
|
||||
|
||||
</project>
|
||||
7
src/test/resources/log4j.properties
Normal file
7
src/test/resources/log4j.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
log4j.rootCategory=INFO, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.category.org.springframework.transaction=INFO
|
||||
18
template.mf
Normal file
18
template.mf
Normal file
@@ -0,0 +1,18 @@
|
||||
Bundle-SymbolicName: org.springframework.commons.retry
|
||||
Bundle-Name: Spring Commons Retry
|
||||
Bundle-Vendor: SpringSource
|
||||
Bundle-ManifestVersion: 2
|
||||
Import-Template:
|
||||
org.springframework.commons.*;version="[1.0.0, 2.0.0)",
|
||||
org.springframework.beans.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.context.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.core.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.jdbc.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.stereotype.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.scheduling.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.ui.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.transaction.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.web.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.validation.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.util;version="[3.0.0, 4.0.0)",
|
||||
org.apache.commons.logging;version="[1.1.1, 2.0.0)"
|
||||
Reference in New Issue
Block a user