Initial port from Spring Batch - no tests

This commit is contained in:
Dave Syer
2010-12-21 17:03:23 +00:00
commit 2215dddc69
54 changed files with 4105 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
target
.springBeans
.settings
.classpath
.project

156
pom.xml Normal file
View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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 + "]";
}
}

View File

@@ -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 &lt; 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);
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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");
}
}
});
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
View 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>

View 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
View 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)"