diff --git a/pom.xml b/pom.xml
index 853f188..8cce5e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,6 +74,24 @@
4.8.2test
+
+ org.easymock
+ easymock
+ 2.3
+ test
+
+
+ org.aspectj
+ aspectjrt
+ 1.6.6
+ test
+
+
+ org.aspectj
+ aspectjweaver
+ 1.6.6
+ test
+ org.springframeworkspring-test
@@ -85,6 +103,12 @@
spring-context${spring.framework.version}
+
+ org.springframework
+ spring-tx
+ ${spring.framework.version}
+ test
+ log4jlog4j
@@ -128,7 +152,7 @@
org.apache.maven.pluginsmaven-surefire-plugin
-
+
**/*Tests.java
diff --git a/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java b/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java
new file mode 100644
index 0000000..a3ec528
--- /dev/null
+++ b/src/main/java/org/springframework/classify/BackToBackPatternClassifier.java
@@ -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.classify;
+
+import java.util.Map;
+
+/**
+ * A special purpose {@link Classifier} with easy configuration options for
+ * mapping from one arbitrary type of object to another via a pattern matcher.
+ *
+ * @author Dave Syer
+ *
+ */
+public class BackToBackPatternClassifier implements Classifier {
+
+ private Classifier router;
+
+ private Classifier matcher;
+
+ /**
+ * Default constructor, provided as a convenience for people using setter
+ * injection.
+ */
+ public BackToBackPatternClassifier() {
+ }
+
+ /**
+ * Set up a classifier with input to the router and output from the matcher.
+ *
+ * @param router see {@link #setRouterDelegate(Object)}
+ * @param matcher see {@link #setMatcherMap(Map)}
+ */
+ public BackToBackPatternClassifier(Classifier router, Classifier matcher) {
+ super();
+ this.router = router;
+ this.matcher = matcher;
+ }
+
+ /**
+ * A convenience method for creating a pattern matching classifier for the
+ * matcher component.
+ *
+ * @param map maps pattern keys with wildcards to output values
+ */
+ public void setMatcherMap(Map map) {
+ this.matcher = new PatternMatchingClassifier(map);
+ }
+
+ /**
+ * A convenience method of creating a router classifier based on a plain old
+ * Java Object. The object provided must have precisely one public method
+ * that either has the @Classifier annotation or accepts a single argument
+ * and outputs a String. This will be used to create an input classifier for
+ * the router component.
+ *
+ * @param delegate the delegate object used to create a router classifier
+ */
+ public void setRouterDelegate(Object delegate) {
+ this.router = new ClassifierAdapter(delegate);
+ }
+
+ /**
+ * Classify the input and map to a String, then take that and put it into a
+ * pattern matcher to match to an output value.
+ */
+ public T classify(C classifiable) {
+ return matcher.classify(router.classify(classifiable));
+ }
+
+}
diff --git a/src/main/java/org/springframework/commons/classify/BinaryExceptionClassifier.java b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java
similarity index 98%
rename from src/main/java/org/springframework/commons/classify/BinaryExceptionClassifier.java
rename to src/main/java/org/springframework/classify/BinaryExceptionClassifier.java
index ebfbfa4..edd6ab1 100644
--- a/src/main/java/org/springframework/commons/classify/BinaryExceptionClassifier.java
+++ b/src/main/java/org/springframework/classify/BinaryExceptionClassifier.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.classify;
+package org.springframework.classify;
import java.util.Collection;
import java.util.HashMap;
diff --git a/src/main/java/org/springframework/commons/classify/Classifier.java b/src/main/java/org/springframework/classify/Classifier.java
similarity index 96%
rename from src/main/java/org/springframework/commons/classify/Classifier.java
rename to src/main/java/org/springframework/classify/Classifier.java
index 83864ac..5e58b8c 100644
--- a/src/main/java/org/springframework/commons/classify/Classifier.java
+++ b/src/main/java/org/springframework/classify/Classifier.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.classify;
+package org.springframework.classify;
/**
* Interface for a classifier. At its simplest a {@link Classifier} is just a
diff --git a/src/main/java/org/springframework/classify/ClassifierAdapter.java b/src/main/java/org/springframework/classify/ClassifierAdapter.java
new file mode 100644
index 0000000..9dab62b
--- /dev/null
+++ b/src/main/java/org/springframework/classify/ClassifierAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.classify;
+
+import org.springframework.classify.util.MethodInvoker;
+import org.springframework.classify.util.MethodInvokerUtils;
+import org.springframework.util.Assert;
+
+/**
+ * Wrapper for an object to adapt it to the {@link Classifier} interface.
+ *
+ * @author Dave Syer
+ *
+ */
+public class ClassifierAdapter implements Classifier {
+
+ private MethodInvoker invoker;
+
+ private Classifier classifier;
+
+ /**
+ * Default constructor for use with setter injection.
+ */
+ public ClassifierAdapter() {
+ super();
+ }
+
+ /**
+ * Create a new {@link Classifier} from the delegate provided. Use the
+ * constructor as an alternative to the {@link #setDelegate(Object)} method.
+ *
+ * @param delegate
+ */
+ public ClassifierAdapter(Object delegate) {
+ setDelegate(delegate);
+ }
+
+ /**
+ * Create a new {@link Classifier} from the delegate provided. Use the
+ * constructor as an alternative to the {@link #setDelegate(Classifier)}
+ * method.
+ *
+ * @param delegate
+ */
+ public ClassifierAdapter(Classifier delegate) {
+ classifier = delegate;
+ }
+
+ public void setDelegate(Classifier delegate) {
+ classifier = delegate;
+ invoker = null;
+ }
+
+ /**
+ * Search for the
+ * {@link org.springframework.classify.annotation.Classifier
+ * Classifier} annotation on a method in the supplied delegate and use that
+ * to create a {@link Classifier} from the parameter type to the return
+ * type. If the annotation is not found a unique non-void method with a
+ * single parameter will be used, if it exists. The signature of the method
+ * cannot be checked here, so might be a runtime exception when the method
+ * is invoked if the signature doesn't match the classifier types.
+ *
+ * @param delegate an object with an annotated method
+ */
+ public final void setDelegate(Object delegate) {
+ classifier = null;
+ invoker = MethodInvokerUtils.getMethodInvokerByAnnotation(
+ org.springframework.classify.annotation.Classifier.class, delegate);
+ if (invoker == null) {
+ invoker = MethodInvokerUtils. getMethodInvokerForSingleArgument(delegate);
+ }
+ Assert.state(invoker != null, "No single argument public method with or without "
+ + "@Classifier was found in delegate of type " + delegate.getClass());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public T classify(C classifiable) {
+ if (classifier != null) {
+ return classifier.classify(classifiable);
+ }
+ return (T) invoker.invokeMethod(classifiable);
+ }
+
+}
diff --git a/src/main/java/org/springframework/commons/classify/ClassifierSupport.java b/src/main/java/org/springframework/classify/ClassifierSupport.java
similarity index 91%
rename from src/main/java/org/springframework/commons/classify/ClassifierSupport.java
rename to src/main/java/org/springframework/classify/ClassifierSupport.java
index c76877c..d24aec2 100644
--- a/src/main/java/org/springframework/commons/classify/ClassifierSupport.java
+++ b/src/main/java/org/springframework/classify/ClassifierSupport.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.classify;
+package org.springframework.classify;
/**
* Base class for {@link Classifier} implementations. Provides default behaviour
@@ -39,7 +39,7 @@ public class ClassifierSupport implements Classifier {
* 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)
+ * @see org.springframework.classify.Classifier#classify(Object)
*/
public T classify(C throwable) {
return defaultValue;
diff --git a/src/main/java/org/springframework/classify/PatternMatcher.java b/src/main/java/org/springframework/classify/PatternMatcher.java
new file mode 100644
index 0000000..b8d96a0
--- /dev/null
+++ b/src/main/java/org/springframework/classify/PatternMatcher.java
@@ -0,0 +1,231 @@
+/*
+ * 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.classify;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * @author Dave Syer
+ * @author Dan Garrette
+ */
+public class PatternMatcher {
+
+ private Map map = new HashMap();
+ private List sorted = new ArrayList();
+
+ /**
+ * Initialize a new {@link PatternMatcher} with a map of patterns to values
+ * @param map a map from String patterns to values
+ */
+ public PatternMatcher(Map map) {
+ super();
+ this.map = map;
+ // Sort keys to start with the most specific
+ sorted = new ArrayList(map.keySet());
+ Collections.sort(sorted, new Comparator() {
+ public int compare(String o1, String o2) {
+ String s1 = o1; // .replace('?', '{');
+ String s2 = o2; // .replace('*', '}');
+ return s2.compareTo(s1);
+ }
+ });
+ }
+
+ /**
+ * Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
+ * matches against a pattern. The pattern may contain two special
+ * characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern pattern to match against. Must not be null.
+ * @param str string which must be matched against the pattern. Must not be
+ * null.
+ * @return true if the string matches against the pattern, or
+ * false otherwise.
+ */
+ public static boolean match(String pattern, String str) {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = pattern.contains("*");
+
+ if (!containsStar) {
+ // No '*'s, so we make a shortcut
+ if (patIdxEnd != strIdxEnd) {
+ return false; // Pattern and string do not have the same size
+ }
+ for (int i = 0; i <= patIdxEnd; i++) {
+ ch = patArr[i];
+ if (ch != '?') {
+ if (ch != strArr[i]) {
+ return false;// Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if (patIdxEnd == 0) {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxEnd]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patArr[i] == '*') {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == patIdxStart + 1) {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - patIdxStart - 1);
+ int strLength = (strIdxEnd - strIdxStart + 1);
+ int foundIdx = -1;
+ strLoop: for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ ch = patArr[patIdxStart + j + 1];
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart + i + j]) {
+ continue strLoop;
+ }
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * This method takes a String key and a map from Strings to values of any
+ * type. During processing, the method will identify the most specific key
+ * in the map that matches the line. Once the correct is identified, its
+ * value is returned. Note that if the map contains the wildcard string "*"
+ * as a key, then it will serve as the "default" case, matching every line
+ * that does not match anything else.
+ *
+ *
+ * If no matching prefix is found, a {@link IllegalStateException} will be
+ * thrown.
+ *
+ *
+ * Null keys are not allowed in the map.
+ *
+ * @param line An input string
+ * @return the value whose prefix matches the given line
+ */
+ public S match(String line) {
+
+ S value = null;
+ Assert.notNull(line, "A non-null key must be provided to match against.");
+
+ for (String key : sorted) {
+ if (PatternMatcher.match(key, line)) {
+ value = map.get(key);
+ break;
+ }
+ }
+
+ if (value == null) {
+ throw new IllegalStateException("Could not find a matching pattern for key=[" + line + "]");
+ }
+ return value;
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/classify/PatternMatchingClassifier.java b/src/main/java/org/springframework/classify/PatternMatchingClassifier.java
new file mode 100644
index 0000000..be9b67f
--- /dev/null
+++ b/src/main/java/org/springframework/classify/PatternMatchingClassifier.java
@@ -0,0 +1,75 @@
+/*
+ * 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.classify;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link Classifier} that maps from String patterns with wildcards to a set
+ * of values of a given type. An input String is matched with the most specific
+ * pattern possible to the corresponding value in an input map. A default value
+ * should be specified with a pattern key of "*".
+ *
+ * @author Dave Syer
+ *
+ */
+public class PatternMatchingClassifier implements Classifier {
+
+ private PatternMatcher values;
+
+ /**
+ * Default constructor. Use the setter or the other constructor to create a
+ * sensible classifier, otherwise all inputs will cause an exception.
+ */
+ public PatternMatchingClassifier() {
+ this(new HashMap());
+ }
+
+ /**
+ * Create a classifier from the provided map. The keys are patterns, using
+ * '?' as a single character and '*' as multi-character wildcard.
+ *
+ * @param values
+ */
+ public PatternMatchingClassifier(Map values) {
+ super();
+ this.values = new PatternMatcher(values);
+ }
+
+ /**
+ * A map from pattern to value
+ * @param values the pattern map to set
+ */
+ public void setPatternMap(Map values) {
+ this.values = new PatternMatcher(values);
+ }
+
+ /**
+ * Classify the input by matching it against the patterns provided in
+ * {@link #setPatternMap(Map)}. The most specific pattern that matches will
+ * be used to locate a value.
+ *
+ * @return the value matching the most specific pattern possible
+ *
+ * @throws IllegalStateException if no matching value is found.
+ */
+ public T classify(String classifiable) {
+ T value = values.match(classifiable);
+ return value;
+ }
+
+}
diff --git a/src/main/java/org/springframework/commons/classify/SubclassClassifier.java b/src/main/java/org/springframework/classify/SubclassClassifier.java
similarity index 98%
rename from src/main/java/org/springframework/commons/classify/SubclassClassifier.java
rename to src/main/java/org/springframework/classify/SubclassClassifier.java
index f23deb6..9428983 100644
--- a/src/main/java/org/springframework/commons/classify/SubclassClassifier.java
+++ b/src/main/java/org/springframework/classify/SubclassClassifier.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.classify;
+package org.springframework.classify;
import java.io.Serializable;
import java.util.Comparator;
@@ -26,7 +26,7 @@ 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
+ * map, or is a subclass of one of the keys, then the map entry value for that
* key is returned. Otherwise returns the default value which is null by
* default.
*
diff --git a/src/main/java/org/springframework/classify/annotation/Classifier.java b/src/main/java/org/springframework/classify/annotation/Classifier.java
new file mode 100644
index 0000000..b661329
--- /dev/null
+++ b/src/main/java/org/springframework/classify/annotation/Classifier.java
@@ -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.classify.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a method as capable of classifying its input to an instance of its
+ * output. Should only be used on non-void methods with one parameter.
+ *
+ * @author Dave Syer
+ *
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Classifier {
+
+}
diff --git a/src/main/java/org/springframework/classify/util/AnnotationMethodResolver.java b/src/main/java/org/springframework/classify/util/AnnotationMethodResolver.java
new file mode 100644
index 0000000..9aec6b5
--- /dev/null
+++ b/src/main/java/org/springframework/classify/util/AnnotationMethodResolver.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2008 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.classify.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * MethodResolver implementation that finds a single Method on the
+ * given Class that contains the specified annotation type.
+ *
+ * @author Mark Fisher
+ */
+public class AnnotationMethodResolver implements MethodResolver {
+
+ private Class extends Annotation> annotationType;
+
+
+ /**
+ * Create a MethodResolver for the specified Method-level annotation type
+ */
+ public AnnotationMethodResolver(Class extends Annotation> annotationType) {
+ Assert.notNull(annotationType, "annotationType must not be null");
+ Assert.isTrue(ObjectUtils.containsElement(
+ annotationType.getAnnotation(Target.class).value(), ElementType.METHOD),
+ "Annotation [" + annotationType + "] is not a Method-level annotation.");
+ this.annotationType = annotationType;
+ }
+
+
+ /**
+ * Find a single Method on the Class of the given candidate object
+ * that contains the annotation type for which this resolver is searching.
+ *
+ * @param candidate the instance whose Class will be checked for the
+ * annotation
+ *
+ * @return a single matching Method instance or null if the
+ * candidate's Class contains no Methods with the specified annotation
+ *
+ * @throws IllegalArgumentException if more than one Method has the
+ * specified annotation
+ */
+ public Method findMethod(Object candidate) {
+ Assert.notNull(candidate, "candidate object must not be null");
+ Class> targetClass = AopUtils.getTargetClass(candidate);
+ if (targetClass == null) {
+ targetClass = candidate.getClass();
+ }
+ return this.findMethod(targetClass);
+ }
+
+ /**
+ * Find a single Method on the given Class that contains the
+ * annotation type for which this resolver is searching.
+ *
+ * @param clazz the Class instance to check for the annotation
+ *
+ * @return a single matching Method instance or null if the
+ * Class contains no Methods with the specified annotation
+ *
+ * @throws IllegalArgumentException if more than one Method has the
+ * specified annotation
+ */
+ public Method findMethod(final Class> clazz) {
+ Assert.notNull(clazz, "class must not be null");
+ final AtomicReference annotatedMethod = new AtomicReference();
+ ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
+ if (annotation != null) {
+ Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
+ + clazz + "] with the annotation type [" + annotationType + "]");
+ annotatedMethod.set(method);
+ }
+ }
+ });
+ return annotatedMethod.get();
+ }
+
+}
diff --git a/src/main/java/org/springframework/classify/util/MethodInvoker.java b/src/main/java/org/springframework/classify/util/MethodInvoker.java
new file mode 100644
index 0000000..2e276e2
--- /dev/null
+++ b/src/main/java/org/springframework/classify/util/MethodInvoker.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2008 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.classify.util;
+
+/**
+ * A strategy interface for invoking a method.
+ * Typically used by adapters.
+ *
+ * @author Mark Fisher
+ */
+public interface MethodInvoker {
+
+ Object invokeMethod(Object ... args);
+
+}
diff --git a/src/main/java/org/springframework/classify/util/MethodInvokerUtils.java b/src/main/java/org/springframework/classify/util/MethodInvokerUtils.java
new file mode 100644
index 0000000..d0b5721
--- /dev/null
+++ b/src/main/java/org/springframework/classify/util/MethodInvokerUtils.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2002-2008 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.classify.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Utility methods for create MethodInvoker instances.
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ */
+public class MethodInvokerUtils {
+
+ /**
+ * Create a {@link MethodInvoker} using the provided method name to search.
+ *
+ * @param object to be invoked
+ * @param methodName of the method to be invoked
+ * @param paramsRequired boolean indicating whether the parameters are
+ * required, if false, a no args version of the method will be searched for.
+ * @param paramTypes - parameter types of the method to search for.
+ * @return MethodInvoker if the method is found, null if it is not.
+ */
+ public static MethodInvoker getMethodInvokerByName(Object object, String methodName, boolean paramsRequired,
+ Class>... paramTypes) {
+ Assert.notNull(object, "Object to invoke must not be null");
+ Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
+ if (method == null) {
+ String errorMsg = "no method found with name [" + methodName + "] on class ["
+ + object.getClass().getSimpleName() + "] compatable with the signature ["
+ + getParamTypesString(paramTypes) + "].";
+ Assert.isTrue(!paramsRequired, errorMsg);
+ // if no method was found for the given parameters, and the
+ // parameters aren't required, then try with no params
+ method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[] {});
+ Assert.notNull(method, errorMsg);
+ }
+ return new SimpleMethodInvoker(object, method);
+ }
+
+ /**
+ * Create a String representation of the array of parameter types.
+ *
+ * @param paramTypes
+ * @return String
+ */
+ public static String getParamTypesString(Class>... paramTypes) {
+ StringBuffer paramTypesList = new StringBuffer("(");
+ for (int i = 0; i < paramTypes.length; i++) {
+ paramTypesList.append(paramTypes[i].getSimpleName());
+ if (i + 1 < paramTypes.length) {
+ paramTypesList.append(", ");
+ }
+ }
+ return paramTypesList.append(")").toString();
+ }
+
+ /**
+ * Create a {@link MethodInvoker} using the provided interface, and method
+ * name from that interface.
+ *
+ * @param cls the interface to search for the method named
+ * @param methodName of the method to be invoked
+ * @param object to be invoked
+ * @param paramTypes - parameter types of the method to search for.
+ * @return MethodInvoker if the method is found, null if it is not.
+ */
+ public static MethodInvoker getMethodInvokerForInterface(Class> cls, String methodName, Object object,
+ Class>... paramTypes) {
+
+ if (cls.isAssignableFrom(object.getClass())) {
+ return MethodInvokerUtils.getMethodInvokerByName(object, methodName, true, paramTypes);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Create a MethodInvoker from the delegate based on the annotationType.
+ * Ensure that the annotated method has a valid set of parameters.
+ *
+ * @param annotationType the annotation to scan for
+ * @param target the target object
+ * @param expectedParamTypes the expected parameter types for the method
+ * @return a MethodInvoker
+ */
+ public static MethodInvoker getMethodInvokerByAnnotation(final Class extends Annotation> annotationType,
+ final Object target, final Class>... expectedParamTypes) {
+ MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, target);
+ final Class> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource()
+ .getTargetClass() : target.getClass();
+ if (mi != null) {
+ ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
+ if (annotation != null) {
+ Class>[] paramTypes = method.getParameterTypes();
+ if (paramTypes.length > 0) {
+ String errorMsg = "The method [" + method.getName() + "] on target class ["
+ + targetClass.getSimpleName() + "] is incompatable with the signature ["
+ + getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
+ + annotationType.getSimpleName() + "].";
+
+ Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
+ for (int i = 0; i < paramTypes.length; i++) {
+ Assert.isTrue(expectedParamTypes[i].isAssignableFrom(paramTypes[i]), errorMsg);
+ }
+ }
+ }
+ }
+ });
+ }
+ return mi;
+ }
+
+ /**
+ * Create {@link MethodInvoker} for the method with the provided annotation
+ * on the provided object. Annotations that cannot be applied to methods
+ * (i.e. that aren't annotated with an element type of METHOD) will cause an
+ * exception to be thrown.
+ *
+ * @param annotationType to be searched for
+ * @param target to be invoked
+ * @return MethodInvoker for the provided annotation, null if none is found.
+ */
+ public static MethodInvoker getMethodInvokerByAnnotation(final Class extends Annotation> annotationType,
+ final Object target) {
+ Assert.notNull(target, "Target must not be null");
+ Assert.notNull(annotationType, "AnnotationType must not be null");
+ Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(),
+ ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation.");
+ final Class> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource()
+ .getTargetClass() : target.getClass();
+ if (targetClass == null) {
+ // Proxy with no target cannot have annotations
+ return null;
+ }
+ final AtomicReference annotatedMethod = new AtomicReference();
+ ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
+ if (annotation != null) {
+ Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
+ + targetClass.getSimpleName() + "] with the annotation type ["
+ + annotationType.getSimpleName() + "].");
+ annotatedMethod.set(method);
+ }
+ }
+ });
+ Method method = annotatedMethod.get();
+ if (method == null) {
+ return null;
+ }
+ else {
+ return new SimpleMethodInvoker(target, annotatedMethod.get());
+ }
+ }
+
+ /**
+ * Create a {@link MethodInvoker} for the delegate from a single public
+ * method.
+ *
+ * @param target an object to search for an appropriate method
+ * @return a MethodInvoker that calls a method on the delegate
+ */
+ public static MethodInvoker getMethodInvokerForSingleArgument(Object target) {
+ final AtomicReference methodHolder = new AtomicReference();
+ ReflectionUtils.doWithMethods(target.getClass(), new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) {
+ return;
+ }
+ if (method.getReturnType().equals(Void.TYPE) || ReflectionUtils.isEqualsMethod(method)) {
+ return;
+ }
+ Assert.state(methodHolder.get() == null,
+ "More than one non-void public method detected with single argument.");
+ methodHolder.set(method);
+ }
+ });
+ Method method = methodHolder.get();
+ return new SimpleMethodInvoker(target, method);
+ }
+}
diff --git a/src/main/java/org/springframework/classify/util/MethodResolver.java b/src/main/java/org/springframework/classify/util/MethodResolver.java
new file mode 100644
index 0000000..cc71ea8
--- /dev/null
+++ b/src/main/java/org/springframework/classify/util/MethodResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2008 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.classify.util;
+
+import java.lang.reflect.Method;
+
+/**
+ * Strategy interface for detecting a single Method on a Class.
+ *
+ * @author Mark Fisher
+ */
+public interface MethodResolver {
+
+ /**
+ * Find a single Method on the provided Object that matches this resolver's
+ * criteria.
+ *
+ * @param candidate the candidate Object whose Class should be searched for
+ * a Method
+ *
+ * @return a single Method or null if no Method matching this
+ * resolver's criteria can be found.
+ *
+ * @throws IllegalArgumentException if more than one Method defined on the
+ * given candidate's Class matches this resolver's criteria
+ */
+ Method findMethod(Object candidate) throws IllegalArgumentException;
+
+ /**
+ * Find a single Method on the given Class that matches this
+ * resolver's criteria.
+ *
+ * @param clazz the Class instance on which to search for a Method
+ *
+ * @return a single Method or null if no Method matching this
+ * resolver's criteria can be found.
+ *
+ * @throws IllegalArgumentException if more than one Method defined on the
+ * given Class matches this resolver's criteria
+ */
+ Method findMethod(Class> clazz);
+
+}
diff --git a/src/main/java/org/springframework/classify/util/SimpleMethodInvoker.java b/src/main/java/org/springframework/classify/util/SimpleMethodInvoker.java
new file mode 100644
index 0000000..bb37a5c
--- /dev/null
+++ b/src/main/java/org/springframework/classify/util/SimpleMethodInvoker.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2002-2008 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.
+ */
+
+/*
+ * Copyright 2002-2008 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.classify.util;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Simple implementation of the {@link MethodInvoker} interface that invokes a
+ * method on an object. If the method has no arguments, but arguments are
+ * provided, they are ignored and the method is invoked anyway. If there are
+ * more arguments than there are provided, then an exception is thrown.
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ */
+public class SimpleMethodInvoker implements MethodInvoker {
+
+ private final Object object;
+
+ private Method method;
+
+ public SimpleMethodInvoker(Object object, Method method) {
+ Assert.notNull(object, "Object to invoke must not be null");
+ Assert.notNull(method, "Method to invoke must not be null");
+ this.method = method;
+ this.object = object;
+ }
+
+ public SimpleMethodInvoker(Object object, String methodName, Class>... paramTypes) {
+ Assert.notNull(object, "Object to invoke must not be null");
+ this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
+ if (this.method == null) {
+ // try with no params
+ this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[] {});
+ }
+ if (this.method == null) {
+ throw new IllegalArgumentException("No methods found for name: [" + methodName + "] in class: ["
+ + object.getClass() + "] with arguments of type: [" + Arrays.toString(paramTypes) + "]");
+ }
+ this.object = object;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod
+ * (java.lang.Object[])
+ */
+ public Object invokeMethod(Object... args) {
+
+ Class>[] parameterTypes = method.getParameterTypes();
+ Object[] invokeArgs;
+ if (parameterTypes.length == 0) {
+ invokeArgs = new Object[] {};
+ }
+ else if (parameterTypes.length != args.length) {
+ throw new IllegalArgumentException("Wrong number of arguments, expected no more than: ["
+ + parameterTypes.length + "]");
+ }
+ else {
+ invokeArgs = args;
+ }
+
+ method.setAccessible(true);
+
+ try {
+ // Extract the target from an Advised as late as possible
+ // in case it contains a lazy initialization
+ Object target = extractTarget(object, method);
+ return method.invoke(target, invokeArgs);
+ }
+ catch (Exception e) {
+ throw new IllegalArgumentException("Unable to invoke method: [" + method + "] on object: [" + object
+ + "] with arguments: [" + Arrays.toString(args) + "]", e);
+ }
+ }
+
+ private Object extractTarget(Object target, Method method) {
+ if (target instanceof Advised) {
+ Object source;
+ try {
+ source = ((Advised) target).getTargetSource().getTarget();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException("Could not extract target from proxy", e);
+ }
+ if (source instanceof Advised) {
+ source = extractTarget(source, method);
+ }
+ if (method.getDeclaringClass().isAssignableFrom(source.getClass())) {
+ target = source;
+ }
+ }
+ return target;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SimpleMethodInvoker)) {
+ return false;
+ }
+
+ if (obj == this) {
+ return true;
+ }
+ SimpleMethodInvoker rhs = (SimpleMethodInvoker) obj;
+ return (rhs.method.equals(this.method)) && (rhs.object.equals(this.object));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 25;
+ result = 31 * result + object.hashCode();
+ result = 31 * result + method.hashCode();
+ return result;
+ }
+}
diff --git a/src/main/java/org/springframework/repeat/CompletionPolicy.java b/src/main/java/org/springframework/repeat/CompletionPolicy.java
new file mode 100644
index 0000000..51fead0
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/CompletionPolicy.java
@@ -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.repeat;
+
+
+/**
+ * Interface for batch completion policies, to enable batch operations to
+ * strategise normal completion conditions. Stateful implementations of batch
+ * iterators should only update state using the update method. If you
+ * need custom behaviour consider extending an existing implementation or using
+ * the composite provided.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface CompletionPolicy {
+
+ /**
+ * Determine whether a batch is complete given the latest result from the
+ * callback. If this method returns true then
+ * {@link #isComplete(RepeatContext)} should also (but not necessarily vice
+ * versa, since the answer here depends on the result).
+ *
+ * @param context the current batch context.
+ * @param result the result of the latest batch item processing.
+ *
+ * @return true if the batch should terminate.
+ *
+ * @see #isComplete(RepeatContext)
+ */
+ boolean isComplete(RepeatContext context, RepeatStatus result);
+
+ /**
+ * Allow policy to signal completion according to internal state, without
+ * having to wait for the callback to complete.
+ *
+ * @param context the current batch context.
+ *
+ * @return true if the batch should terminate.
+ */
+ boolean isComplete(RepeatContext context);
+
+ /**
+ * Create a new context for the execution of a batch. N.B. implementations
+ * should not return the parent from this method - they must
+ * create a new context to meet the specific needs of the policy. The best
+ * way to do this might be to override an existing implementation and use
+ * the {@link RepeatContext} to store state in its attributes.
+ *
+ * @param parent the current context if one is already in progress.
+ * @return a context object that can be used by the implementation to store
+ * internal state for a batch.
+ */
+ RepeatContext start(RepeatContext parent);
+
+ /**
+ * Give implementations the opportunity to update the state of the current
+ * batch. Will be called once per callback, after it has been
+ * launched, but not necessarily after it completes (if the batch is
+ * asynchronous).
+ *
+ * @param context the value returned by start.
+ */
+ void update(RepeatContext context);
+
+}
diff --git a/src/main/java/org/springframework/commons/repeat/RepeatCallback.java b/src/main/java/org/springframework/repeat/RepeatCallback.java
similarity index 97%
rename from src/main/java/org/springframework/commons/repeat/RepeatCallback.java
rename to src/main/java/org/springframework/repeat/RepeatCallback.java
index 2e5d426..2996171 100644
--- a/src/main/java/org/springframework/commons/repeat/RepeatCallback.java
+++ b/src/main/java/org/springframework/repeat/RepeatCallback.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.repeat;
+package org.springframework.repeat;
/**
diff --git a/src/main/java/org/springframework/commons/repeat/RepeatContext.java b/src/main/java/org/springframework/repeat/RepeatContext.java
similarity index 98%
rename from src/main/java/org/springframework/commons/repeat/RepeatContext.java
rename to src/main/java/org/springframework/repeat/RepeatContext.java
index 099cf8a..bcc528d 100644
--- a/src/main/java/org/springframework/commons/repeat/RepeatContext.java
+++ b/src/main/java/org/springframework/repeat/RepeatContext.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.repeat;
+package org.springframework.repeat;
import org.springframework.core.AttributeAccessor;
diff --git a/src/main/java/org/springframework/commons/repeat/RepeatException.java b/src/main/java/org/springframework/repeat/RepeatException.java
similarity index 95%
rename from src/main/java/org/springframework/commons/repeat/RepeatException.java
rename to src/main/java/org/springframework/repeat/RepeatException.java
index 8e110b0..9f02abf 100644
--- a/src/main/java/org/springframework/commons/repeat/RepeatException.java
+++ b/src/main/java/org/springframework/repeat/RepeatException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.repeat;
+package org.springframework.repeat;
import org.springframework.core.NestedRuntimeException;
diff --git a/src/main/java/org/springframework/repeat/RepeatListener.java b/src/main/java/org/springframework/repeat/RepeatListener.java
new file mode 100644
index 0000000..834d40b
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/RepeatListener.java
@@ -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.repeat;
+
+
+/**
+ * Interface for listeners to the batch process. Implementers can provide
+ * enhance the behaviour of a batch in small cross-cutting modules. The
+ * framework provides callbacks at key points in the processing.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface RepeatListener {
+ /**
+ * Called by the framework before each batch item. Implementers can halt a
+ * batch by setting the complete flag on the context.
+ *
+ * @param context the current batch context.
+ */
+ void before(RepeatContext context);
+
+ /**
+ * Called by the framework after each item has been processed, unless the
+ * item processing results in an exception. This method is called as soon as
+ * the result is known.
+ *
+ * @param context the current batch context
+ * @param result the result of the callback
+ */
+ void after(RepeatContext context, RepeatStatus result);
+
+ /**
+ * Called once at the start of a complete batch, before any items are
+ * processed. Implementers can use this method to acquire any resources that
+ * might be needed during processing. Implementers can halt the current
+ * operation by setting the complete flag on the context. To halt all
+ * enclosing batches (the whole job), the would need to use the parent
+ * context (recursively).
+ *
+ * @param context the current batch context
+ */
+ void open(RepeatContext context);
+
+ /**
+ * Called when a repeat callback fails by throwing an exception. There will
+ * be one call to this method for each exception thrown during a repeat
+ * operation (e.g. a chunk).
+ *
+ * There is no need to re-throw the exception here - that will be done by
+ * the enclosing framework.
+ *
+ * @param context the current batch context
+ * @param e the error that was encountered in an item callback.
+ */
+ void onError(RepeatContext context, Throwable e);
+
+ /**
+ * Called once at the end of a complete batch, after normal or abnormal
+ * completion (i.e. even after an exception). Implementers can use this
+ * method to clean up any resources.
+ *
+ * @param context the current batch context.
+ */
+ void close(RepeatContext context);
+}
diff --git a/src/main/java/org/springframework/commons/repeat/RepeatOperations.java b/src/main/java/org/springframework/repeat/RepeatOperations.java
similarity index 97%
rename from src/main/java/org/springframework/commons/repeat/RepeatOperations.java
rename to src/main/java/org/springframework/repeat/RepeatOperations.java
index da49ee2..c6a8965 100644
--- a/src/main/java/org/springframework/commons/repeat/RepeatOperations.java
+++ b/src/main/java/org/springframework/repeat/RepeatOperations.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.repeat;
+package org.springframework.repeat;
/**
diff --git a/src/main/java/org/springframework/commons/repeat/RepeatStatus.java b/src/main/java/org/springframework/repeat/RepeatStatus.java
similarity index 96%
rename from src/main/java/org/springframework/commons/repeat/RepeatStatus.java
rename to src/main/java/org/springframework/repeat/RepeatStatus.java
index 3a117ab..7b41a51 100644
--- a/src/main/java/org/springframework/commons/repeat/RepeatStatus.java
+++ b/src/main/java/org/springframework/repeat/RepeatStatus.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.repeat;
+package org.springframework.repeat;
public enum RepeatStatus {
diff --git a/src/main/java/org/springframework/repeat/callback/NestedRepeatCallback.java b/src/main/java/org/springframework/repeat/callback/NestedRepeatCallback.java
new file mode 100644
index 0000000..c5708fe
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/callback/NestedRepeatCallback.java
@@ -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.repeat.callback;
+
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * Callback that delegates to another callback, via a {@link RepeatOperations}
+ * instance. Useful when nesting or composing batches in one another, e.g. for
+ * breaking a batch down into chunks.
+ *
+ * @author Dave Syer
+ *
+ */
+public class NestedRepeatCallback implements RepeatCallback {
+
+ private RepeatOperations template;
+
+ private RepeatCallback callback;
+
+ /**
+ * Constructor setting mandatory fields.
+ *
+ * @param template the {@link RepeatOperations} to use when calling the
+ * delegate callback
+ * @param callback the {@link RepeatCallback} delegate
+ */
+ public NestedRepeatCallback(RepeatOperations template, RepeatCallback callback) {
+ super();
+ this.template = template;
+ this.callback = callback;
+ }
+
+ /**
+ * Simply calls template.execute(callback). Clients can use this to repeat a
+ * batch process, or to break a process up into smaller chunks (e.g. to
+ * change the transaction boundaries).
+ *
+ * @see org.springframework.repeat.RepeatCallback#doInIteration(RepeatContext)
+ */
+ public RepeatStatus doInIteration(RepeatContext context) throws Exception {
+ return template.iterate(callback);
+ }
+}
diff --git a/src/main/java/org/springframework/repeat/callback/package.html b/src/main/java/org/springframework/repeat/callback/package.html
new file mode 100644
index 0000000..a25a6cf
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/callback/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat callback concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/context/RepeatContextCounter.java b/src/main/java/org/springframework/repeat/context/RepeatContextCounter.java
new file mode 100644
index 0000000..9830607
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/context/RepeatContextCounter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.repeat.context;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class for policies that need to count the number of occurrences of
+ * some event (e.g. an exception type in the context) in the scope of a batch.
+ * The value of the counter can be stored between batches in a nested context,
+ * so that the termination decision is based on the aggregate of a number of
+ * sibling batches.
+ *
+ * @author Dave Syer
+ *
+ */
+public class RepeatContextCounter {
+
+ final private String countKey;
+
+ /**
+ * Flag to indicate whether the count is stored at the level of the parent
+ * context, or just local to the current context. Default value is false.
+ */
+ final private boolean useParent;
+
+ final private RepeatContext context;
+
+ /**
+ * Increment the counter.
+ *
+ * @param delta the amount by which to increment the counter.
+ */
+ final public void increment(int delta) {
+ AtomicInteger count = getCounter();
+ count.addAndGet(delta);
+ }
+
+ /**
+ * Increment by 1.
+ */
+ final public void increment() {
+ increment(1);
+ }
+
+ /**
+ * Convenience constructor with useParent=false.
+ * @param context the current context.
+ * @param countKey the key to use to store the counter in the context.
+ */
+ public RepeatContextCounter(RepeatContext context, String countKey) {
+ this(context, countKey, false);
+ }
+
+ /**
+ * Construct a new {@link RepeatContextCounter}.
+ *
+ * @param context the current context.
+ * @param countKey the key to use to store the counter in the context.
+ * @param useParent true if the counter is to be shared between siblings.
+ * The state will be stored in the parent of the context (if it exists)
+ * instead of the context itself.
+ */
+ public RepeatContextCounter(RepeatContext context, String countKey, boolean useParent) {
+
+ super();
+
+ Assert.notNull(context, "The context must be provided to initialize a counter");
+
+ this.countKey = countKey;
+ this.useParent = useParent;
+
+ RepeatContext parent = context.getParent();
+
+ if (this.useParent && parent != null) {
+ this.context = parent;
+ }
+ else {
+ this.context = context;
+ }
+ if (!this.context.hasAttribute(countKey)) {
+ this.context.setAttribute(countKey, new AtomicInteger());
+ }
+
+ }
+
+ /**
+ * @return the current value of the counter
+ */
+ public int getCount() {
+ return getCounter().intValue();
+ }
+
+ private AtomicInteger getCounter() {
+ return ((AtomicInteger) context.getAttribute(countKey));
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/context/RepeatContextSupport.java b/src/main/java/org/springframework/repeat/context/RepeatContextSupport.java
new file mode 100644
index 0000000..8430b7a
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/context/RepeatContextSupport.java
@@ -0,0 +1,178 @@
+/*
+ * 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.repeat.context;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.repeat.RepeatContext;
+
+public class RepeatContextSupport extends SynchronizedAttributeAccessor implements RepeatContext {
+
+ private RepeatContext parent;
+
+ private int count;
+
+ private volatile boolean completeOnly;
+
+ private volatile boolean terminateOnly;
+
+ private Map> callbacks = new HashMap>();
+
+ /**
+ * Constructor for {@link RepeatContextSupport}. The parent can be null, but
+ * should be set to the enclosing repeat context if there is one, e.g. if
+ * this context is an inner loop.
+ * @param parent
+ */
+ public RepeatContextSupport(RepeatContext parent) {
+ super();
+ this.parent = parent;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#isCompleteOnly()
+ */
+ public boolean isCompleteOnly() {
+ return completeOnly;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#setCompleteOnly()
+ */
+ public void setCompleteOnly() {
+ completeOnly = true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#isTerminateOnly()
+ */
+ public boolean isTerminateOnly() {
+ return terminateOnly;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#setTerminateOnly()
+ */
+ public void setTerminateOnly() {
+ terminateOnly = true;
+ setCompleteOnly();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#getParent()
+ */
+ public RepeatContext getParent() {
+ return parent;
+ }
+
+ /**
+ * Used by clients to increment the started count.
+ */
+ public synchronized void increment() {
+ count++;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#getStartedCount()
+ */
+ public synchronized int getStartedCount() {
+ return count;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.batch.repeat.RepeatContext#registerDestructionCallback
+ * (java.lang.String, java.lang.Runnable)
+ */
+ public void registerDestructionCallback(String name, Runnable callback) {
+ synchronized (callbacks) {
+ Set set = callbacks.get(name);
+ if (set == null) {
+ set = new HashSet();
+ callbacks.put(name, set);
+ }
+ set.add(callback);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.RepeatContext#close()
+ */
+ public void close() {
+
+ List errors = new ArrayList();
+
+ Set>> copy;
+
+ synchronized (callbacks) {
+ copy = new HashSet>>(callbacks.entrySet());
+ }
+
+ for (Map.Entry> entry : copy) {
+
+ for (Runnable callback : entry.getValue()) {
+ /*
+ * Potentially we could check here if there is an attribute with
+ * the given name - if it has been removed, maybe the callback
+ * is invalid. On the other hand it is less surprising for the
+ * callback register if it is always executed.
+ */
+ if (callback != null) {
+ /*
+ * The documentation of the interface says that these
+ * callbacks must not throw exceptions, but we don't trust
+ * them necessarily...
+ */
+ try {
+ callback.run();
+ }
+ catch (RuntimeException t) {
+ errors.add(t);
+ }
+ }
+ }
+ }
+
+ if (errors.isEmpty()) {
+ return;
+ }
+
+ throw (RuntimeException) errors.get(0);
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/context/SynchronizedAttributeAccessor.java b/src/main/java/org/springframework/repeat/context/SynchronizedAttributeAccessor.java
new file mode 100644
index 0000000..f67b2b6
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/context/SynchronizedAttributeAccessor.java
@@ -0,0 +1,161 @@
+/*
+ * 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.repeat.context;
+
+import org.springframework.core.AttributeAccessor;
+import org.springframework.core.AttributeAccessorSupport;
+
+/**
+ * An {@link AttributeAccessor} that synchronizes on a mutex (not this) before
+ * modifying or accessing the underlying attributes.
+ *
+ * @author Dave Syer
+ *
+ */
+public class SynchronizedAttributeAccessor implements AttributeAccessor {
+
+ /**
+ * All methods are delegated to this support object.
+ */
+ AttributeAccessorSupport support = new AttributeAccessorSupport() {
+ /**
+ * Generated serial UID.
+ */
+ private static final long serialVersionUID = -7664290016506582290L;
+ };
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.AttributeAccessor#attributeNames()
+ */
+ public String[] attributeNames() {
+ synchronized (support) {
+ return support.attributeNames();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ AttributeAccessorSupport that;
+ if (other instanceof SynchronizedAttributeAccessor) {
+ that = ((SynchronizedAttributeAccessor) other).support;
+ }
+ else if (other instanceof AttributeAccessorSupport) {
+ that = (AttributeAccessorSupport) other;
+ }
+ else {
+ return false;
+ }
+ synchronized (support) {
+ return support.equals(that);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.AttributeAccessor#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name) {
+ synchronized (support) {
+ return support.getAttribute(name);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.AttributeAccessor#hasAttribute(java.lang.String)
+ */
+ public boolean hasAttribute(String name) {
+ synchronized (support) {
+ return support.hasAttribute(name);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return support.hashCode();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.AttributeAccessor#removeAttribute(java.lang.String)
+ */
+ public Object removeAttribute(String name) {
+ synchronized (support) {
+ return support.removeAttribute(name);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.core.AttributeAccessor#setAttribute(java.lang.String,
+ * java.lang.Object)
+ */
+ public void setAttribute(String name, Object value) {
+ synchronized (support) {
+ support.setAttribute(name, value);
+ }
+ }
+
+ /**
+ * Additional support for atomic put if absent.
+ * @param name the key for the attribute name
+ * @param value the value of the attribute
+ * @return null if the attribute was not already set, the existing value
+ * otherwise.
+ */
+ public Object setAttributeIfAbsent(String name, Object value) {
+ synchronized (support) {
+ Object old = getAttribute(name);
+ if (old != null) {
+ return old;
+ }
+ setAttribute(name, value);
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer("SynchronizedAttributeAccessor: [");
+ synchronized (support) {
+ String[] names = attributeNames();
+ for (int i = 0; i < names.length; i++) {
+ String name = names[i];
+ buffer.append(names[i]).append("=").append(getAttribute(name));
+ if (i < names.length - 1) {
+ buffer.append(", ");
+ }
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/context/package.html b/src/main/java/org/springframework/repeat/context/package.html
new file mode 100644
index 0000000..7bb43fa
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/context/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat context concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/exception/CompositeExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/CompositeExceptionHandler.java
new file mode 100644
index 0000000..ba808ef
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/CompositeExceptionHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.repeat.exception;
+
+import java.util.Arrays;
+
+import org.springframework.repeat.RepeatContext;
+
+/**
+ * Composiste {@link ExceptionHandler} that loops though a list of delegates.
+ *
+ * @author Dave Syer
+ *
+ */
+public class CompositeExceptionHandler implements ExceptionHandler {
+
+ private ExceptionHandler[] handlers = new ExceptionHandler[0];
+
+ public void setHandlers(ExceptionHandler[] handlers) {
+ this.handlers = Arrays.asList(handlers).toArray(new ExceptionHandler[handlers.length]);
+ }
+
+ /**
+ * Iterate over the handlers delegating the call to each in turn. The chain
+ * ends if an exception is thrown.
+ *
+ * @see ExceptionHandler#handleException(RepeatContext, Throwable)
+ */
+ public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
+ for (int i = 0; i < handlers.length; i++) {
+ ExceptionHandler handler = handlers[i];
+ handler.handleException(context, throwable);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/repeat/exception/DefaultExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/DefaultExceptionHandler.java
new file mode 100644
index 0000000..eb1ce51
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/DefaultExceptionHandler.java
@@ -0,0 +1,39 @@
+/*
+ * 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.repeat.exception;
+
+import org.springframework.repeat.RepeatContext;
+
+/**
+ * Default implementation of {@link ExceptionHandler} - just re-throws the exception it encounters.
+ *
+ * @author Dave Syer
+ *
+ */
+public class DefaultExceptionHandler implements ExceptionHandler {
+
+ /**
+ * Re-throw the throwable.
+ *
+ * @see org.springframework.repeat.exception.ExceptionHandler#handleException(RepeatContext,
+ * Throwable)
+ */
+ public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
+ throw throwable;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/exception/ExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/ExceptionHandler.java
new file mode 100644
index 0000000..0634952
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/ExceptionHandler.java
@@ -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.repeat.exception;
+
+import org.springframework.repeat.CompletionPolicy;
+import org.springframework.repeat.RepeatContext;
+
+/**
+ * Handler to allow strategies for re-throwing exceptions. Normally a
+ * {@link CompletionPolicy} will be used to decide whether to end a batch when
+ * there is no exception, and the {@link ExceptionHandler} is used to signal an
+ * abnormal ending - an abnormal ending would result in an
+ * {@link ExceptionHandler} throwing an exception. The caller will catch and
+ * re-throw it if necessary.
+ *
+ * @author Dave Syer
+ * @author Robert Kasanicky
+ *
+ */
+public interface ExceptionHandler {
+
+ /**
+ * Deal with a Throwable during a batch - decide whether it should be
+ * re-thrown in the first place.
+ *
+ * @param context the current {@link RepeatContext}. Can be used to store
+ * state (via attributes), for example to count the number of occurrences of
+ * a particular exception type and implement a threshold policy.
+ * @param throwable an exception.
+ * @throws Throwable implementations are free to re-throw the exception
+ */
+ void handleException(RepeatContext context, Throwable throwable) throws Throwable;
+
+}
diff --git a/src/main/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandler.java
new file mode 100644
index 0000000..51f03d1
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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.repeat.exception;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.classify.Classifier;
+import org.springframework.classify.ClassifierSupport;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatException;
+
+/**
+ * Implementation of {@link ExceptionHandler} based on an {@link Classifier}.
+ * The classifier determines whether to log the exception or rethrow it. The
+ * keys in the classifier must be the same as the static enum in this class.
+ *
+ * @author Dave Syer
+ *
+ */
+public class LogOrRethrowExceptionHandler implements ExceptionHandler {
+
+ /**
+ * Logging levels for the handler.
+ *
+ * @author Dave Syer
+ *
+ */
+ public static enum Level {
+
+ /**
+ * Key for {@link Classifier} signalling that the throwable should be
+ * rethrown. If the throwable is not a RuntimeException it is wrapped in
+ * a {@link RepeatException}.
+ */
+ RETHROW,
+
+ /**
+ * Key for {@link Classifier} signalling that the throwable should be
+ * logged at debug level.
+ */
+ DEBUG,
+
+ /**
+ * Key for {@link Classifier} signalling that the throwable should be
+ * logged at warn level.
+ */
+ WARN,
+
+ /**
+ * Key for {@link Classifier} signalling that the throwable should be
+ * logged at error level.
+ */
+ ERROR
+
+ }
+
+ protected final Log logger = LogFactory.getLog(LogOrRethrowExceptionHandler.class);
+
+ private Classifier exceptionClassifier = new ClassifierSupport(Level.RETHROW);
+
+ /**
+ * Setter for the {@link Classifier} used by this handler. The default is to
+ * map all throwable instances to {@link Level#RETHROW}.
+ *
+ * @param exceptionClassifier the ExceptionClassifier to use
+ */
+ public void setExceptionClassifier(Classifier exceptionClassifier) {
+ this.exceptionClassifier = exceptionClassifier;
+ }
+
+ /**
+ * Classify the throwables and decide whether to rethrow based on the
+ * result. The context is not used.
+ *
+ * @throws Throwable
+ *
+ * @see ExceptionHandler#handleException(RepeatContext, Throwable)
+ */
+ public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
+
+ Level key = exceptionClassifier.classify(throwable);
+ if (Level.ERROR.equals(key)) {
+ logger.error("Exception encountered in batch repeat.", throwable);
+ }
+ else if (Level.WARN.equals(key)) {
+ logger.warn("Exception encountered in batch repeat.", throwable);
+ }
+ else if (Level.DEBUG.equals(key) && logger.isDebugEnabled()) {
+ logger.debug("Exception encountered in batch repeat.", throwable);
+ }
+ else if (Level.RETHROW.equals(key)) {
+ throw throwable;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandler.java
new file mode 100644
index 0000000..22b61a9
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandler.java
@@ -0,0 +1,150 @@
+/*
+ * 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.repeat.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.classify.Classifier;
+import org.springframework.classify.SubclassClassifier;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.context.RepeatContextCounter;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Implementation of {@link ExceptionHandler} that rethrows when exceptions of a
+ * given type reach a threshold. Requires an {@link Classifier} that maps
+ * exception types to unique keys, and also a map from those keys to threshold
+ * values (Integer type).
+ *
+ * @author Dave Syer
+ *
+ */
+public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
+
+ protected static final IntegerHolder ZERO = new IntegerHolder(0);
+
+ protected final Log logger = LogFactory.getLog(RethrowOnThresholdExceptionHandler.class);
+
+ private Classifier super Throwable, IntegerHolder> exceptionClassifier = new Classifier() {
+ public RethrowOnThresholdExceptionHandler.IntegerHolder classify(Throwable classifiable) {
+ return ZERO;
+ }
+ };
+
+ private boolean useParent = false;
+
+ /**
+ * Flag to indicate the the exception counters should be shared between
+ * sibling contexts in a nested batch. Default is false.
+ *
+ * @param useParent true if the parent context should be used to store the
+ * counters.
+ */
+ public void setUseParent(boolean useParent) {
+ this.useParent = useParent;
+ }
+
+ /**
+ * Set up the exception handler. Creates a default exception handler and
+ * threshold that maps all exceptions to a threshold of 0 - all exceptions
+ * are rethrown by default.
+ */
+ public RethrowOnThresholdExceptionHandler() {
+ super();
+ }
+
+ /**
+ * A map from exception classes to a threshold value of type Integer.
+ *
+ * @param thresholds the threshold value map.
+ */
+ public void setThresholds(Map, Integer> thresholds) {
+ Map, IntegerHolder> typeMap = new HashMap, IntegerHolder>();
+ for (Entry, Integer> entry : thresholds.entrySet()) {
+ typeMap.put(entry.getKey(), new IntegerHolder(entry.getValue()));
+ }
+ exceptionClassifier = new SubclassClassifier(typeMap, ZERO);
+ }
+
+ /**
+ * Classify the throwables and decide whether to re-throw based on the
+ * result. The context is used to accumulate the number of exceptions of the
+ * same type according to the classifier.
+ *
+ * @throws Throwable
+ * @see ExceptionHandler#handleException(RepeatContext, Throwable)
+ */
+ public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
+
+ IntegerHolder key = exceptionClassifier.classify(throwable);
+
+ RepeatContextCounter counter = getCounter(context, key);
+ counter.increment();
+ int count = counter.getCount();
+ int threshold = key.getValue();
+ if (count > threshold) {
+ throw throwable;
+ }
+
+ }
+
+ private RepeatContextCounter getCounter(RepeatContext context, IntegerHolder key) {
+ String attribute = RethrowOnThresholdExceptionHandler.class.getName() + "." + key;
+ // Creates a new counter and stores it in the correct context:
+ return new RepeatContextCounter(context, attribute, useParent);
+ }
+
+ /**
+ * @author Dave Syer
+ *
+ */
+ private static class IntegerHolder {
+
+ private final int value;
+
+ /**
+ * @param value
+ */
+ public IntegerHolder(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Public getter for the value.
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return ObjectUtils.getIdentityHexString(this) + "." + value;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/exception/SimpleLimitExceptionHandler.java b/src/main/java/org/springframework/repeat/exception/SimpleLimitExceptionHandler.java
new file mode 100644
index 0000000..1786091
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/SimpleLimitExceptionHandler.java
@@ -0,0 +1,147 @@
+/*
+ * 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.repeat.exception;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.repeat.RepeatContext;
+
+/**
+ * Simple implementation of exception handler which looks for given exception
+ * types. If one of the types is found then a counter is incremented and the
+ * limit is checked to determine if it has been exceeded and the Throwable
+ * should be re-thrown. Also allows to specify list of 'fatal' exceptions that
+ * are never subject to counting, but are immediately re-thrown. The fatal list
+ * has higher priority so the two lists needn't be exclusive.
+ *
+ * @author Dave Syer
+ * @author Robert Kasanicky
+ */
+public class SimpleLimitExceptionHandler implements ExceptionHandler, InitializingBean {
+
+ private RethrowOnThresholdExceptionHandler delegate = new RethrowOnThresholdExceptionHandler();
+
+ private Collection> exceptionClasses = Collections
+ .> singleton(Exception.class);
+
+ private Collection> fatalExceptionClasses = Collections
+ .> singleton(Error.class);
+
+ private int limit = 0;
+
+ /**
+ * Apply the provided properties to create a delegate handler.
+ *
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() throws Exception {
+ if (limit <= 0) {
+ return;
+ }
+ Map, Integer> thresholds = new HashMap, Integer>();
+ for (Class extends Throwable> type : exceptionClasses) {
+ thresholds.put(type, limit);
+ }
+ // do the fatalExceptionClasses last so they override the others
+ for (Class extends Throwable> type : fatalExceptionClasses) {
+ thresholds.put(type, 0);
+ }
+ delegate.setThresholds(thresholds);
+ }
+
+ /**
+ * Flag to indicate the the exception counters should be shared between
+ * sibling contexts in a nested batch (i.e. inner loop). Default is false.
+ * Set this flag to true if you want to count exceptions for the whole
+ * (outer) loop in a typical container.
+ *
+ * @param useParent true if the parent context should be used to store the
+ * counters.
+ */
+ public void setUseParent(boolean useParent) {
+ delegate.setUseParent(useParent);
+ }
+
+ /**
+ * Convenience constructor for the {@link SimpleLimitExceptionHandler} to
+ * set the limit.
+ *
+ * @param limit the limit
+ */
+ public SimpleLimitExceptionHandler(int limit) {
+ this();
+ this.limit = limit;
+ }
+
+ /**
+ * Default constructor for the {@link SimpleLimitExceptionHandler}.
+ */
+ public SimpleLimitExceptionHandler() {
+ super();
+ }
+
+ /**
+ * Rethrows only if the limit is breached for this context on the exception
+ * type specified.
+ *
+ * @see #setExceptionClasses(Collection)
+ * @see #setLimit(int)
+ *
+ * @see org.springframework.repeat.exception.ExceptionHandler#handleException(org.springframework.repeat.RepeatContext,
+ * Throwable)
+ */
+ public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
+ delegate.handleException(context, throwable);
+ }
+
+ /**
+ * The limit on the given exception type within a single context before it
+ * is rethrown.
+ *
+ * @param limit the limit
+ */
+ public void setLimit(final int limit) {
+ this.limit = limit;
+ }
+
+ /**
+ * Setter for the exception classes that this handler counts. Defaults to
+ * {@link Exception}. If more exceptionClasses are specified handler uses
+ * single counter that is incremented when one of the recognized exception
+ * exceptionClasses is handled.
+ * @param classes exceptionClasses
+ */
+ public void setExceptionClasses(Collection> classes) {
+ this.exceptionClasses = classes;
+ }
+
+ /**
+ * Setter for the exception classes that shouldn't be counted, but rethrown
+ * immediately. This list has higher priority than
+ * {@link #setExceptionClasses(Collection)}.
+ *
+ * @param fatalExceptionClasses defaults to {@link Error}
+ */
+ public void setFatalExceptionClasses(Collection> fatalExceptionClasses) {
+ this.fatalExceptionClasses = fatalExceptionClasses;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/exception/package.html b/src/main/java/org/springframework/repeat/exception/package.html
new file mode 100644
index 0000000..edbc051
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/exception/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat exception handler concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptor.java b/src/main/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptor.java
new file mode 100644
index 0000000..37c5c3b
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptor.java
@@ -0,0 +1,203 @@
+/*
+ * 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.repeat.interceptor;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.ProxyMethodInvocation;
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatException;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.support.RepeatTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link MethodInterceptor} that can be used to automatically repeat calls to
+ * a method on a service. The injected {@link RepeatOperations} is used to
+ * control the completion of the loop. Independent of the completion policy in
+ * the {@link RepeatOperations} the loop will repeat until the target method
+ * returns null or false. Be careful when injecting a bespoke
+ * {@link RepeatOperations} that the loop will actually terminate, because the
+ * default policy for a vanilla {@link RepeatTemplate} will never complete if
+ * the return type of the target method is void (the value returned is always
+ * not-null, representing the {@link Void#TYPE}).
+ *
+ * @author Dave Syer
+ */
+public class RepeatOperationsInterceptor implements MethodInterceptor {
+
+ private RepeatOperations repeatOperations = new RepeatTemplate();
+
+ /**
+ * Setter for the {@link RepeatOperations}.
+ *
+ * @param batchTempate
+ * @throws IllegalArgumentException if the argument is null.
+ */
+ public void setRepeatOperations(RepeatOperations batchTempate) {
+ Assert.notNull(batchTempate, "'repeatOperations' cannot be null.");
+ this.repeatOperations = batchTempate;
+ }
+
+ /**
+ * Invoke the proceeding method call repeatedly, according to the properties
+ * of the injected {@link RepeatOperations}.
+ *
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(final MethodInvocation invocation) throws Throwable {
+
+ final ResultHolder result = new ResultHolder();
+ // Cache void return value if intercepted method returns void
+ final boolean voidReturnType = Void.TYPE.equals(invocation.getMethod().getReturnType());
+ if (voidReturnType) {
+ // This will be ignored anyway, but we want it to be non-null for
+ // convenience of checking that there is a result.
+ result.setValue(new Object());
+ }
+
+ try {
+ repeatOperations.iterate(new RepeatCallback() {
+
+ public RepeatStatus doInIteration(RepeatContext context) throws Exception {
+ try {
+
+ MethodInvocation clone = invocation;
+ if (invocation instanceof ProxyMethodInvocation) {
+ clone = ((ProxyMethodInvocation) invocation).invocableClone();
+ }
+ 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");
+ }
+
+ Object value = clone.proceed();
+ if (voidReturnType) {
+ return RepeatStatus.CONTINUABLE;
+ }
+ if (!isComplete(value)) {
+ // Save the last result
+ result.setValue(value);
+ return RepeatStatus.CONTINUABLE;
+ }
+ else {
+ result.setFinalValue(value);
+ return RepeatStatus.FINISHED;
+ }
+ }
+ catch (Throwable e) {
+ if (e instanceof Exception) {
+ throw (Exception) e;
+ }
+ else {
+ throw new RepeatOperationsInterceptorException("Unexpected error in batch interceptor", e);
+ }
+ }
+ }
+
+ });
+ }
+ catch (Throwable t) {
+ // The repeat exception should be unwrapped by the template
+ throw t;
+ }
+
+ if (result.isReady()) {
+ return result.getValue();
+ }
+
+ // No result means something weird happened
+ throw new IllegalStateException("No result available for attempted repeat call to " + invocation
+ + ". The invocation was never called, so maybe there is a problem with the completion policy?");
+ }
+
+ /**
+ * @param result
+ * @return
+ */
+ private boolean isComplete(Object result) {
+ return result == null || (result instanceof Boolean) && !((Boolean) result).booleanValue();
+ }
+
+ /**
+ * Simple wrapper exception class to enable nasty errors to be passed out of
+ * the scope of the repeat operations and handled by the caller.
+ *
+ * @author Dave Syer
+ *
+ */
+ private static class RepeatOperationsInterceptorException extends RepeatException {
+ /**
+ * @param message
+ * @param e
+ */
+ public RepeatOperationsInterceptorException(String message, Throwable e) {
+ super(message, e);
+ }
+ }
+
+ /**
+ * Simple wrapper object for the result from a method invocation.
+ *
+ * @author Dave Syer
+ *
+ */
+ private static class ResultHolder {
+ private Object value = null;
+
+ private boolean ready = false;
+
+ /**
+ * Public setter for the Object.
+ * @param value the value to set
+ */
+ public void setValue(Object value) {
+ this.ready = true;
+ this.value = value;
+ }
+
+ /**
+ * @param value
+ */
+ public void setFinalValue(Object value) {
+ if (ready) {
+ // Only set the value the last time if the last time was also
+ // the first time
+ return;
+ }
+ setValue(value);
+ }
+
+ /**
+ * Public getter for the Object.
+ * @return the value
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * @return true if a value has been set
+ */
+ public boolean isReady() {
+ return ready;
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/interceptor/package.html b/src/main/java/org/springframework/repeat/interceptor/package.html
new file mode 100644
index 0000000..bb348ce
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/interceptor/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat aop concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/listener/CompositeRepeatListener.java b/src/main/java/org/springframework/repeat/listener/CompositeRepeatListener.java
new file mode 100644
index 0000000..57c3204
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/listener/CompositeRepeatListener.java
@@ -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.repeat.listener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatListener;
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class CompositeRepeatListener implements RepeatListener {
+
+ private List listeners = new ArrayList();
+
+ /**
+ * Public setter for the listeners.
+ *
+ * @param listeners
+ */
+ public void setListeners(RepeatListener[] listeners) {
+ this.listeners = Arrays.asList(listeners);
+ }
+
+ /**
+ * Register additional listener.
+ *
+ * @param listener
+ */
+ public void register(RepeatListener listener) {
+ if (!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.RepeatListener#after(org.springframework.batch.repeat.RepeatContext, org.springframework.batch.repeat.ExitStatus)
+ */
+ public void after(RepeatContext context, RepeatStatus result) {
+ for (RepeatListener listener : listeners) {
+ listener.after(context, result);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.RepeatListener#before(org.springframework.batch.repeat.RepeatContext)
+ */
+ public void before(RepeatContext context) {
+ for (RepeatListener listener : listeners) {
+ listener.before(context);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.RepeatListener#close(org.springframework.batch.repeat.RepeatContext)
+ */
+ public void close(RepeatContext context) {
+ for (RepeatListener listener : listeners) {
+ listener.close(context);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.RepeatListener#onError(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)
+ */
+ public void onError(RepeatContext context, Throwable e) {
+ for (RepeatListener listener : listeners) {
+ listener.onError(context, e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.RepeatListener#open(org.springframework.batch.repeat.RepeatContext)
+ */
+ public void open(RepeatContext context) {
+ for (RepeatListener listener : listeners) {
+ listener.open(context);
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/listener/RepeatListenerSupport.java b/src/main/java/org/springframework/repeat/listener/RepeatListenerSupport.java
new file mode 100644
index 0000000..8335adb
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/listener/RepeatListenerSupport.java
@@ -0,0 +1,46 @@
+/*
+ * 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.repeat.listener;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatListener;
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * Empty method implementation of {@link RepeatListener}.
+ *
+ * @author Dave Syer
+ *
+ */
+public class RepeatListenerSupport implements RepeatListener {
+
+ public void before(RepeatContext context) {
+ }
+
+ public void after(RepeatContext context, RepeatStatus result) {
+ }
+
+ public void close(RepeatContext context) {
+ }
+
+ public void onError(RepeatContext context, Throwable e) {
+ }
+
+ public void open(RepeatContext context) {
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/listener/package.html b/src/main/java/org/springframework/repeat/listener/package.html
new file mode 100644
index 0000000..8a95d83
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/listener/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat interceptor concerns.
+
+Infrastructure implementations of repeat concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/policy/CompletionPolicySupport.java b/src/main/java/org/springframework/repeat/policy/CompletionPolicySupport.java
new file mode 100644
index 0000000..952dfe9
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/CompletionPolicySupport.java
@@ -0,0 +1,77 @@
+/*
+ * 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.repeat.policy;
+
+import org.springframework.repeat.CompletionPolicy;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+/**
+ * Very simple base class for {@link CompletionPolicy} implementations.
+ *
+ * @author Dave Syer
+ *
+ */
+public class CompletionPolicySupport implements CompletionPolicy {
+
+ /**
+ * If exit status is not continuable return true, otherwise
+ * delegate to {@link #isComplete(RepeatContext)}.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
+ * RepeatStatus)
+ */
+ public boolean isComplete(RepeatContext context, RepeatStatus result) {
+ if (result != null && !result.isContinuable()) {
+ return true;
+ }
+ else {
+ return isComplete(context);
+ }
+ }
+
+ /**
+ * Always true.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
+ */
+ public boolean isComplete(RepeatContext context) {
+ return true;
+ }
+
+ /**
+ * Build a new {@link RepeatContextSupport} and return it.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
+ */
+ public RepeatContext start(RepeatContext context) {
+ return new RepeatContextSupport(context);
+ }
+
+ /**
+ * Increment the context so the counter is up to date. Do nothing else.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#update(org.springframework.repeat.RepeatContext)
+ */
+ public void update(RepeatContext context) {
+ if (context instanceof RepeatContextSupport) {
+ ((RepeatContextSupport) context).increment();
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/policy/CompositeCompletionPolicy.java b/src/main/java/org/springframework/repeat/policy/CompositeCompletionPolicy.java
new file mode 100644
index 0000000..589c6ae
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/CompositeCompletionPolicy.java
@@ -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.repeat.policy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.repeat.CompletionPolicy;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+/**
+ * Composite policy that loops through a list of delegate policies and answers
+ * calls by a concensus.
+ *
+ * @author Dave Syer
+ *
+ */
+public class CompositeCompletionPolicy implements CompletionPolicy {
+
+ CompletionPolicy[] policies = new CompletionPolicy[0];
+
+ /**
+ * Setter for the policies.
+ *
+ * @param policies
+ */
+ public void setPolicies(CompletionPolicy[] policies) {
+ this.policies = Arrays.asList(policies).toArray(new CompletionPolicy[policies.length]);
+ }
+
+ /**
+ * This policy is complete if any of the composed policies is complete.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
+ * RepeatStatus)
+ */
+ public boolean isComplete(RepeatContext context, RepeatStatus result) {
+ RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
+ CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
+ for (int i = 0; i < policies.length; i++) {
+ if (policies[i].isComplete(contexts[i], result)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This policy is complete if any of the composed policies is complete.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
+ */
+ public boolean isComplete(RepeatContext context) {
+ RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
+ CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
+ for (int i = 0; i < policies.length; i++) {
+ if (policies[i].isComplete(contexts[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create a new composite context from all the available policies.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
+ */
+ public RepeatContext start(RepeatContext context) {
+ List list = new ArrayList();
+ for (int i = 0; i < policies.length; i++) {
+ list.add(policies[i].start(context));
+ }
+ return new CompositeBatchContext(context, list);
+
+ }
+
+ /**
+ * Update all the composed contexts, and also increment the parent context.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#update(org.springframework.repeat.RepeatContext)
+ */
+ public void update(RepeatContext context) {
+ RepeatContext[] contexts = ((CompositeBatchContext) context).contexts;
+ CompletionPolicy[] policies = ((CompositeBatchContext) context).policies;
+ for (int i = 0; i < policies.length; i++) {
+ policies[i].update(contexts[i]);
+ }
+ ((RepeatContextSupport) context).increment();
+ }
+
+ /**
+ * Composite context that knows about the policies and contexts is was
+ * created with.
+ *
+ * @author Dave Syer
+ *
+ */
+ protected class CompositeBatchContext extends RepeatContextSupport {
+
+ private RepeatContext[] contexts;
+
+ // Save a reference to the policies when we were created - gives some
+ // protection against reference changes (e.g. if the number of policies
+ // change).
+ private CompletionPolicy[] policies;
+
+ public CompositeBatchContext(RepeatContext context, List contexts) {
+ super(context);
+ this.contexts = contexts.toArray(new RepeatContext[contexts.size()]);
+ this.policies = CompositeCompletionPolicy.this.policies;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/policy/CountingCompletionPolicy.java b/src/main/java/org/springframework/repeat/policy/CountingCompletionPolicy.java
new file mode 100644
index 0000000..528a6c3
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/CountingCompletionPolicy.java
@@ -0,0 +1,132 @@
+/*
+ * 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.repeat.policy;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.context.RepeatContextCounter;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+/**
+ * Abstract base class for policies that need to count the number of occurrences
+ * of some event (e.g. an exception type in the context), and terminate based on
+ * a limit for the counter. The value of the counter can be stored between
+ * batches in a nested context, so that the termination decision is based on the
+ * aggregate of a number of sibling batches.
+ *
+ * @author Dave Syer
+ *
+ */
+public abstract class CountingCompletionPolicy extends DefaultResultCompletionPolicy {
+
+ /**
+ * Session key for global counter.
+ */
+ public static final String COUNT = CountingCompletionPolicy.class.getName() + ".COUNT";
+
+ private boolean useParent = false;
+
+ private int maxCount = 0;
+
+ /**
+ * Flag to indicate whether the count is at the level of the parent context,
+ * or just local to the context. If true then the count is aggregated among
+ * siblings in a nested batch.
+ *
+ * @param useParent whether to use the parent context to cache the total
+ * count. Default value is false.
+ */
+ public void setUseParent(boolean useParent) {
+ this.useParent = useParent;
+ }
+
+ /**
+ * Setter for maximum value of count before termination.
+ *
+ * @param maxCount the maximum number of counts before termination. Default
+ * 0 so termination is immediate.
+ */
+ public void setMaxCount(int maxCount) {
+ this.maxCount = maxCount;
+ }
+
+ /**
+ * Extension point for subclasses. Obtain the value of the count in the
+ * current context. Subclasses can count the number of attempts or
+ * violations and store the result in their context. This policy base class
+ * will take care of the termination contract and aggregating at the level
+ * of the session if required.
+ *
+ * @param context the current context, specific to the subclass.
+ * @return the value of the counter in the context.
+ */
+ protected abstract int getCount(RepeatContext context);
+
+ /**
+ * Extension point for subclasses. Inspect the context and update the state
+ * of a counter in whatever way is appropriate. This will be added to the
+ * session-level counter if {@link #setUseParent(boolean)} is true.
+ *
+ * @param context the current context.
+ *
+ * @return the change in the value of the counter (default 0).
+ */
+ protected int doUpdate(RepeatContext context) {
+ return 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.batch.repeat.policy.CompletionPolicySupport#isComplete(org.springframework.batch.repeat.BatchContext)
+ */
+ final public boolean isComplete(RepeatContext context) {
+ int count = ((CountingBatchContext) context).getCounter().getCount();
+ return count >= maxCount;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.batch.repeat.policy.CompletionPolicySupport#start(org.springframework.batch.repeat.BatchContext)
+ */
+ public RepeatContext start(RepeatContext parent) {
+ return new CountingBatchContext(parent);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.batch.repeat.policy.CompletionPolicySupport#update(org.springframework.batch.repeat.BatchContext)
+ */
+ final public void update(RepeatContext context) {
+ super.update(context);
+ int delta = doUpdate(context);
+ ((CountingBatchContext) context).getCounter().increment(delta);
+ }
+
+ protected class CountingBatchContext extends RepeatContextSupport {
+
+ RepeatContextCounter counter;
+
+ public CountingBatchContext(RepeatContext parent) {
+ super(parent);
+ counter = new RepeatContextCounter(this, COUNT, useParent);
+ }
+
+ public RepeatContextCounter getCounter() {
+ return counter;
+ }
+
+ }
+}
diff --git a/src/main/java/org/springframework/repeat/policy/DefaultResultCompletionPolicy.java b/src/main/java/org/springframework/repeat/policy/DefaultResultCompletionPolicy.java
new file mode 100644
index 0000000..782c7b7
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/DefaultResultCompletionPolicy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.repeat.policy;
+
+import org.springframework.repeat.CompletionPolicy;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * Very simple {@link CompletionPolicy} that bases its decision on the result of
+ * a batch operation. If the result is null or not continuable according to the
+ * {@link RepeatStatus} the batch is complete, otherwise not.
+ *
+ * @author Dave Syer
+ *
+ */
+public class DefaultResultCompletionPolicy extends CompletionPolicySupport {
+
+ /**
+ * True if the result is null, or a {@link RepeatStatus} indicating
+ * completion.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext,
+ * RepeatStatus)
+ */
+ public boolean isComplete(RepeatContext context, RepeatStatus result) {
+ return (result == null || !result.isContinuable());
+ }
+
+ /**
+ * Always false.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
+ */
+ public boolean isComplete(RepeatContext context) {
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/policy/SimpleCompletionPolicy.java b/src/main/java/org/springframework/repeat/policy/SimpleCompletionPolicy.java
new file mode 100644
index 0000000..d0aa862
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/SimpleCompletionPolicy.java
@@ -0,0 +1,115 @@
+/*
+ * 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.repeat.policy;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.context.RepeatContextSupport;
+import org.springframework.repeat.support.RepeatTemplate;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Policy for terminating a batch after a fixed number of operations. Internal
+ * state is maintained and a counter incremented, so successful use of this
+ * policy requires that isComplete() is only called once per batch item. Using
+ * the standard {@link RepeatTemplate} should ensure this contract is kept, but it needs
+ * to be carefully monitored.
+ *
+ * @author Dave Syer
+ *
+ */
+public class SimpleCompletionPolicy extends DefaultResultCompletionPolicy {
+
+ public static final int DEFAULT_CHUNK_SIZE = 5;
+
+ int chunkSize = 0;
+
+ public SimpleCompletionPolicy() {
+ this(DEFAULT_CHUNK_SIZE);
+ }
+
+ public SimpleCompletionPolicy(int chunkSize) {
+ super();
+ this.chunkSize = chunkSize;
+ }
+
+ public void setChunkSize(int chunkSize) {
+ this.chunkSize = chunkSize;
+ }
+
+ /**
+ * Reset the counter.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
+ */
+ public RepeatContext start(RepeatContext context) {
+ return new SimpleTerminationContext(context);
+ }
+
+ /**
+ * Terminate if the chunk size has been reached, or the result is null.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext,
+ * RepeatStatus)
+ * @throws RuntimeException (normally terminating the batch) if the result is
+ * itself an exception.
+ */
+ public boolean isComplete(RepeatContext context, RepeatStatus result) {
+ return super.isComplete(context, result) || ((SimpleTerminationContext) context).isComplete();
+ }
+
+ /**
+ * Terminate if the chunk size has been reached.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext)
+ */
+ public boolean isComplete(RepeatContext context) {
+ return ((SimpleTerminationContext) context).isComplete();
+ }
+
+ /**
+ * Increment the counter in the context.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#update(RepeatContext)
+ */
+ public void update(RepeatContext context) {
+ ((SimpleTerminationContext) context).update();
+ }
+
+ protected class SimpleTerminationContext extends RepeatContextSupport {
+
+ public SimpleTerminationContext(RepeatContext context) {
+ super(context);
+ }
+
+ public void update() {
+ increment();
+ }
+
+ public boolean isComplete() {
+ return getStartedCount() >= chunkSize;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return ClassUtils.getShortName(SimpleCompletionPolicy.class)+": chunkSize="+chunkSize;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/policy/TimeoutTerminationPolicy.java b/src/main/java/org/springframework/repeat/policy/TimeoutTerminationPolicy.java
new file mode 100644
index 0000000..daf6485
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/TimeoutTerminationPolicy.java
@@ -0,0 +1,98 @@
+/*
+ * 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.repeat.policy;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+/**
+ * Termination policy that times out after a fixed period. Allows graceful exit
+ * from a batch if the latest result comes in after the timeout expires (i.e.
+ * does not throw a timeout exception).
+ *
+ * N.B. It may often be the case that the batch governed by this policy will be
+ * transactional, and the transaction might have its own timeout. In this case
+ * the transaction might throw a timeout exception on commit if its timeout
+ * threshold is lower than the termination policy.
+ *
+ * @author Dave Syer
+ *
+ */
+public class TimeoutTerminationPolicy extends CompletionPolicySupport {
+
+ /**
+ * Default timeout value in millisecs (the value equivalent to 30 seconds).
+ */
+ public static final long DEFAULT_TIMEOUT = 30000L;
+
+ private long timeout = DEFAULT_TIMEOUT;
+
+ /**
+ * Default constructor.
+ */
+ public TimeoutTerminationPolicy() {
+ super();
+ }
+
+ /**
+ * Construct a {@link TimeoutTerminationPolicy} with the specified timeout
+ * value (in milliseconds).
+ *
+ * @param timeout
+ */
+ public TimeoutTerminationPolicy(long timeout) {
+ super();
+ this.timeout = timeout;
+ }
+
+ /**
+ * Check the timeout and complete gracefully if it has expires.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(org.springframework.repeat.RepeatContext)
+ */
+ @Override
+ public boolean isComplete(RepeatContext context) {
+ return ((TimeoutBatchContext) context).isComplete();
+ }
+
+ /**
+ * Start the clock on the timeout.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
+ */
+ @Override
+ public RepeatContext start(RepeatContext context) {
+ return new TimeoutBatchContext(context);
+ }
+
+ protected class TimeoutBatchContext extends RepeatContextSupport {
+
+ private volatile long time = System.currentTimeMillis();
+
+ private final long timeout = TimeoutTerminationPolicy.this.timeout;
+
+ public TimeoutBatchContext(RepeatContext context) {
+ super(context);
+ }
+
+ public boolean isComplete() {
+ return (System.currentTimeMillis() - time) > timeout;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/policy/package.html b/src/main/java/org/springframework/repeat/policy/package.html
new file mode 100644
index 0000000..e30f24f
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/policy/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat policy concerns.
+
+
+
diff --git a/src/main/java/org/springframework/repeat/support/RepeatInternalState.java b/src/main/java/org/springframework/repeat/support/RepeatInternalState.java
new file mode 100644
index 0000000..7767048
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/RepeatInternalState.java
@@ -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.repeat.support;
+
+import java.util.Collection;
+
+/**
+ * Internal interface for extensions of {@link RepeatTemplate}.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface RepeatInternalState {
+
+ /**
+ * Returns a mutable collection of exceptions that have occurred in the
+ * current repeat context. Clients are expected to mutate this collection.
+ *
+ * @return the collection of exceptions being accumulated
+ */
+ Collection getThrowables();
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/RepeatInternalStateSupport.java b/src/main/java/org/springframework/repeat/support/RepeatInternalStateSupport.java
new file mode 100644
index 0000000..d4dc2c5
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/RepeatInternalStateSupport.java
@@ -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.repeat.support;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class RepeatInternalStateSupport implements RepeatInternalState {
+
+ // Accumulation of failed results.
+ private final Set throwables = new HashSet();
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.repeat.support.BatchInternalState#getThrowables()
+ */
+ public Collection getThrowables() {
+ return throwables;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/RepeatSynchronizationManager.java b/src/main/java/org/springframework/repeat/support/RepeatSynchronizationManager.java
new file mode 100644
index 0000000..33cdc32
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/RepeatSynchronizationManager.java
@@ -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.repeat.support;
+
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatOperations;
+
+/**
+ * Global variable support for repeat clients. Normally it is not necessary for
+ * clients to be aware of the surrounding environment because a
+ * {@link RepeatCallback} can always use the context it is passed by the
+ * enclosing {@link RepeatOperations}. But occasionally it might be helpful to
+ * have lower level access to the ongoing {@link RepeatContext} so we provide a
+ * global accessor here. The mutator methods ({@link #clear()} and
+ * {@link #register(RepeatContext)} should not be used except internally by
+ * {@link RepeatOperations} implementations.
+ *
+ * @author Dave Syer
+ *
+ */
+public final class RepeatSynchronizationManager {
+
+ private static final ThreadLocal contextHolder = new ThreadLocal();
+
+ private RepeatSynchronizationManager() {
+ }
+
+ /**
+ * Getter for the current context. A context is shared by all items in the
+ * batch, so this method is intended to return the same context object
+ * independent of whether the callback is running synchronously or
+ * asynchronously with the surrounding {@link RepeatOperations}.
+ *
+ * @return the current {@link RepeatContext} or null if there is none (if we
+ * are not in a batch).
+ */
+ public static RepeatContext getContext() {
+ return contextHolder.get();
+ }
+
+ /**
+ * Convenience method to set the current repeat operation to complete if it
+ * exists.
+ */
+ public static void setCompleteOnly() {
+ RepeatContext context = getContext();
+ if (context != null) {
+ context.setCompleteOnly();
+ }
+ }
+
+ /**
+ * Method for registering a context - should only be used by
+ * {@link RepeatOperations} implementations to ensure that
+ * {@link #getContext()} always returns the correct value.
+ *
+ * @param context a new context at the start of a batch.
+ * @return the old value if there was one.
+ */
+ public static RepeatContext register(RepeatContext context) {
+ RepeatContext oldSession = getContext();
+ RepeatSynchronizationManager.contextHolder.set(context);
+ return oldSession;
+ }
+
+ /**
+ * 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 RepeatContext clear() {
+ RepeatContext context = getContext();
+ RepeatSynchronizationManager.contextHolder.set(null);
+ return context;
+ }
+
+ /**
+ * Set current session and all ancestors (via parent) to complete.,
+ */
+ public static void setAncestorsCompleteOnly() {
+ RepeatContext context = getContext();
+ while (context != null) {
+ context.setCompleteOnly();
+ context = context.getParent();
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/RepeatTemplate.java b/src/main/java/org/springframework/repeat/support/RepeatTemplate.java
new file mode 100644
index 0000000..05a8d08
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/RepeatTemplate.java
@@ -0,0 +1,476 @@
+/*
+ * 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.repeat.support;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.repeat.CompletionPolicy;
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatException;
+import org.springframework.repeat.RepeatListener;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.exception.DefaultExceptionHandler;
+import org.springframework.repeat.exception.ExceptionHandler;
+import org.springframework.repeat.policy.DefaultResultCompletionPolicy;
+import org.springframework.util.Assert;
+
+/**
+ * Simple implementation and base class for batch templates implementing
+ * {@link RepeatOperations}. Provides a framework including interceptors and
+ * policies. Subclasses just need to provide a method that gets the next result
+ * and one that waits for all the results to be returned from concurrent
+ * processes or threads.
+ *
+ * N.B. the template accumulates thrown exceptions during the iteration, and
+ * they are all processed together when the main loop ends (i.e. finished
+ * processing the items). Clients that do not want to stop execution when an
+ * exception is thrown can use a specific {@link CompletionPolicy} that does not
+ * finish when exceptions are received. This is not the default behaviour.
+ *
+ * Clients that want to take some business action when an exception is thrown by
+ * the {@link RepeatCallback} can consider using a custom {@link RepeatListener}
+ * instead of trying to customise the {@link CompletionPolicy}. This is
+ * generally a friendlier interface to implement, and the
+ * {@link RepeatListener#after(RepeatContext, RepeatStatus)} method is passed in
+ * the result of the callback, which would be an instance of {@link Throwable}
+ * if the business processing had thrown an exception. If the exception is not
+ * to be propagated to the caller, then a non-default {@link CompletionPolicy}
+ * needs to be provided as well, but that could be off the shelf, with the
+ * business action implemented only in the interceptor.
+ *
+ * @author Dave Syer
+ *
+ */
+public class RepeatTemplate implements RepeatOperations {
+
+ protected Log logger = LogFactory.getLog(getClass());
+
+ private RepeatListener[] listeners = new RepeatListener[] {};
+
+ private CompletionPolicy completionPolicy = new DefaultResultCompletionPolicy();
+
+ private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+
+ /**
+ * Set the listeners for this template, registering them for callbacks at
+ * appropriate times in the iteration.
+ *
+ * @param listeners
+ */
+ public void setListeners(RepeatListener[] listeners) {
+ this.listeners = Arrays.asList(listeners).toArray(new RepeatListener[listeners.length]);
+ }
+
+ /**
+ * Register an additional listener.
+ *
+ * @param listener
+ */
+ public void registerListener(RepeatListener listener) {
+ List list = new ArrayList(Arrays.asList(listeners));
+ list.add(listener);
+ listeners = (RepeatListener[]) list.toArray(new RepeatListener[list.size()]);
+ }
+
+ /**
+ * Setter for exception handler strategy. The exception handler is called at
+ * the end of a batch, after the {@link CompletionPolicy} has determined
+ * that the batch is complete. By default all exceptions are re-thrown.
+ *
+ * @see ExceptionHandler
+ * @see DefaultExceptionHandler
+ * @see #setCompletionPolicy(CompletionPolicy)
+ *
+ * @param exceptionHandler the {@link ExceptionHandler} to use.
+ */
+ public void setExceptionHandler(ExceptionHandler exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ /**
+ * Setter for policy to decide when the batch is complete. The default is to
+ * complete normally when the callback returns a {@link RepeatStatus} which
+ * is not marked as continuable, and abnormally when the callback throws an
+ * exception (but the decision to re-throw the exception is deferred to the
+ * {@link ExceptionHandler}).
+ *
+ * @see #setExceptionHandler(ExceptionHandler)
+ *
+ * @param terminationPolicy a TerminationPolicy.
+ * @throws IllegalArgumentException if the argument is null
+ */
+ public void setCompletionPolicy(CompletionPolicy terminationPolicy) {
+ Assert.notNull(terminationPolicy);
+ this.completionPolicy = terminationPolicy;
+ }
+
+ /**
+ * Execute the batch callback until the completion policy decides that we
+ * are finished. Wait for the whole batch to finish before returning even if
+ * the task executor is asynchronous.
+ *
+ * @see org.springframework.repeat.RepeatOperations#iterate(org.springframework.repeat.RepeatCallback)
+ */
+ public RepeatStatus iterate(RepeatCallback callback) {
+
+ RepeatContext outer = RepeatSynchronizationManager.getContext();
+
+ RepeatStatus result = RepeatStatus.CONTINUABLE;
+ try {
+ // This works with an asynchronous TaskExecutor: the
+ // interceptors have to wait for the child processes.
+ result = executeInternal(callback);
+ }
+ finally {
+ RepeatSynchronizationManager.clear();
+ if (outer != null) {
+ RepeatSynchronizationManager.register(outer);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Internal convenience method to loop over interceptors and batch
+ * callbacks.
+ *
+ * @param callback the callback to process each element of the loop.
+ *
+ * @return the aggregate of {@link ContinuationPolicy#canContinue(Object)}
+ * for all the results from the callback.
+ *
+ */
+ private RepeatStatus executeInternal(final RepeatCallback callback) {
+
+ // Reset the termination policy if there is one...
+ RepeatContext context = start();
+
+ // Make sure if we are already marked complete before we start then no
+ // processing takes place.
+ boolean running = !isMarkedComplete(context);
+
+ for (int i = 0; i < listeners.length; i++) {
+ RepeatListener interceptor = listeners[i];
+ interceptor.open(context);
+ running = running && !isMarkedComplete(context);
+ if (!running)
+ break;
+ }
+
+ // Return value, default is to allow continued processing.
+ RepeatStatus result = RepeatStatus.CONTINUABLE;
+
+ RepeatInternalState state = createInternalState(context);
+ // This is the list of exceptions thrown by all active callbacks
+ Collection throwables = state.getThrowables();
+ // Keep a separate list of exceptions we handled that need to be
+ // rethrown
+ Collection deferred = new ArrayList();
+
+ try {
+
+ while (running) {
+
+ /*
+ * Run the before interceptors here, not in the task executor so
+ * that they all happen in the same thread - it's easier for
+ * tracking batch status, amongst other things.
+ */
+ for (int i = 0; i < listeners.length; i++) {
+ RepeatListener interceptor = listeners[i];
+ interceptor.before(context);
+ // Allow before interceptors to veto the batch by setting
+ // flag.
+ running = running && !isMarkedComplete(context);
+ }
+
+ // Check that we are still running (should always be true) ...
+ if (running) {
+
+ try {
+
+ result = getNextResult(context, callback, state);
+ executeAfterInterceptors(context, result);
+
+ }
+ catch (Throwable throwable) {
+ doHandle(throwable, context, deferred);
+ }
+
+ // N.B. the order may be important here:
+ if (isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty()) {
+ running = false;
+ }
+
+ }
+
+ }
+
+ result = result.and(waitForResults(state));
+ for (Throwable throwable : throwables) {
+ doHandle(throwable, context, deferred);
+ }
+
+ // Explicitly drop any references to internal state...
+ state = null;
+
+ }
+ /*
+ * No need for explicit catch here - if the business processing threw an
+ * exception it was already handled by the helper methods. An exception
+ * here is necessarily fatal.
+ */
+ finally {
+
+ try {
+
+ if (!deferred.isEmpty()) {
+ Throwable throwable = (Throwable) deferred.iterator().next();
+ logger.debug("Handling fatal exception explicitly (rethrowing first of " + deferred.size() + "): "
+ + throwable.getClass().getName() + ": " + throwable.getMessage());
+ rethrow(throwable);
+ }
+
+ }
+ finally {
+
+ try {
+ for (int i = listeners.length; i-- > 0;) {
+ RepeatListener interceptor = listeners[i];
+ interceptor.close(context);
+ }
+ }
+ finally {
+ context.close();
+ }
+
+ }
+
+ }
+
+ return result;
+
+ }
+
+ private void doHandle(Throwable throwable, RepeatContext context, Collection deferred) {
+ // An exception alone is not sufficient grounds for not
+ // continuing
+ Throwable unwrappedThrowable = unwrapIfRethrown(throwable);
+ try {
+
+ for (int i = listeners.length; i-- > 0;) {
+ RepeatListener interceptor = listeners[i];
+ // This is not an error - only log at debug
+ // level.
+ logger.debug("Exception intercepted (" + (i + 1) + " of " + listeners.length + ")", unwrappedThrowable);
+ interceptor.onError(context, unwrappedThrowable);
+ }
+
+ logger.debug("Handling exception: " + throwable.getClass().getName() + ", caused by: "
+ + unwrappedThrowable.getClass().getName() + ": " + unwrappedThrowable.getMessage());
+ exceptionHandler.handleException(context, unwrappedThrowable);
+
+ }
+ catch (Throwable handled) {
+ deferred.add(handled);
+ }
+ }
+
+ /**
+ * Re-throws the original throwable if it is unchecked, wraps checked
+ * exceptions into {@link RepeatException}.
+ */
+ private static void rethrow(Throwable throwable) throws RuntimeException {
+ if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ else if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+ else {
+ throw new RepeatException("Exception in batch process", throwable);
+ }
+ }
+
+ /**
+ * Unwraps the throwable if it has been wrapped by
+ * {@link #rethrow(Throwable)}.
+ */
+ private static Throwable unwrapIfRethrown(Throwable throwable) {
+ if (throwable instanceof RepeatException) {
+ return throwable.getCause();
+ }
+ else {
+ return throwable;
+ }
+ }
+
+ /**
+ * Create an internal state object that is used to store data needed
+ * internally in the scope of an iteration. Used by subclasses to manage the
+ * queueing and retrieval of asynchronous results. The default just provides
+ * an accumulation of Throwable instances for processing at the end of the
+ * batch.
+ *
+ * @param context the current {@link RepeatContext}
+ * @return a {@link RepeatInternalState} instance.
+ *
+ * @see RepeatTemplate#waitForResults(RepeatInternalState)
+ */
+ protected RepeatInternalState createInternalState(RepeatContext context) {
+ return new RepeatInternalStateSupport();
+ }
+
+ /**
+ * Get the next completed result, possibly executing several callbacks until
+ * one finally finishes. Normally a subclass would have to override both
+ * this method and {@link #createInternalState(RepeatContext)} because the
+ * implementation of this method would rely on the details of the internal
+ * state.
+ *
+ * @param context current BatchContext.
+ * @param callback the callback to execute.
+ * @param state maintained by the implementation.
+ * @return a finished result.
+ *
+ * @see #isComplete(RepeatContext)
+ * @see #createInternalState(RepeatContext)
+ */
+ protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
+ throws Throwable {
+ update(context);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Repeat operation about to start at count=" + context.getStartedCount());
+ }
+ return callback.doInIteration(context);
+
+ }
+
+ /**
+ * If necessary, wait for results to come back from remote or concurrent
+ * processes. By default does nothing and returns true.
+ *
+ * @param state the internal state.
+ * @return true if {@link #canContinue(RepeatStatus)} is true for all
+ * results retrieved.
+ */
+ protected boolean waitForResults(RepeatInternalState state) {
+ // no-op by default
+ return true;
+ }
+
+ /**
+ * Check return value from batch operation.
+ *
+ * @param value the last callback result.
+ * @return true if the value is {@link RepeatStatus#CONTINUABLE}.
+ */
+ protected final boolean canContinue(RepeatStatus value) {
+ return ((RepeatStatus) value).isContinuable();
+ }
+
+ private boolean isMarkedComplete(RepeatContext context) {
+ boolean complete = context.isCompleteOnly();
+ if (context.getParent() != null) {
+ complete = complete || isMarkedComplete(context.getParent());
+ }
+ if (complete) {
+ logger.debug("Repeat is complete according to context alone.");
+ }
+ return complete;
+
+ }
+
+ /**
+ * Convenience method to execute after interceptors on a callback result.
+ *
+ * @param context the current batch context.
+ * @param value the result of the callback to process.
+ */
+ protected void executeAfterInterceptors(final RepeatContext context, RepeatStatus value) {
+
+ // Don't re-throw exceptions here: let the exception handler deal with
+ // that...
+
+ if (value != null && value.isContinuable()) {
+ for (int i = listeners.length; i-- > 0;) {
+ RepeatListener interceptor = listeners[i];
+ interceptor.after(context, value);
+ }
+
+ }
+
+ }
+
+ /**
+ * Delegate to the {@link CompletionPolicy}.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext,
+ * RepeatStatus)
+ */
+ protected boolean isComplete(RepeatContext context, RepeatStatus result) {
+ boolean complete = completionPolicy.isComplete(context, result);
+ if (complete) {
+ logger.debug("Repeat is complete according to policy and result value.");
+ }
+ return complete;
+ }
+
+ /**
+ * Delegate to {@link CompletionPolicy}.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#isComplete(RepeatContext)
+ */
+ protected boolean isComplete(RepeatContext context) {
+ boolean complete = completionPolicy.isComplete(context);
+ if (complete) {
+ logger.debug("Repeat is complete according to policy alone not including result.");
+ }
+ return complete;
+ }
+
+ /**
+ * Delegate to the {@link CompletionPolicy}.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#start(RepeatContext)
+ */
+ protected RepeatContext start() {
+ RepeatContext parent = RepeatSynchronizationManager.getContext();
+ RepeatContext context = completionPolicy.start(parent);
+ RepeatSynchronizationManager.register(context);
+ logger.debug("Starting repeat context.");
+ return context;
+ }
+
+ /**
+ * Delegate to the {@link CompletionPolicy}.
+ *
+ * @see org.springframework.repeat.CompletionPolicy#update(RepeatContext)
+ */
+ protected void update(RepeatContext context) {
+ completionPolicy.update(context);
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/ResultHolder.java b/src/main/java/org/springframework/repeat/support/ResultHolder.java
new file mode 100644
index 0000000..a2d73be
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/ResultHolder.java
@@ -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.repeat.support;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * Interface for result holder.
+ *
+ * @author Dave Syer
+ */
+interface ResultHolder {
+ /**
+ * Get the result for client from this holder. Does not block if none is
+ * available yet.
+ *
+ * @return the result, or null if there is none.
+ * @throws IllegalStateException
+ */
+ RepeatStatus getResult();
+
+ /**
+ * Get the error for client from this holder if any. Does not block if
+ * none is available yet.
+ *
+ * @return the error, or null if there is none.
+ * @throws IllegalStateException
+ */
+ Throwable getError();
+
+ /**
+ * Get the context in which the result evaluation is executing.
+ *
+ * @return the context of the result evaluation.
+ */
+ RepeatContext getContext();
+}
\ No newline at end of file
diff --git a/src/main/java/org/springframework/repeat/support/ResultHolderResultQueue.java b/src/main/java/org/springframework/repeat/support/ResultHolderResultQueue.java
new file mode 100644
index 0000000..3e16597
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/ResultHolderResultQueue.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2002-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.repeat.support;
+
+import java.util.Comparator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.Semaphore;
+
+import org.springframework.repeat.RepeatStatus;
+
+/**
+ * An implementation of the {@link ResultQueue} that throttles the number of
+ * expected results, limiting it to a maximum at any given time.
+ *
+ * @author Dave Syer
+ */
+public class ResultHolderResultQueue implements ResultQueue {
+
+ // Accumulation of result objects as they finish.
+ private final BlockingQueue results;
+
+ // Accumulation of dummy objects flagging expected results in the future.
+ private final Semaphore waits;
+
+ private final Object lock = new Object();
+
+ private volatile int count = 0;
+
+ /**
+ * @param throttleLimit the maximum number of results that can be expected
+ * at any given time.
+ */
+ public ResultHolderResultQueue(int throttleLimit) {
+ results = new PriorityBlockingQueue(throttleLimit, new ResultHolderComparator());
+ waits = new Semaphore(throttleLimit);
+ }
+
+ public boolean isEmpty() {
+ return results.isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.support.ResultQueue#isExpecting()
+ */
+ public boolean isExpecting() {
+ // Base the decision about whether we expect more results on a
+ // counter of the number of expected results actually collected.
+ // Do not synchronize! Otherwise put and expect can deadlock.
+ return count > 0;
+ }
+
+ /**
+ * Tell the queue to expect one more result. Blocks until a new result is
+ * available if already expecting too many (as determined by the throttle
+ * limit).
+ *
+ * @see ResultQueue#expect()
+ */
+ public void expect() throws InterruptedException {
+ waits.acquire();
+ // Don't acquire the lock in a synchronized block - might deadlock
+ synchronized (lock) {
+ count++;
+ }
+ }
+
+ public void put(ResultHolder holder) throws IllegalArgumentException {
+ if (!isExpecting()) {
+ throw new IllegalArgumentException("Not expecting a result. Call expect() before put().");
+ }
+ results.add(holder);
+ // Take from the waits queue now to allow another result to
+ // accumulate. But don't decrement the counter.
+ waits.release();
+ synchronized (lock) {
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * Get the next result as soon as it becomes available.
+ *
+ * Release result immediately if:
+ *
+ *
There is a result that is continuable.
+ *
+ * Otherwise block if either:
+ *
+ *
There is no result (as per contract of {@link ResultQueue}).
+ *
The number of results is less than the number expected.
+ *
+ * Error if either:
+ *
+ *
Not expecting.
+ *
Interrupted.
+ *
+ *
+ * @see ResultQueue#take()
+ */
+ public ResultHolder take() throws NoSuchElementException, InterruptedException {
+ if (!isExpecting()) {
+ throw new NoSuchElementException("Not expecting a result. Call expect() before take().");
+ }
+ ResultHolder value;
+ synchronized (lock) {
+ value = results.take();
+ if (isContinuable(value)) {
+ // Decrement the counter only when the result is collected.
+ count--;
+ return value;
+ }
+ }
+ results.put(value);
+ synchronized (lock) {
+ while (count > results.size()) {
+ lock.wait();
+ }
+ value = results.take();
+ count--;
+ }
+ return value;
+ }
+
+ private boolean isContinuable(ResultHolder value) {
+ return value.getResult() != null && value.getResult().isContinuable();
+ }
+
+ /**
+ * Compares ResultHolders so that one that is continuable ranks lowest.
+ *
+ * @author Dave Syer
+ *
+ */
+ private static class ResultHolderComparator implements Comparator {
+ public int compare(ResultHolder h1, ResultHolder h2) {
+ RepeatStatus result1 = h1.getResult();
+ RepeatStatus result2 = h2.getResult();
+ if (result1 == null && result2 == null) {
+ return 0;
+ }
+ if (result1 == null) {
+ return -1;
+ }
+ else if (result2 == null) {
+ return 1;
+ }
+ if ((result1.isContinuable() && result2.isContinuable())
+ || (!result1.isContinuable() && !result2.isContinuable())) {
+ return 0;
+ }
+ if (result1.isContinuable()) {
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/ResultQueue.java b/src/main/java/org/springframework/repeat/support/ResultQueue.java
new file mode 100644
index 0000000..55a85d4
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/ResultQueue.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-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.repeat.support;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+
+import org.springframework.core.task.TaskExecutor;
+
+/**
+ * Abstraction for queue of {@link ResultHolder} objects. Acts a bit likeT a
+ * {@link BlockingQueue} with the ability to count the number of items it
+ * expects to ever hold. When clients schedule an item to be added they call
+ * {@link #expect()}, and then collect the result later with {@link #take()}.
+ * Result providers in another thread call {@link #put(Object)} to notify the
+ * expecting client of a new result.
+ *
+ * @author Dave Syer
+ * @author Ben Hale
+ */
+interface ResultQueue {
+
+ /**
+ * In a master-slave pattern, the master calls this method paired with
+ * {@link #take()} to manage the flow of items. Normally a task is submitted
+ * for processing in another thread, at which point the master uses this
+ * method to keep track of the number of expected results. It has the
+ * personality of an counter increment, rather than a work queue, which is
+ * usually managed elsewhere, e.g. by a {@link TaskExecutor}.
+ * Implementations may choose to block here, if they need to limit the
+ * number or rate of tasks being submitted.
+ *
+ * @throws InterruptedException if the call blocks and is then interrupted.
+ */
+ void expect() throws InterruptedException;
+
+ /**
+ * Once it is expecting a result, clients call this method to satisfy the
+ * expectation. In a master-worker pattern, the workers call this method to
+ * deposit the result of a finished task on the queue for collection.
+ *
+ * @param result the result for later collection.
+ *
+ * @throws IllegalArgumentException if the queue is not expecting a new
+ * result
+ */
+ void put(T result) throws IllegalArgumentException;
+
+ /**
+ * Gets the next available result, blocking if there are none yet available.
+ *
+ * @return a result previously deposited
+ *
+ * @throws NoSuchElementException if there is no result expected
+ * @throws InterruptedException if the operation is interrupted while
+ * waiting
+ */
+ T take() throws NoSuchElementException, InterruptedException;
+
+ /**
+ * Used by master thread to verify that there are results available from
+ * {@link #take()} without possibly having to block and wait.
+ *
+ * @return true if there are no results available
+ */
+ boolean isEmpty();
+
+ /**
+ * Check if any results are expected. Usually used by master thread to drain
+ * queue when it is finished.
+ *
+ * @return true if more results are expected, but possibly not yet
+ * available.
+ */
+ public boolean isExpecting();
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/TaskExecutorRepeatTemplate.java b/src/main/java/org/springframework/repeat/support/TaskExecutorRepeatTemplate.java
new file mode 100644
index 0000000..dc57b2b
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/TaskExecutorRepeatTemplate.java
@@ -0,0 +1,324 @@
+/*
+ * 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.repeat.support;
+
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatException;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.util.Assert;
+
+/**
+ * Provides {@link RepeatOperations} support including interceptors that can be
+ * used to modify or monitor the behaviour at run time.
+ *
+ * This implementation is sufficient to be used to configure transactional
+ * behaviour for each item by making the {@link RepeatCallback} transactional,
+ * or for the whole batch by making the execute method transactional (but only
+ * then if the task executor is synchronous).
+ *
+ * This class is thread safe if its collaborators are thread safe (interceptors,
+ * terminationPolicy, callback). Normally this will be the case, but clients
+ * need to be aware that if the task executor is asynchronous, then the other
+ * collaborators should be also. In particular the {@link RepeatCallback} that
+ * is wrapped in the execute method must be thread safe - often it is based on
+ * some form of data source, which itself should be both thread safe and
+ * transactional (multiple threads could be accessing it at any given time, and
+ * each thread would have its own transaction).
+ *
+ * @author Dave Syer
+ *
+ */
+public class TaskExecutorRepeatTemplate extends RepeatTemplate {
+
+ /**
+ * Default limit for maximum number of concurrent unfinished results allowed
+ * by the template.
+ * {@link #getNextResult(RepeatContext, RepeatCallback, RepeatInternalState)}
+ * .
+ */
+ public static final int DEFAULT_THROTTLE_LIMIT = 4;
+
+ private int throttleLimit = DEFAULT_THROTTLE_LIMIT;
+
+ private TaskExecutor taskExecutor = new SyncTaskExecutor();
+
+ /**
+ * Public setter for the throttle limit. The throttle limit is the largest
+ * number of concurrent tasks that can be executing at one time - if a new
+ * task arrives and the throttle limit is breached we wait for one of the
+ * executing tasks to finish before submitting the new one to the
+ * {@link TaskExecutor}. Default value is {@link #DEFAULT_THROTTLE_LIMIT}.
+ * N.B. when used with a thread pooled {@link TaskExecutor} the thread pool
+ * might prevent the throttle limit actually being reached (so make the core
+ * pool size larger than the throttle limit if possible).
+ *
+ * @param throttleLimit the throttleLimit to set.
+ */
+ public void setThrottleLimit(int throttleLimit) {
+ this.throttleLimit = throttleLimit;
+ }
+
+ /**
+ * Setter for task executor to be used to run the individual item callbacks.
+ *
+ * @param taskExecutor a TaskExecutor
+ * @throws IllegalArgumentException if the argument is null
+ */
+ public void setTaskExecutor(TaskExecutor taskExecutor) {
+ Assert.notNull(taskExecutor);
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * Use the {@link #setTaskExecutor(TaskExecutor)} to generate a result. The
+ * internal state in this case is a queue of unfinished result holders of
+ * type {@link ResultHolder}. The holder with the return value should not be
+ * on the queue when this method exits. The queue is scoped in the calling
+ * method so there is no need to synchronize access.
+ *
+ */
+ protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
+ throws Throwable {
+
+ ExecutingRunnable runnable = null;
+
+ ResultQueue queue = ((ResultQueueInternalState) state).getResultQueue();
+
+ do {
+
+ /*
+ * Wrap the callback in a runnable that will add its result to the
+ * queue when it is ready.
+ */
+ runnable = new ExecutingRunnable(callback, context, queue);
+
+ /**
+ * Tell the runnable that it can expect a result. This could have
+ * been in-lined with the constructor, but it might block, so it's
+ * better to do it here, since we have the option (it's a private
+ * class).
+ */
+ runnable.expect();
+
+ /*
+ * Start the task possibly concurrently / in the future.
+ */
+ taskExecutor.execute(runnable);
+
+ /*
+ * Allow termination policy to update its state. This must happen
+ * immediately before or after the call to the task executor.
+ */
+ update(context);
+
+ /*
+ * Keep going until we get a result that is finished, or early
+ * termination...
+ */
+ } while (queue.isEmpty() && !isComplete(context));
+
+ /*
+ * N.B. If the queue is empty then take() blocks until a result appears,
+ * and there must be at least one because we just submitted one to the
+ * task executor.
+ */
+ ResultHolder result = queue.take();
+ if (result.getError() != null) {
+ throw result.getError();
+ }
+ return result.getResult();
+ }
+
+ /**
+ * Wait for all the results to appear on the queue and execute the after
+ * interceptors for each one.
+ *
+ * @see org.springframework.repeat.support.RepeatTemplate#waitForResults(org.springframework.repeat.support.RepeatInternalState)
+ */
+ protected boolean waitForResults(RepeatInternalState state) {
+
+ ResultQueue queue = ((ResultQueueInternalState) state).getResultQueue();
+
+ boolean result = true;
+
+ while (queue.isExpecting()) {
+
+ /*
+ * Careful that no runnables that are not going to finish ever get
+ * onto the queue, else this may block forever.
+ */
+ ResultHolder future;
+ try {
+ future = (ResultHolder) queue.take();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RepeatException("InterruptedException while waiting for result.");
+ }
+
+ if (future.getError() != null) {
+ state.getThrowables().add(future.getError());
+ }
+ else {
+ RepeatStatus status = future.getResult();
+ result = result && canContinue(status);
+ executeAfterInterceptors(future.getContext(), status);
+ }
+
+ }
+
+ Assert.state(queue.isEmpty(), "Future results queue should be empty at end of batch.");
+
+ return result;
+ }
+
+ protected RepeatInternalState createInternalState(RepeatContext context) {
+ // Queue of pending results:
+ return new ResultQueueInternalState(throttleLimit);
+ }
+
+ /**
+ * A runnable that puts its result on a queue when it is done.
+ *
+ * @author Dave Syer
+ *
+ */
+ private class ExecutingRunnable implements Runnable, ResultHolder {
+
+ private final RepeatCallback callback;
+
+ private final RepeatContext context;
+
+ private final ResultQueue queue;
+
+ private volatile RepeatStatus result;
+
+ private volatile Throwable error;
+
+ public ExecutingRunnable(RepeatCallback callback, RepeatContext context, ResultQueue queue) {
+
+ super();
+
+ this.callback = callback;
+ this.context = context;
+ this.queue = queue;
+
+ }
+
+ /**
+ * Tell the queue to expect a result.
+ */
+ public void expect() {
+ try {
+ queue.expect();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RepeatException("InterruptedException waiting for to acquire lock on input.");
+ }
+ }
+
+ /**
+ * Execute the batch callback, and store the result, or any exception
+ * that is thrown for retrieval later by caller.
+ *
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+ boolean clearContext = false;
+ try {
+ if (RepeatSynchronizationManager.getContext() == null) {
+ clearContext = true;
+ RepeatSynchronizationManager.register(context);
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Repeat operation about to start at count=" + context.getStartedCount());
+ }
+
+ result = callback.doInIteration(context);
+
+ }
+ catch (Exception e) {
+ error = e;
+ }
+ finally {
+
+ if (clearContext) {
+ RepeatSynchronizationManager.clear();
+ }
+
+ queue.put(this);
+
+ }
+ }
+
+ /**
+ * Get the result - never blocks because the queue manages waiting for
+ * the task to finish.
+ */
+ public RepeatStatus getResult() {
+ return result;
+ }
+
+ /**
+ * Get the error - never blocks because the queue manages waiting for
+ * the task to finish.
+ */
+ public Throwable getError() {
+ return error;
+ }
+
+ /**
+ * Getter for the context.
+ */
+ public RepeatContext getContext() {
+ return this.context;
+ }
+
+ }
+
+ /**
+ * @author Dave Syer
+ *
+ */
+ private static class ResultQueueInternalState extends RepeatInternalStateSupport {
+
+ private final ResultQueue results;
+
+ /**
+ * @param throttleLimit the throttle limit for the result queue
+ */
+ public ResultQueueInternalState(int throttleLimit) {
+ super();
+ this.results = new ResultHolderResultQueue(throttleLimit);
+ }
+
+ /**
+ * @return the result queue
+ */
+ public ResultQueue getResultQueue() {
+ return results;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/ThrottleLimitResultQueue.java b/src/main/java/org/springframework/repeat/support/ThrottleLimitResultQueue.java
new file mode 100644
index 0000000..dc96536
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/ThrottleLimitResultQueue.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-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.repeat.support;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+
+/**
+ * An implementation of the {@link ResultQueue} that throttles the number of
+ * expected results, limiting it to a maximum at any given time.
+ *
+ * @author Dave Syer
+ */
+public class ThrottleLimitResultQueue implements ResultQueue {
+
+ // Accumulation of result objects as they finish.
+ private final BlockingQueue results;
+
+ // Accumulation of dummy objects flagging expected results in the future.
+ private final Semaphore waits;
+
+ private final Object lock = new Object();
+
+ private volatile int count = 0;
+
+ /**
+ * @param throttleLimit the maximum number of results that can be expected
+ * at any given time.
+ */
+ public ThrottleLimitResultQueue(int throttleLimit) {
+ results = new LinkedBlockingQueue();
+ waits = new Semaphore(throttleLimit);
+ }
+
+ public boolean isEmpty() {
+ return results.isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.repeat.support.ResultQueue#isExpecting()
+ */
+ public boolean isExpecting() {
+ // Base the decision about whether we expect more results on a
+ // counter of the number of expected results actually collected.
+ // Do not synchronize! Otherwise put and expect can deadlock.
+ return count > 0;
+ }
+
+ /**
+ * Tell the queue to expect one more result. Blocks until a new result is
+ * available if already expecting too many (as determined by the throttle
+ * limit).
+ *
+ * @see ResultQueue#expect()
+ */
+ public void expect() throws InterruptedException {
+ synchronized (lock) {
+ waits.acquire();
+ count++;
+ }
+ }
+
+ public void put(T holder) throws IllegalArgumentException {
+ if (!isExpecting()) {
+ throw new IllegalArgumentException("Not expecting a result. Call expect() before put().");
+ }
+ // There should be no need to block here, or to use offer()
+ results.add(holder);
+ // Take from the waits queue now to allow another result to
+ // accumulate. But don't decrement the counter.
+ waits.release();
+ }
+
+ public T take() throws NoSuchElementException, InterruptedException {
+ if (!isExpecting()) {
+ throw new NoSuchElementException("Not expecting a result. Call expect() before take().");
+ }
+ T value;
+ synchronized (lock) {
+ value = results.take();
+ // Decrement the counter only when the result is collected.
+ count--;
+ }
+ return value;
+ }
+
+}
diff --git a/src/main/java/org/springframework/repeat/support/package.html b/src/main/java/org/springframework/repeat/support/package.html
new file mode 100644
index 0000000..6e3f0ee
--- /dev/null
+++ b/src/main/java/org/springframework/repeat/support/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of repeat support concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/ExhaustedRetryException.java b/src/main/java/org/springframework/retry/ExhaustedRetryException.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/ExhaustedRetryException.java
rename to src/main/java/org/springframework/retry/ExhaustedRetryException.java
index 2a630fd..dc2f507 100644
--- a/src/main/java/org/springframework/commons/retry/ExhaustedRetryException.java
+++ b/src/main/java/org/springframework/retry/ExhaustedRetryException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
public class ExhaustedRetryException extends RetryException {
diff --git a/src/main/java/org/springframework/commons/retry/RecoveryCallback.java b/src/main/java/org/springframework/retry/RecoveryCallback.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/RecoveryCallback.java
rename to src/main/java/org/springframework/retry/RecoveryCallback.java
index d158dc8..ee5906a 100644
--- a/src/main/java/org/springframework/commons/retry/RecoveryCallback.java
+++ b/src/main/java/org/springframework/retry/RecoveryCallback.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
* Callback for stateful retry after all tries are exhausted.
diff --git a/src/main/java/org/springframework/commons/retry/RetryCallback.java b/src/main/java/org/springframework/retry/RetryCallback.java
similarity index 96%
rename from src/main/java/org/springframework/commons/retry/RetryCallback.java
rename to src/main/java/org/springframework/retry/RetryCallback.java
index 167fe68..ed9e825 100644
--- a/src/main/java/org/springframework/commons/retry/RetryCallback.java
+++ b/src/main/java/org/springframework/retry/RetryCallback.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
* Callback interface for an operation that can be retried using a
diff --git a/src/main/java/org/springframework/commons/retry/RetryContext.java b/src/main/java/org/springframework/retry/RetryContext.java
similarity index 97%
rename from src/main/java/org/springframework/commons/retry/RetryContext.java
rename to src/main/java/org/springframework/retry/RetryContext.java
index d785066..a2906c6 100644
--- a/src/main/java/org/springframework/commons/retry/RetryContext.java
+++ b/src/main/java/org/springframework/retry/RetryContext.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
import org.springframework.core.AttributeAccessor;
diff --git a/src/main/java/org/springframework/commons/retry/RetryException.java b/src/main/java/org/springframework/retry/RetryException.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/RetryException.java
rename to src/main/java/org/springframework/retry/RetryException.java
index 92900be..2ff3a3f 100644
--- a/src/main/java/org/springframework/commons/retry/RetryException.java
+++ b/src/main/java/org/springframework/retry/RetryException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
import org.springframework.core.NestedRuntimeException;
diff --git a/src/main/java/org/springframework/commons/retry/RetryListener.java b/src/main/java/org/springframework/retry/RetryListener.java
similarity index 98%
rename from src/main/java/org/springframework/commons/retry/RetryListener.java
rename to src/main/java/org/springframework/retry/RetryListener.java
index d8499c3..1541fcf 100644
--- a/src/main/java/org/springframework/commons/retry/RetryListener.java
+++ b/src/main/java/org/springframework/retry/RetryListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
diff --git a/src/main/java/org/springframework/commons/retry/RetryOperations.java b/src/main/java/org/springframework/retry/RetryOperations.java
similarity index 96%
rename from src/main/java/org/springframework/commons/retry/RetryOperations.java
rename to src/main/java/org/springframework/retry/RetryOperations.java
index f77e825..55fc0b3 100644
--- a/src/main/java/org/springframework/commons/retry/RetryOperations.java
+++ b/src/main/java/org/springframework/retry/RetryOperations.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
-import org.springframework.commons.retry.support.DefaultRetryState;
+import org.springframework.retry.support.DefaultRetryState;
/**
* Defines the basic set of operations implemented by {@link RetryOperations} to
diff --git a/src/main/java/org/springframework/commons/retry/RetryPolicy.java b/src/main/java/org/springframework/retry/RetryPolicy.java
similarity index 97%
rename from src/main/java/org/springframework/commons/retry/RetryPolicy.java
rename to src/main/java/org/springframework/retry/RetryPolicy.java
index 273f870..0f4a182 100644
--- a/src/main/java/org/springframework/commons/retry/RetryPolicy.java
+++ b/src/main/java/org/springframework/retry/RetryPolicy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
* A {@link RetryPolicy} is responsible for allocating and managing resources
diff --git a/src/main/java/org/springframework/commons/retry/RetryState.java b/src/main/java/org/springframework/retry/RetryState.java
similarity index 98%
rename from src/main/java/org/springframework/commons/retry/RetryState.java
rename to src/main/java/org/springframework/retry/RetryState.java
index 6dd1331..8089dc9 100644
--- a/src/main/java/org/springframework/commons/retry/RetryState.java
+++ b/src/main/java/org/springframework/retry/RetryState.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
* Stateful retry is characterised by having to recognise the items that are
diff --git a/src/main/java/org/springframework/commons/retry/RetryStatistics.java b/src/main/java/org/springframework/retry/RetryStatistics.java
similarity index 97%
rename from src/main/java/org/springframework/commons/retry/RetryStatistics.java
rename to src/main/java/org/springframework/retry/RetryStatistics.java
index 098220d..b7b0409 100644
--- a/src/main/java/org/springframework/commons/retry/RetryStatistics.java
+++ b/src/main/java/org/springframework/retry/RetryStatistics.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
/**
* Interface for statistics reporting of retry attempts. Counts the number of
diff --git a/src/main/java/org/springframework/commons/retry/TerminatedRetryException.java b/src/main/java/org/springframework/retry/TerminatedRetryException.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/TerminatedRetryException.java
rename to src/main/java/org/springframework/retry/TerminatedRetryException.java
index 6642bb7..4e8680b 100644
--- a/src/main/java/org/springframework/commons/retry/TerminatedRetryException.java
+++ b/src/main/java/org/springframework/retry/TerminatedRetryException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry;
+package org.springframework.retry;
public class TerminatedRetryException extends RetryException {
diff --git a/src/main/java/org/springframework/commons/retry/backoff/BackOffContext.java b/src/main/java/org/springframework/retry/backoff/BackOffContext.java
similarity index 93%
rename from src/main/java/org/springframework/commons/retry/backoff/BackOffContext.java
rename to src/main/java/org/springframework/retry/backoff/BackOffContext.java
index 6f445a2..8d7249f 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/BackOffContext.java
+++ b/src/main/java/org/springframework/retry/backoff/BackOffContext.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
/**
* @author Rob Harrop
diff --git a/src/main/java/org/springframework/commons/retry/backoff/BackOffInterruptedException.java b/src/main/java/org/springframework/retry/backoff/BackOffInterruptedException.java
similarity index 91%
rename from src/main/java/org/springframework/commons/retry/backoff/BackOffInterruptedException.java
rename to src/main/java/org/springframework/retry/backoff/BackOffInterruptedException.java
index 9a84e1b..acc639b 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/BackOffInterruptedException.java
+++ b/src/main/java/org/springframework/retry/backoff/BackOffInterruptedException.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
-import org.springframework.commons.retry.RetryException;
+import org.springframework.retry.RetryException;
/**
* Exception class signifiying that an attempt to back off using a
diff --git a/src/main/java/org/springframework/commons/retry/backoff/BackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/BackOffPolicy.java
similarity index 89%
rename from src/main/java/org/springframework/commons/retry/backoff/BackOffPolicy.java
rename to src/main/java/org/springframework/retry/backoff/BackOffPolicy.java
index 14c7857..3b7ce79 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/BackOffPolicy.java
+++ b/src/main/java/org/springframework/retry/backoff/BackOffPolicy.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
-import org.springframework.commons.retry.RetryContext;
+import org.springframework.retry.RetryContext;
/**
* Strategy interface to control back off between attempts in a single
- * {@link org.springframework.commons.retry.support.RetryTemplate retry operation}.
+ * {@link org.springframework.retry.support.RetryTemplate retry operation}.
* Implementations are expected to be thread-safe and should be designed
* for concurrent access. Configuration for each implementation is also expected
* to be thread-safe but need not be suitable for high load concurrent access.
@@ -29,7 +29,7 @@ import org.springframework.commons.retry.RetryContext;
* {@link BackOffContext} that can be used to track state through subsequent
* back off invocations. Each back off process is handled via a call to
* {@link #backOff}. The
- * {@link org.springframework.commons.retry.support.RetryTemplate} will pass in
+ * {@link org.springframework.retry.support.RetryTemplate} will pass in
* the corresponding {@link BackOffContext} object created by the call to
* {@link #start}.
*
diff --git a/src/main/java/org/springframework/commons/retry/backoff/ExponentialBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java
similarity index 98%
rename from src/main/java/org/springframework/commons/retry/backoff/ExponentialBackOffPolicy.java
rename to src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java
index b286e3c..1c85a48 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/ExponentialBackOffPolicy.java
+++ b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
-import org.springframework.commons.retry.RetryContext;
+import org.springframework.retry.RetryContext;
import org.springframework.util.ClassUtils;
/**
diff --git a/src/main/java/org/springframework/commons/retry/backoff/FixedBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java
similarity index 97%
rename from src/main/java/org/springframework/commons/retry/backoff/FixedBackOffPolicy.java
rename to src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java
index ec44217..665af8f 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/FixedBackOffPolicy.java
+++ b/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
/**
diff --git a/src/main/java/org/springframework/commons/retry/backoff/NoBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/NoBackOffPolicy.java
similarity index 94%
rename from src/main/java/org/springframework/commons/retry/backoff/NoBackOffPolicy.java
rename to src/main/java/org/springframework/retry/backoff/NoBackOffPolicy.java
index 66c99d0..231bd55 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/NoBackOffPolicy.java
+++ b/src/main/java/org/springframework/retry/backoff/NoBackOffPolicy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
/**
diff --git a/src/main/java/org/springframework/commons/retry/backoff/ObjectWaitSleeper.java b/src/main/java/org/springframework/retry/backoff/ObjectWaitSleeper.java
similarity index 88%
rename from src/main/java/org/springframework/commons/retry/backoff/ObjectWaitSleeper.java
rename to src/main/java/org/springframework/retry/backoff/ObjectWaitSleeper.java
index 10a88ad..2821c17 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/ObjectWaitSleeper.java
+++ b/src/main/java/org/springframework/retry/backoff/ObjectWaitSleeper.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
/**
* Simple {@link Sleeper} implementation that just waits on a local Object.
@@ -25,7 +25,7 @@ public class ObjectWaitSleeper implements Sleeper {
/*
* (non-Javadoc)
- * @see org.springframework.commons.retry.backoff.Sleeper#sleep(long)
+ * @see org.springframework.batch.retry.backoff.Sleeper#sleep(long)
*/
public void sleep(long backOffPeriod) throws InterruptedException {
Object mutex = new Object();
diff --git a/src/main/java/org/springframework/commons/retry/backoff/Sleeper.java b/src/main/java/org/springframework/retry/backoff/Sleeper.java
similarity index 94%
rename from src/main/java/org/springframework/commons/retry/backoff/Sleeper.java
rename to src/main/java/org/springframework/retry/backoff/Sleeper.java
index 8eccf45..4a313c9 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/Sleeper.java
+++ b/src/main/java/org/springframework/retry/backoff/Sleeper.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
/**
* Strategy interface for backoff policies to delegate the pausing of execution.
diff --git a/src/main/java/org/springframework/commons/retry/backoff/StatelessBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/StatelessBackOffPolicy.java
similarity index 93%
rename from src/main/java/org/springframework/commons/retry/backoff/StatelessBackOffPolicy.java
rename to src/main/java/org/springframework/retry/backoff/StatelessBackOffPolicy.java
index 14ee264..3a16170 100644
--- a/src/main/java/org/springframework/commons/retry/backoff/StatelessBackOffPolicy.java
+++ b/src/main/java/org/springframework/retry/backoff/StatelessBackOffPolicy.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.backoff;
+package org.springframework.retry.backoff;
-import org.springframework.commons.retry.RetryContext;
+import org.springframework.retry.RetryContext;
/**
* Simple base class for {@link BackOffPolicy} implementations that maintain no
diff --git a/src/main/java/org/springframework/retry/backoff/package.html b/src/main/java/org/springframework/retry/backoff/package.html
new file mode 100644
index 0000000..53837ce
--- /dev/null
+++ b/src/main/java/org/springframework/retry/backoff/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry backoff concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/context/RetryContextSupport.java b/src/main/java/org/springframework/retry/context/RetryContextSupport.java
similarity index 93%
rename from src/main/java/org/springframework/commons/retry/context/RetryContextSupport.java
rename to src/main/java/org/springframework/retry/context/RetryContextSupport.java
index 0c5292a..8aa295a 100644
--- a/src/main/java/org/springframework/commons/retry/context/RetryContextSupport.java
+++ b/src/main/java/org/springframework/retry/context/RetryContextSupport.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.context;
+package org.springframework.retry.context;
-import org.springframework.commons.retry.RetryContext;
-import org.springframework.commons.retry.RetryPolicy;
import org.springframework.core.AttributeAccessorSupport;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
public class RetryContextSupport extends AttributeAccessorSupport implements RetryContext {
diff --git a/src/main/java/org/springframework/retry/context/package.html b/src/main/java/org/springframework/retry/context/package.html
new file mode 100644
index 0000000..d277563
--- /dev/null
+++ b/src/main/java/org/springframework/retry/context/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry context concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/interceptor/MethodArgumentsKeyGenerator.java b/src/main/java/org/springframework/retry/interceptor/MethodArgumentsKeyGenerator.java
similarity index 94%
rename from src/main/java/org/springframework/commons/retry/interceptor/MethodArgumentsKeyGenerator.java
rename to src/main/java/org/springframework/retry/interceptor/MethodArgumentsKeyGenerator.java
index fd15b31..80c4e88 100644
--- a/src/main/java/org/springframework/commons/retry/interceptor/MethodArgumentsKeyGenerator.java
+++ b/src/main/java/org/springframework/retry/interceptor/MethodArgumentsKeyGenerator.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.interceptor;
+package org.springframework.retry.interceptor;
/**
* Interface that allows method parameters to be identified and tagged by a
diff --git a/src/main/java/org/springframework/commons/retry/interceptor/MethodInvocationRecoverer.java b/src/main/java/org/springframework/retry/interceptor/MethodInvocationRecoverer.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/interceptor/MethodInvocationRecoverer.java
rename to src/main/java/org/springframework/retry/interceptor/MethodInvocationRecoverer.java
index 376a6b9..e254cdc 100644
--- a/src/main/java/org/springframework/commons/retry/interceptor/MethodInvocationRecoverer.java
+++ b/src/main/java/org/springframework/retry/interceptor/MethodInvocationRecoverer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.interceptor;
+package org.springframework.retry.interceptor;
/**
diff --git a/src/main/java/org/springframework/commons/retry/interceptor/NewMethodArgumentsIdentifier.java b/src/main/java/org/springframework/retry/interceptor/NewMethodArgumentsIdentifier.java
similarity index 95%
rename from src/main/java/org/springframework/commons/retry/interceptor/NewMethodArgumentsIdentifier.java
rename to src/main/java/org/springframework/retry/interceptor/NewMethodArgumentsIdentifier.java
index 92b1f2b..2d5c0c6 100644
--- a/src/main/java/org/springframework/commons/retry/interceptor/NewMethodArgumentsIdentifier.java
+++ b/src/main/java/org/springframework/retry/interceptor/NewMethodArgumentsIdentifier.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.interceptor;
+package org.springframework.retry.interceptor;
/**
* Strategy interface to distinguish new arguments from ones that have been
diff --git a/src/main/java/org/springframework/commons/retry/interceptor/RetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java
similarity index 91%
rename from src/main/java/org/springframework/commons/retry/interceptor/RetryOperationsInterceptor.java
rename to src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java
index 377482f..97ac085 100644
--- a/src/main/java/org/springframework/commons/retry/interceptor/RetryOperationsInterceptor.java
+++ b/src/main/java/org/springframework/retry/interceptor/RetryOperationsInterceptor.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.interceptor;
+package org.springframework.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.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryOperations;
+import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
/**
diff --git a/src/main/java/org/springframework/commons/retry/interceptor/StatefulRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java
similarity index 89%
rename from src/main/java/org/springframework/commons/retry/interceptor/StatefulRetryOperationsInterceptor.java
rename to src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java
index 259fbb6..badf6d3 100644
--- a/src/main/java/org/springframework/commons/retry/interceptor/StatefulRetryOperationsInterceptor.java
+++ b/src/main/java/org/springframework/retry/interceptor/StatefulRetryOperationsInterceptor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.interceptor;
+package org.springframework.retry.interceptor;
import java.util.Arrays;
@@ -22,15 +22,15 @@ 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.retry.ExhaustedRetryException;
+import org.springframework.retry.RecoveryCallback;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryOperations;
+import org.springframework.retry.RetryState;
+import org.springframework.retry.policy.NeverRetryPolicy;
+import org.springframework.retry.support.DefaultRetryState;
+import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
diff --git a/src/main/java/org/springframework/retry/interceptor/package.html b/src/main/java/org/springframework/retry/interceptor/package.html
new file mode 100644
index 0000000..2c89378
--- /dev/null
+++ b/src/main/java/org/springframework/retry/interceptor/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry aop concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/listener/RetryListenerSupport.java b/src/main/java/org/springframework/retry/listener/RetryListenerSupport.java
similarity index 83%
rename from src/main/java/org/springframework/commons/retry/listener/RetryListenerSupport.java
rename to src/main/java/org/springframework/retry/listener/RetryListenerSupport.java
index a4b84f1..fbc542b 100644
--- a/src/main/java/org/springframework/commons/retry/listener/RetryListenerSupport.java
+++ b/src/main/java/org/springframework/retry/listener/RetryListenerSupport.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.listener;
+package org.springframework.retry.listener;
-import org.springframework.commons.retry.RetryCallback;
-import org.springframework.commons.retry.RetryContext;
-import org.springframework.commons.retry.RetryListener;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryListener;
/**
* Empty method implementation of {@link RetryListener}.
diff --git a/src/main/java/org/springframework/retry/listener/package.html b/src/main/java/org/springframework/retry/listener/package.html
new file mode 100644
index 0000000..f75aebf
--- /dev/null
+++ b/src/main/java/org/springframework/retry/listener/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry interceptor concerns.
+
+Infrastructure implementations of retry concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/policy/AlwaysRetryPolicy.java b/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java
similarity index 78%
rename from src/main/java/org/springframework/commons/retry/policy/AlwaysRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java
index 369922d..8871a60 100644
--- a/src/main/java/org/springframework/commons/retry/policy/AlwaysRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/AlwaysRetryPolicy.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
-import org.springframework.commons.retry.RetryContext;
-import org.springframework.commons.retry.RetryPolicy;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
/**
* A {@link RetryPolicy} that always permits a retry. Can also be used as a base
@@ -31,7 +31,7 @@ public class AlwaysRetryPolicy extends NeverRetryPolicy {
/**
* Always returns true.
*
- * @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*/
public boolean canRetry(RetryContext context) {
return true;
diff --git a/src/main/java/org/springframework/commons/retry/policy/CompositeRetryPolicy.java b/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java
similarity index 84%
rename from src/main/java/org/springframework/commons/retry/policy/CompositeRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java
index 117ec71..6b1242f 100644
--- a/src/main/java/org/springframework/commons/retry/policy/CompositeRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/CompositeRetryPolicy.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.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;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.context.RetryContextSupport;
/**
* A {@link RetryPolicy} that composes a list of other policies and delegates
@@ -49,7 +49,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*/
public boolean canRetry(RetryContext context) {
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
@@ -67,7 +67,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
*/
public void close(RetryContext context) {
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
@@ -92,7 +92,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#open(RetryContext)
*/
public RetryContext open(RetryContext parent) {
List list = new ArrayList();
@@ -106,7 +106,7 @@ public class CompositeRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
*/
public void registerThrowable(RetryContext context, Throwable throwable) {
RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
diff --git a/src/main/java/org/springframework/commons/retry/policy/ExceptionClassifierRetryPolicy.java b/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java
similarity index 85%
rename from src/main/java/org/springframework/commons/retry/policy/ExceptionClassifierRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java
index 301ff19..e94e6dd 100644
--- a/src/main/java/org/springframework/commons/retry/policy/ExceptionClassifierRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/ExceptionClassifierRetryPolicy.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.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.classify.Classifier;
+import org.springframework.classify.ClassifierSupport;
+import org.springframework.classify.SubclassClassifier;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.context.RetryContextSupport;
import org.springframework.util.Assert;
/**
@@ -66,7 +66,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
/**
* Delegate to the policy currently activated in the context.
*
- * @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*/
public boolean canRetry(RetryContext context) {
RetryPolicy policy = (RetryPolicy) context;
@@ -76,7 +76,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
/**
* Delegate to the policy currently activated in the context.
*
- * @see org.springframework.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
+ * @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
*/
public void close(RetryContext context) {
RetryPolicy policy = (RetryPolicy) context;
@@ -87,7 +87,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#open(RetryContext)
*/
public RetryContext open(RetryContext parent) {
return new ExceptionClassifierRetryContext(parent, exceptionClassifier).open(parent);
@@ -96,7 +96,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
/**
* Delegate to the policy currently activated in the context.
*
- * @see org.springframework.commons.retry.RetryPolicy#registerThrowable(org.springframework.commons.retry.RetryContext,
+ * @see org.springframework.retry.RetryPolicy#registerThrowable(org.springframework.retry.RetryContext,
* Throwable)
*/
public void registerThrowable(RetryContext context, Throwable throwable) {
diff --git a/src/main/java/org/springframework/commons/retry/policy/MapRetryContextCache.java b/src/main/java/org/springframework/retry/policy/MapRetryContextCache.java
similarity index 96%
rename from src/main/java/org/springframework/commons/retry/policy/MapRetryContextCache.java
rename to src/main/java/org/springframework/retry/policy/MapRetryContextCache.java
index d41021c..a3f330c 100644
--- a/src/main/java/org/springframework/commons/retry/policy/MapRetryContextCache.java
+++ b/src/main/java/org/springframework/retry/policy/MapRetryContextCache.java
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import org.springframework.commons.retry.RetryContext;
+import org.springframework.retry.RetryContext;
/**
* Map-based implementation of {@link RetryContextCache}. The map backing the
diff --git a/src/main/java/org/springframework/commons/retry/policy/NeverRetryPolicy.java b/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java
similarity index 78%
rename from src/main/java/org/springframework/commons/retry/policy/NeverRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java
index 09d53c0..5844551 100644
--- a/src/main/java/org/springframework/commons/retry/policy/NeverRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/NeverRetryPolicy.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
-import org.springframework.commons.retry.RetryContext;
-import org.springframework.commons.retry.RetryPolicy;
-import org.springframework.commons.retry.context.RetryContextSupport;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.context.RetryContextSupport;
/**
* A {@link RetryPolicy} that allows the first attempt but never permits a
@@ -34,7 +34,7 @@ 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)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*/
public boolean canRetry(RetryContext context) {
return !((NeverRetryContext) context).isFinished();
@@ -43,7 +43,7 @@ public class NeverRetryPolicy implements RetryPolicy {
/**
* Do nothing.
*
- * @see org.springframework.commons.retry.RetryPolicy#close(org.springframework.commons.retry.RetryContext)
+ * @see org.springframework.retry.RetryPolicy#close(org.springframework.retry.RetryContext)
*/
public void close(RetryContext context) {
// no-op
@@ -53,7 +53,7 @@ public class NeverRetryPolicy implements RetryPolicy {
* Return a context that can respond to early termination requests, but does
* nothing else.
*
- * @see org.springframework.commons.retry.RetryPolicy#open(RetryContext)
+ * @see org.springframework.retry.RetryPolicy#open(RetryContext)
*/
public RetryContext open(RetryContext parent) {
return new NeverRetryContext(parent);
@@ -61,7 +61,7 @@ public class NeverRetryPolicy implements RetryPolicy {
/**
* Make the throwable available for downstream use through the context.
- * @see org.springframework.commons.retry.RetryPolicy#registerThrowable(org.springframework.commons.retry.RetryContext,
+ * @see org.springframework.retry.RetryPolicy#registerThrowable(org.springframework.retry.RetryContext,
* Throwable)
*/
public void registerThrowable(RetryContext context, Throwable throwable) {
diff --git a/src/main/java/org/springframework/commons/retry/policy/RetryCacheCapacityExceededException.java b/src/main/java/org/springframework/retry/policy/RetryCacheCapacityExceededException.java
similarity index 93%
rename from src/main/java/org/springframework/commons/retry/policy/RetryCacheCapacityExceededException.java
rename to src/main/java/org/springframework/retry/policy/RetryCacheCapacityExceededException.java
index e19db59..2860800 100644
--- a/src/main/java/org/springframework/commons/retry/policy/RetryCacheCapacityExceededException.java
+++ b/src/main/java/org/springframework/retry/policy/RetryCacheCapacityExceededException.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
-import org.springframework.commons.retry.RetryException;
+import org.springframework.retry.RetryException;
/**
* Exception that indicates that a cache limit was exceeded. This is often a
diff --git a/src/main/java/org/springframework/commons/retry/policy/RetryContextCache.java b/src/main/java/org/springframework/retry/policy/RetryContextCache.java
similarity index 90%
rename from src/main/java/org/springframework/commons/retry/policy/RetryContextCache.java
rename to src/main/java/org/springframework/retry/policy/RetryContextCache.java
index 0def72c..9689488 100644
--- a/src/main/java/org/springframework/commons/retry/policy/RetryContextCache.java
+++ b/src/main/java/org/springframework/retry/policy/RetryContextCache.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
-import org.springframework.commons.retry.RetryContext;
+import org.springframework.retry.RetryContext;
/**
* Simple map-like abstraction for stateful retry policies to use when storing
diff --git a/src/main/java/org/springframework/commons/retry/policy/SimpleRetryPolicy.java b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java
similarity index 87%
rename from src/main/java/org/springframework/commons/retry/policy/SimpleRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java
index ce2aec0..7ccf6cc 100644
--- a/src/main/java/org/springframework/commons/retry/policy/SimpleRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/SimpleRetryPolicy.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.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;
+import org.springframework.classify.BinaryExceptionClassifier;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.context.RetryContextSupport;
/**
*
@@ -96,7 +96,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
/**
* Test for retryable operation based on the status.
*
- * @see org.springframework.commons.retry.RetryPolicy#canRetry(org.springframework.commons.retry.RetryContext)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*
* @return true if the last exception was retryable and the number of
* attempts so far is less than the limit.
@@ -107,7 +107,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
}
/**
- * @see org.springframework.commons.retry.RetryPolicy#close(RetryContext)
+ * @see org.springframework.retry.RetryPolicy#close(RetryContext)
*/
public void close(RetryContext status) {
}
@@ -127,7 +127,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#open(RetryContext)
*/
public RetryContext open(RetryContext parent) {
return new SimpleRetryContext(parent);
diff --git a/src/main/java/org/springframework/commons/retry/policy/SoftReferenceMapRetryContextCache.java b/src/main/java/org/springframework/retry/policy/SoftReferenceMapRetryContextCache.java
similarity index 96%
rename from src/main/java/org/springframework/commons/retry/policy/SoftReferenceMapRetryContextCache.java
rename to src/main/java/org/springframework/retry/policy/SoftReferenceMapRetryContextCache.java
index 3de75de..12140ac 100644
--- a/src/main/java/org/springframework/commons/retry/policy/SoftReferenceMapRetryContextCache.java
+++ b/src/main/java/org/springframework/retry/policy/SoftReferenceMapRetryContextCache.java
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.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;
+import org.springframework.retry.RetryContext;
/**
* Map-based implementation of {@link RetryContextCache}. The map backing the
diff --git a/src/main/java/org/springframework/commons/retry/policy/TimeoutRetryPolicy.java b/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java
similarity index 86%
rename from src/main/java/org/springframework/commons/retry/policy/TimeoutRetryPolicy.java
rename to src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java
index 0d206ed..3129895 100644
--- a/src/main/java/org/springframework/commons/retry/policy/TimeoutRetryPolicy.java
+++ b/src/main/java/org/springframework/retry/policy/TimeoutRetryPolicy.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.policy;
+package org.springframework.retry.policy;
-import org.springframework.commons.retry.RetryContext;
-import org.springframework.commons.retry.RetryPolicy;
-import org.springframework.commons.retry.context.RetryContextSupport;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.context.RetryContextSupport;
/**
* A {@link RetryPolicy} that allows a retry only if it hasn't timed out. The
@@ -57,7 +57,7 @@ public class TimeoutRetryPolicy implements RetryPolicy {
* 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)
+ * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
*/
public boolean canRetry(RetryContext context) {
return ((TimeoutRetryContext) context).isAlive();
diff --git a/src/main/java/org/springframework/retry/policy/package.html b/src/main/java/org/springframework/retry/policy/package.html
new file mode 100644
index 0000000..7652009
--- /dev/null
+++ b/src/main/java/org/springframework/retry/policy/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry policy concerns.
+
+
+
diff --git a/src/main/java/org/springframework/commons/retry/support/DefaultRetryState.java b/src/main/java/org/springframework/retry/support/DefaultRetryState.java
similarity index 84%
rename from src/main/java/org/springframework/commons/retry/support/DefaultRetryState.java
rename to src/main/java/org/springframework/retry/support/DefaultRetryState.java
index 8e8f3bb..b6f8ae8 100644
--- a/src/main/java/org/springframework/commons/retry/support/DefaultRetryState.java
+++ b/src/main/java/org/springframework/retry/support/DefaultRetryState.java
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.commons.retry.support;
+package org.springframework.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;
+import org.springframework.classify.Classifier;
+import org.springframework.retry.RecoveryCallback;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryOperations;
+import org.springframework.retry.RetryState;
/**
*
@@ -84,7 +84,7 @@ public class DefaultRetryState implements RetryState {
/*
* (non-Javadoc)
*
- * @see org.springframework.commons.retry.IRetryState#getKey()
+ * @see org.springframework.batch.retry.IRetryState#getKey()
*/
public Object getKey() {
return key;
@@ -93,7 +93,7 @@ public class DefaultRetryState implements RetryState {
/*
* (non-Javadoc)
*
- * @see org.springframework.commons.retry.IRetryState#isForceRefresh()
+ * @see org.springframework.batch.retry.IRetryState#isForceRefresh()
*/
public boolean isForceRefresh() {
return forceRefresh;
@@ -103,7 +103,7 @@ public class DefaultRetryState implements RetryState {
* (non-Javadoc)
*
* @see
- * org.springframework.commons.retry.RetryState#rollbackFor(java.lang.Throwable
+ * org.springframework.batch.retry.RetryState#rollbackFor(java.lang.Throwable
* )
*/
public boolean rollbackFor(Throwable exception) {
diff --git a/src/main/java/org/springframework/commons/retry/support/RetrySynchronizationManager.java b/src/main/java/org/springframework/retry/support/RetrySynchronizationManager.java
similarity index 90%
rename from src/main/java/org/springframework/commons/retry/support/RetrySynchronizationManager.java
rename to src/main/java/org/springframework/retry/support/RetrySynchronizationManager.java
index 35c3939..bacef57 100644
--- a/src/main/java/org/springframework/commons/retry/support/RetrySynchronizationManager.java
+++ b/src/main/java/org/springframework/retry/support/RetrySynchronizationManager.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.support;
+package org.springframework.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;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryOperations;
/**
* Global variable support for retry clients. Normally it is not necessary for
diff --git a/src/main/java/org/springframework/commons/retry/support/RetryTemplate.java b/src/main/java/org/springframework/retry/support/RetryTemplate.java
similarity index 92%
rename from src/main/java/org/springframework/commons/retry/support/RetryTemplate.java
rename to src/main/java/org/springframework/retry/support/RetryTemplate.java
index 20a9914..ca0b11b 100644
--- a/src/main/java/org/springframework/commons/retry/support/RetryTemplate.java
+++ b/src/main/java/org/springframework/retry/support/RetryTemplate.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.commons.retry.support;
+package org.springframework.retry.support;
import java.util.ArrayList;
import java.util.Arrays;
@@ -23,24 +23,24 @@ 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;
+import org.springframework.repeat.RepeatException;
+import org.springframework.retry.ExhaustedRetryException;
+import org.springframework.retry.RecoveryCallback;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryException;
+import org.springframework.retry.RetryListener;
+import org.springframework.retry.RetryOperations;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.RetryState;
+import org.springframework.retry.TerminatedRetryException;
+import org.springframework.retry.backoff.BackOffContext;
+import org.springframework.retry.backoff.BackOffInterruptedException;
+import org.springframework.retry.backoff.BackOffPolicy;
+import org.springframework.retry.backoff.NoBackOffPolicy;
+import org.springframework.retry.policy.MapRetryContextCache;
+import org.springframework.retry.policy.RetryContextCache;
+import org.springframework.retry.policy.SimpleRetryPolicy;
/**
* Template class that simplifies the execution of operations with retry
@@ -57,7 +57,7 @@ import org.springframework.commons.retry.policy.SimpleRetryPolicy;
* 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
+ * {@link org.springframework.retry.backoff.BackOffPolicy} controls how
* long the pause is between each individual retry attempt.
*
* This class is thread-safe and suitable for concurrent access when executing
@@ -202,7 +202,9 @@ public class RetryTemplate implements RetryOperations {
// Allow the retry policy to initialise itself...
RetryContext context = open(retryPolicy, state);
- logger.debug("RetryContext retrieved: " + context);
+ if (logger.isTraceEnabled()) {
+ logger.trace("RetryContext retrieved: " + context);
+ }
// Make sure the context is available globally for clients who need
// it...
diff --git a/src/main/java/org/springframework/retry/support/package.html b/src/main/java/org/springframework/retry/support/package.html
new file mode 100644
index 0000000..549ec82
--- /dev/null
+++ b/src/main/java/org/springframework/retry/support/package.html
@@ -0,0 +1,7 @@
+
+
+
+Infrastructure implementations of retry support concerns.
+
+
+
diff --git a/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java b/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java
new file mode 100644
index 0000000..c0b7204
--- /dev/null
+++ b/src/test/java/org/springframework/classify/BackToBackPatternClassifierTests.java
@@ -0,0 +1,72 @@
+/*
+ * 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.classify;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.classify.annotation.Classifier;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class BackToBackPatternClassifierTests {
+
+ private BackToBackPatternClassifier classifier = new BackToBackPatternClassifier();
+
+ private Map map;
+
+ @Before
+ public void createMap() {
+ map = new HashMap();
+ map.put("foo", "bar");
+ map.put("*", "spam");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNoClassifiers() {
+ classifier.classify("foo");
+ }
+
+ @Test
+ public void testCreateFromConstructor() {
+ classifier = new BackToBackPatternClassifier(
+ new PatternMatchingClassifier(Collections.singletonMap(
+ "oof", "bucket")),
+ new PatternMatchingClassifier(map));
+ assertEquals("spam", classifier.classify("oof"));
+ }
+
+ @Test
+ public void testSetRouterDelegate() {
+ classifier.setRouterDelegate(new Object() {
+ @SuppressWarnings("unused")
+ @Classifier
+ public String convert(String value) {
+ return "bucket";
+ }
+ });
+ classifier.setMatcherMap(map);
+ assertEquals("spam", classifier.classify("oof"));
+ }
+
+}
diff --git a/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java b/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java
new file mode 100644
index 0000000..1d763b0
--- /dev/null
+++ b/src/test/java/org/springframework/classify/BinaryExceptionClassifierTests.java
@@ -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.classify;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+public class BinaryExceptionClassifierTests extends TestCase {
+
+ BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(false);
+
+ public void testClassifyNullIsDefault() {
+ assertFalse(classifier.classify(null));
+ }
+
+ public void testFalseIsDefault() {
+ assertFalse(classifier.getDefault());
+ }
+
+ public void testDefaultProvided() {
+ classifier = new BinaryExceptionClassifier(true);
+ assertTrue(classifier.getDefault());
+ }
+
+ public void testClassifyRandomException() {
+ assertFalse(classifier.classify(new IllegalStateException("foo")));
+ }
+
+ public void testClassifyExactMatch() {
+ Collection> set = Collections
+ .> singleton(IllegalStateException.class);
+ assertTrue(new BinaryExceptionClassifier(set).classify(new IllegalStateException("Foo")));
+ }
+
+ public void testTypesProvidedInConstructor() {
+ classifier = new BinaryExceptionClassifier(Collections
+ .> singleton(IllegalStateException.class));
+ assertTrue(classifier.classify(new IllegalStateException("Foo")));
+ }
+
+ public void testTypesProvidedInConstructorWithNonDefault() {
+ classifier = new BinaryExceptionClassifier(Collections
+ .> singleton(IllegalStateException.class), false);
+ assertFalse(classifier.classify(new IllegalStateException("Foo")));
+ }
+}
diff --git a/src/test/java/org/springframework/classify/ClassifierAdapterTests.java b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java
new file mode 100644
index 0000000..a97bfb3
--- /dev/null
+++ b/src/test/java/org/springframework/classify/ClassifierAdapterTests.java
@@ -0,0 +1,127 @@
+/*
+ * 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.classify;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.classify.annotation.Classifier;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class ClassifierAdapterTests {
+
+ private ClassifierAdapter adapter = new ClassifierAdapter();
+
+ @Test
+ public void testClassifierAdapterObject() {
+ adapter = new ClassifierAdapter(new Object() {
+ @SuppressWarnings("unused")
+ @Classifier
+ public Integer getValue(String key) {
+ return Integer.parseInt(key);
+ }
+
+ @SuppressWarnings("unused")
+ public Integer getAnother(String key) {
+ throw new UnsupportedOperationException("Not allowed");
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testClassifierAdapterObjectWithNoAnnotation() {
+ adapter = new ClassifierAdapter(new Object() {
+ @SuppressWarnings("unused")
+ public Integer getValue(String key) {
+ return Integer.parseInt(key);
+ }
+
+ @SuppressWarnings("unused")
+ public Integer getAnother(String key) {
+ throw new UnsupportedOperationException("Not allowed");
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test
+ public void testClassifierAdapterObjectSingleMethodWithNoAnnotation() {
+ adapter = new ClassifierAdapter(new Object() {
+ @SuppressWarnings("unused")
+ public Integer getValue(String key) {
+ return Integer.parseInt(key);
+ }
+ @SuppressWarnings("unused")
+ public void doNothing(String key) {
+ }
+ @SuppressWarnings("unused")
+ public String doNothing(String key, int value) {
+ return "foo";
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test
+ public void testClassifierAdapterClassifier() {
+ adapter = new ClassifierAdapter(
+ new org.springframework.classify.Classifier() {
+ public Integer classify(String classifiable) {
+ return Integer.valueOf(classifiable);
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test
+ public void testClassifyWithSetter() {
+ adapter.setDelegate(new Object() {
+ @SuppressWarnings("unused")
+ @Classifier
+ public Integer getValue(String key) {
+ return Integer.parseInt(key);
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testClassifyWithWrongType() {
+ adapter.setDelegate(new Object() {
+ @SuppressWarnings("unused")
+ @Classifier
+ public String getValue(Integer key) {
+ return key.toString();
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+ @Test
+ public void testClassifyWithClassifier() {
+ adapter.setDelegate(new org.springframework.classify.Classifier() {
+ public Integer classify(String classifiable) {
+ return Integer.valueOf(classifiable);
+ }
+ });
+ assertEquals(23, adapter.classify("23").intValue());
+ }
+
+}
diff --git a/src/test/java/org/springframework/classify/ClassifierSupportTests.java b/src/test/java/org/springframework/classify/ClassifierSupportTests.java
new file mode 100644
index 0000000..c1416c2
--- /dev/null
+++ b/src/test/java/org/springframework/classify/ClassifierSupportTests.java
@@ -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.classify;
+
+import org.springframework.classify.ClassifierSupport;
+
+import junit.framework.TestCase;
+
+public class ClassifierSupportTests extends TestCase {
+
+ public void testClassifyNullIsDefault() {
+ ClassifierSupport classifier = new ClassifierSupport("foo");
+ assertEquals(classifier.classify(null), "foo");
+ }
+
+ public void testClassifyRandomException() {
+ ClassifierSupport classifier = new ClassifierSupport("foo");
+ assertEquals(classifier.classify(new IllegalStateException("Foo")), classifier.classify(null));
+ }
+
+}
diff --git a/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java b/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java
new file mode 100644
index 0000000..bfb0f8d
--- /dev/null
+++ b/src/test/java/org/springframework/classify/PatternMatchingClassifierTests.java
@@ -0,0 +1,58 @@
+/*
+ * 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.classify;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.classify.PatternMatchingClassifier;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class PatternMatchingClassifierTests {
+
+ private PatternMatchingClassifier classifier = new PatternMatchingClassifier();
+
+ private Map map;
+
+ @Before
+ public void createMap() {
+ map = new HashMap();
+ map.put("foo", "bar");
+ map.put("*", "spam");
+ }
+
+ @Test
+ public void testSetPatternMap() {
+ classifier.setPatternMap(map);
+ assertEquals("bar", classifier.classify("foo"));
+ assertEquals("spam", classifier.classify("bucket"));
+ }
+
+ @Test
+ public void testCreateFromMap() {
+ classifier = new PatternMatchingClassifier(map);
+ assertEquals("bar", classifier.classify("foo"));
+ assertEquals("spam", classifier.classify("bucket"));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/springframework/classify/SubclassExceptionClassifierTests.java b/src/test/java/org/springframework/classify/SubclassExceptionClassifierTests.java
new file mode 100644
index 0000000..4982937
--- /dev/null
+++ b/src/test/java/org/springframework/classify/SubclassExceptionClassifierTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.classify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+import org.junit.Test;
+import org.springframework.classify.SubclassClassifier;
+
+public class SubclassExceptionClassifierTests {
+
+ SubclassClassifier classifier = new SubclassClassifier();
+
+ @Test
+ public void testClassifyNullIsDefault() {
+ assertEquals(classifier.classify(null), classifier.getDefault());
+ }
+
+ @Test
+ public void testClassifyNull() {
+ assertNull(classifier.classify(null));
+ }
+
+ @Test
+ public void testClassifyNullNonDefault() {
+ classifier = new SubclassClassifier("foo");
+ assertEquals("foo", classifier.classify(null));
+ }
+
+ @Test
+ public void testClassifyRandomException() {
+ assertNull(classifier.classify(new IllegalStateException("Foo")));
+ }
+
+ @Test
+ public void testClassifyExactMatch() {
+ classifier.setTypeMap(Collections., String> singletonMap(
+ IllegalStateException.class, "foo"));
+ assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
+ }
+
+ @Test
+ public void testClassifySubclassMatch() {
+ classifier.setTypeMap(Collections., String> singletonMap(RuntimeException.class,
+ "foo"));
+ assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
+ }
+
+ @Test
+ public void testClassifySuperclassDoesNotMatch() {
+ classifier.setTypeMap(Collections., String> singletonMap(
+ IllegalStateException.class, "foo"));
+ assertEquals(classifier.getDefault(), classifier.classify(new RuntimeException("Foo")));
+ }
+
+ @Test
+ public void testClassifyAncestorMatch() {
+ classifier.setTypeMap(new HashMap, String>() {
+ {
+ put(Exception.class, "foo");
+ put(IllegalArgumentException.class, "bar");
+ put(RuntimeException.class, "spam");
+ }
+ });
+ assertEquals("spam", classifier.classify(new IllegalStateException("Foo")));
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/AbstractExceptionTests.java b/src/test/java/org/springframework/repeat/AbstractExceptionTests.java
new file mode 100644
index 0000000..54d7f73
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/AbstractExceptionTests.java
@@ -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.repeat;
+
+import junit.framework.TestCase;
+
+public abstract class AbstractExceptionTests extends TestCase {
+
+ public void testExceptionString() throws Exception {
+ Exception exception = getException("foo");
+ assertEquals("foo", exception.getMessage());
+ }
+
+ public void testExceptionStringThrowable() throws Exception {
+ Exception exception = getException("foo", new IllegalStateException());
+ assertEquals("foo", exception.getMessage().substring(0, 3));
+ }
+
+ public abstract Exception getException(String msg) throws Exception;
+
+ public abstract Exception getException(String msg, Throwable t) throws Exception;
+}
diff --git a/src/test/java/org/springframework/repeat/RepeatExceptionTests.java b/src/test/java/org/springframework/repeat/RepeatExceptionTests.java
new file mode 100644
index 0000000..b26cd50
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/RepeatExceptionTests.java
@@ -0,0 +1,34 @@
+/*
+ * 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.repeat;
+
+import org.springframework.repeat.RepeatException;
+
+public class RepeatExceptionTests extends AbstractExceptionTests {
+
+ public Exception getException(String msg) throws Exception {
+ return new RepeatException(msg);
+ }
+
+ public Exception getException(String msg, Throwable t) throws Exception {
+ return new RepeatException(msg, t);
+ }
+
+ public void testNothing() throws Exception {
+ // fool coverage tools...
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/callback/NestedRepeatCallbackTests.java b/src/test/java/org/springframework/repeat/callback/NestedRepeatCallbackTests.java
new file mode 100644
index 0000000..3562582
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/callback/NestedRepeatCallbackTests.java
@@ -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.repeat.callback;
+
+import junit.framework.TestCase;
+
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.support.RepeatTemplate;
+
+public class NestedRepeatCallbackTests extends TestCase {
+
+ int count = 0;
+
+ public void testExecute() throws Exception {
+ NestedRepeatCallback callback = new NestedRepeatCallback(new RepeatTemplate(), new RepeatCallback() {
+ public RepeatStatus doInIteration(RepeatContext context) throws Exception {
+ count++;
+ return RepeatStatus.continueIf(count <= 1);
+ }
+ });
+ RepeatStatus result = callback.doInIteration(null);
+ assertEquals(2, count);
+ assertFalse(result.isContinuable()); // False because processing has finished
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/context/RepeatContextCounterTests.java b/src/test/java/org/springframework/repeat/context/RepeatContextCounterTests.java
new file mode 100644
index 0000000..d17373d
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/context/RepeatContextCounterTests.java
@@ -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.repeat.context;
+
+import junit.framework.TestCase;
+
+import org.springframework.repeat.RepeatContext;
+
+public class RepeatContextCounterTests extends TestCase {
+
+ RepeatContext parent = new RepeatContextSupport(null);
+ RepeatContext context = new RepeatContextSupport(parent);
+
+ public void testAttributeCreated() {
+ new RepeatContextCounter(context, "FOO");
+ assertTrue(context.hasAttribute("FOO"));
+ }
+
+ public void testAttributeCreatedWithNullParent() {
+ new RepeatContextCounter(parent, "FOO", true);
+ assertTrue(parent.hasAttribute("FOO"));
+ }
+
+ public void testVanillaIncrement() throws Exception {
+ RepeatContextCounter counter = new RepeatContextCounter(context, "FOO");
+ assertEquals(0, counter.getCount());
+ counter.increment(1);
+ assertEquals(1, counter.getCount());
+ counter.increment(2);
+ assertEquals(3, counter.getCount());
+ }
+
+ public void testAttributeCreatedInParent() throws Exception {
+ new RepeatContextCounter(context, "FOO", true);
+ assertFalse(context.hasAttribute("FOO"));
+ assertTrue(parent.hasAttribute("FOO"));
+ }
+
+ public void testParentIncrement() throws Exception {
+ RepeatContextCounter counter = new RepeatContextCounter(context, "FOO", true);
+ assertEquals(0, counter.getCount());
+ counter.increment(1);
+ // now get new context with same parent
+ counter = new RepeatContextCounter(new RepeatContextSupport(parent), "FOO", true);
+ assertEquals(1, counter.getCount());
+ counter.increment(2);
+ assertEquals(3, counter.getCount());
+ }
+
+}
diff --git a/src/test/java/org/springframework/repeat/context/RepeatContextSupportTests.java b/src/test/java/org/springframework/repeat/context/RepeatContextSupportTests.java
new file mode 100644
index 0000000..d89d052
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/context/RepeatContextSupportTests.java
@@ -0,0 +1,93 @@
+/*
+ * 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.repeat.context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * @author dsyer
+ *
+ */
+public class RepeatContextSupportTests extends TestCase {
+
+ private List list = new ArrayList();
+
+ /**
+ * Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ */
+ public void testDestructionCallbackSunnyDay() throws Exception {
+ RepeatContextSupport context = new RepeatContextSupport(null);
+ context.setAttribute("foo", "FOO");
+ context.registerDestructionCallback("foo", new Runnable() {
+ public void run() {
+ list.add("bar");
+ }
+ });
+ context.close();
+ assertEquals(1, list.size());
+ assertEquals("bar", list.get(0));
+ }
+
+ /**
+ * Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ */
+ public void testDestructionCallbackMissingAttribute() throws Exception {
+ RepeatContextSupport context = new RepeatContextSupport(null);
+ context.registerDestructionCallback("foo", new Runnable() {
+ public void run() {
+ list.add("bar");
+ }
+ });
+ context.close();
+ // No check for the attribute before executing callback
+ assertEquals(1, list.size());
+ }
+
+ /**
+ * Test method for {@link org.springframework.repeat.context.RepeatContextSupport#registerDestructionCallback(java.lang.String, java.lang.Runnable)}.
+ */
+ public void testDestructionCallbackWithException() throws Exception {
+ RepeatContextSupport context = new RepeatContextSupport(null);
+ context.setAttribute("foo", "FOO");
+ context.setAttribute("bar", "BAR");
+ context.registerDestructionCallback("bar", new Runnable() {
+ public void run() {
+ list.add("spam");
+ throw new RuntimeException("fail!");
+ }
+ });
+ context.registerDestructionCallback("foo", new Runnable() {
+ public void run() {
+ list.add("bar");
+ throw new RuntimeException("fail!");
+ }
+ });
+ try {
+ context.close();
+ fail("Expected RuntimeException");
+ } catch (RuntimeException e) {
+ // We don't care which one was thrown...
+ assertEquals("fail!", e.getMessage());
+ }
+ // ...but we do care that both were executed:
+ assertEquals(2, list.size());
+ assertTrue(list.contains("bar"));
+ assertTrue(list.contains("spam"));
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/context/SynchronizedAttributeAccessorTests.java b/src/test/java/org/springframework/repeat/context/SynchronizedAttributeAccessorTests.java
new file mode 100644
index 0000000..3a94bfd
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/context/SynchronizedAttributeAccessorTests.java
@@ -0,0 +1,113 @@
+/*
+ * 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.repeat.context;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.AttributeAccessorSupport;
+
+public class SynchronizedAttributeAccessorTests extends TestCase {
+
+ SynchronizedAttributeAccessor accessor = new SynchronizedAttributeAccessor();
+
+ public void testHashCode() {
+ SynchronizedAttributeAccessor another = new SynchronizedAttributeAccessor();
+ accessor.setAttribute("foo", "bar");
+ another.setAttribute("foo", "bar");
+ assertEquals(accessor, another);
+ assertEquals("Object.hashCode() contract broken", accessor.hashCode(), another.hashCode());
+ }
+
+ public void testToStringWithNoAttributes() throws Exception {
+ assertNotNull(accessor.toString());
+ }
+
+ public void testToStringWithAttributes() throws Exception {
+ accessor.setAttribute("foo", "bar");
+ accessor.setAttribute("spam", "bucket");
+ assertNotNull(accessor.toString());
+ }
+
+ public void testAttributeNames() {
+ accessor.setAttribute("foo", "bar");
+ accessor.setAttribute("spam", "bucket");
+ List list = Arrays.asList(accessor.attributeNames());
+ assertEquals(2, list.size());
+ assertTrue(list.contains("foo"));
+ }
+
+ public void testEqualsSameType() {
+ SynchronizedAttributeAccessor another = new SynchronizedAttributeAccessor();
+ accessor.setAttribute("foo", "bar");
+ another.setAttribute("foo", "bar");
+ assertEquals(accessor, another);
+ }
+
+ public void testEqualsSelf() {
+ accessor.setAttribute("foo", "bar");
+ assertEquals(accessor, accessor);
+ }
+
+ public void testEqualsWrongType() {
+ accessor.setAttribute("foo", "bar");
+ Map another = Collections.singletonMap("foo", "bar");
+ // Accessor and another are instances of unrelated classes, they should
+ // never be equal...
+ assertFalse(accessor.equals(another));
+ }
+
+ public void testEqualsSupport() {
+ AttributeAccessorSupport another = new AttributeAccessorSupport() {
+ };
+ accessor.setAttribute("foo", "bar");
+ another.setAttribute("foo", "bar");
+ assertEquals(accessor, another);
+ }
+
+ public void testGetAttribute() {
+ accessor.setAttribute("foo", "bar");
+ assertEquals("bar", accessor.getAttribute("foo"));
+ }
+
+ public void testSetAttributeIfAbsentWhenAlreadyPresent() {
+ accessor.setAttribute("foo", "bar");
+ assertEquals("bar", accessor.setAttributeIfAbsent("foo", "spam"));
+ }
+
+ public void testSetAttributeIfAbsentWhenNotAlreadyPresent() {
+ assertEquals(null, accessor.setAttributeIfAbsent("foo", "bar"));
+ assertEquals("bar", accessor.getAttribute("foo"));
+ }
+
+ public void testHasAttribute() {
+ accessor.setAttribute("foo", "bar");
+ assertEquals(true, accessor.hasAttribute("foo"));
+ }
+
+ public void testRemoveAttribute() {
+ accessor.setAttribute("foo", "bar");
+ assertEquals("bar", accessor.getAttribute("foo"));
+ accessor.removeAttribute("foo");
+ assertEquals(null, accessor.getAttribute("foo"));
+ }
+
+}
diff --git a/src/test/java/org/springframework/repeat/exception/CompositeExceptionHandlerTests.java b/src/test/java/org/springframework/repeat/exception/CompositeExceptionHandlerTests.java
new file mode 100644
index 0000000..54cd8c4
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/exception/CompositeExceptionHandlerTests.java
@@ -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.repeat.exception;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.exception.CompositeExceptionHandler;
+import org.springframework.repeat.exception.ExceptionHandler;
+
+public class CompositeExceptionHandlerTests extends TestCase {
+
+ private CompositeExceptionHandler handler = new CompositeExceptionHandler();
+
+ public void testNewHandler() throws Throwable {
+ try {
+ handler.handleException(null, new RuntimeException());
+ }
+ catch (RuntimeException e) {
+ fail("Unexpected RuntimeException");
+ }
+ }
+
+ public void testDelegation() throws Throwable {
+ final List list = new ArrayList();
+ handler.setHandlers(new ExceptionHandler[] {
+ new ExceptionHandler() {
+ public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
+ list.add("1");
+ }
+ },
+ new ExceptionHandler() {
+ public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
+ list.add("2");
+ }
+ }
+ });
+ handler.handleException(null, new RuntimeException());
+ assertEquals(2, list.size());
+ assertEquals("1", list.get(0));
+ assertEquals("2", list.get(1));
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/exception/DefaultExceptionHandlerTests.java b/src/test/java/org/springframework/repeat/exception/DefaultExceptionHandlerTests.java
new file mode 100644
index 0000000..820e9f9
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/exception/DefaultExceptionHandlerTests.java
@@ -0,0 +1,45 @@
+/*
+ * 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.repeat.exception;
+
+import junit.framework.TestCase;
+
+import org.springframework.repeat.RepeatContext;
+
+public class DefaultExceptionHandlerTests extends TestCase {
+
+ private DefaultExceptionHandler handler = new DefaultExceptionHandler();
+ private RepeatContext context = null;
+
+ public void testRuntimeException() throws Throwable {
+ try {
+ handler.handleException(context, new RuntimeException("Foo"));
+ fail("Expected RuntimeException");
+ } catch (RuntimeException e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ public void testError() throws Throwable {
+ try {
+ handler.handleException(context, new Error("Foo"));
+ fail("Expected Error");
+ } catch (Error e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandlerTests.java b/src/test/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandlerTests.java
new file mode 100644
index 0000000..5d98969
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/exception/LogOrRethrowExceptionHandlerTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.repeat.exception;
+
+import java.io.StringWriter;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
+import org.apache.log4j.WriterAppender;
+import org.springframework.classify.ClassifierSupport;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.exception.LogOrRethrowExceptionHandler.Level;
+
+public class LogOrRethrowExceptionHandlerTests extends TestCase {
+
+ private LogOrRethrowExceptionHandler handler = new LogOrRethrowExceptionHandler();
+
+ private StringWriter writer;
+
+ private RepeatContext context = null;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ Logger logger = Logger.getLogger(LogOrRethrowExceptionHandler.class);
+ logger.setLevel(org.apache.log4j.Level.DEBUG);
+ writer = new StringWriter();
+ logger.removeAllAppenders();
+ logger.getParent().removeAllAppenders();
+ logger.addAppender(new WriterAppender(new SimpleLayout(), writer));
+ }
+
+ public void testRuntimeException() throws Throwable {
+ try {
+ handler.handleException(context, new RuntimeException("Foo"));
+ fail("Expected RuntimeException");
+ }
+ catch (RuntimeException e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ public void testError() throws Throwable {
+ try {
+ handler.handleException(context, new Error("Foo"));
+ fail("Expected Error");
+ }
+ catch (Error e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ public void testNotRethrownErrorLevel() throws Throwable {
+ handler.setExceptionClassifier(new ClassifierSupport(Level.RETHROW) {
+ public Level classify(Throwable throwable) {
+ return Level.ERROR;
+ }
+ });
+ // No exception...
+ handler.handleException(context, new Error("Foo"));
+ assertNotNull(writer.toString());
+ }
+
+ public void testNotRethrownWarnLevel() throws Throwable {
+ handler.setExceptionClassifier(new ClassifierSupport(Level.RETHROW) {
+ public Level classify(Throwable throwable) {
+ return Level.WARN;
+ }
+ });
+ // No exception...
+ handler.handleException(context, new Error("Foo"));
+ assertNotNull(writer.toString());
+ }
+
+ public void testNotRethrownDebugLevel() throws Throwable {
+ handler.setExceptionClassifier(new ClassifierSupport(Level.RETHROW) {
+ public Level classify(Throwable throwable) {
+ return Level.DEBUG;
+ }
+ });
+ // No exception...
+ handler.handleException(context, new Error("Foo"));
+ assertNotNull(writer.toString());
+ }
+
+}
diff --git a/src/test/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandlerTests.java b/src/test/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandlerTests.java
new file mode 100644
index 0000000..0ab51c8
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/exception/RethrowOnThresholdExceptionHandlerTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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.repeat.exception;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+import org.springframework.repeat.RepeatContext;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+public class RethrowOnThresholdExceptionHandlerTests {
+
+ private RethrowOnThresholdExceptionHandler handler = new RethrowOnThresholdExceptionHandler();
+
+ private RepeatContext parent = new RepeatContextSupport(null);
+
+ private RepeatContext context = new RepeatContextSupport(parent);
+
+ @Test
+ public void testRuntimeException() throws Throwable {
+ try {
+ handler.handleException(context, new RuntimeException("Foo"));
+ fail("Expected RuntimeException");
+ }
+ catch (RuntimeException e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testError() throws Throwable {
+ try {
+ handler.handleException(context, new Error("Foo"));
+ fail("Expected Error");
+ }
+ catch (Error e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotRethrownWithThreshold() throws Throwable {
+ handler.setThresholds(Collections., Integer> singletonMap(Exception.class, 1));
+ // No exception...
+ handler.handleException(context, new RuntimeException("Foo"));
+ AtomicInteger counter = (AtomicInteger) context.getAttribute(context.attributeNames()[0]);
+ assertNotNull(counter);
+ assertEquals(1, counter.get());
+ }
+
+ @Test
+ public void testRethrowOnThreshold() throws Throwable {
+ handler.setThresholds(Collections., Integer> singletonMap(Exception.class, 2));
+ // No exception...
+ handler.handleException(context, new RuntimeException("Foo"));
+ handler.handleException(context, new RuntimeException("Foo"));
+ try {
+ handler.handleException(context, new RuntimeException("Foo"));
+ fail("Expected RuntimeException");
+ }
+ catch (RuntimeException e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotUseParent() throws Throwable {
+ handler.setThresholds(Collections., Integer> singletonMap(Exception.class, 1));
+ // No exception...
+ handler.handleException(context, new RuntimeException("Foo"));
+ context = new RepeatContextSupport(parent);
+ try {
+ // No exception again - context is changed...
+ handler.handleException(context, new RuntimeException("Foo"));
+ }
+ catch (RuntimeException e) {
+ fail("Unexpected Error");
+ }
+ }
+
+ @Test
+ public void testUseParent() throws Throwable {
+ handler.setThresholds(Collections., Integer> singletonMap(Exception.class, 1));
+ handler.setUseParent(true);
+ // No exception...
+ handler.handleException(context, new RuntimeException("Foo"));
+ context = new RepeatContextSupport(parent);
+ try {
+ handler.handleException(context, new RuntimeException("Foo"));
+ fail("Expected Error");
+ }
+ catch (RuntimeException e) {
+ assertEquals("Foo", e.getMessage());
+ }
+ }
+
+}
diff --git a/src/test/java/org/springframework/repeat/exception/SimpleLimitExceptionHandlerTests.java b/src/test/java/org/springframework/repeat/exception/SimpleLimitExceptionHandlerTests.java
new file mode 100644
index 0000000..86bc3be
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/exception/SimpleLimitExceptionHandlerTests.java
@@ -0,0 +1,267 @@
+/*
+ * 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.repeat.exception;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.repeat.context.RepeatContextSupport;
+
+/**
+ * Unit tests for {@link SimpleLimitExceptionHandler}
+ *
+ * @author Robert Kasanicky
+ * @author Dave Syer
+ */
+public class SimpleLimitExceptionHandlerTests {
+
+ // object under test
+ private SimpleLimitExceptionHandler handler = new SimpleLimitExceptionHandler();
+
+ @Before
+ public void initializeHandler() throws Exception {
+ handler.afterPropertiesSet();
+ }
+
+ @Test
+ public void testInitializeWithNullContext() throws Throwable {
+ try {
+ handler.handleException(null, new RuntimeException("foo"));
+ fail("Expected IllegalArgumentException");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testInitializeWithNullContextAndNullException() throws Throwable {
+ try {
+ handler.handleException(null, null);
+ }
+ catch (IllegalArgumentException e) {
+ // expected;
+ }
+ }
+
+ @Test
+ public void testDefaultBehaviour() throws Throwable {
+ Throwable throwable = new RuntimeException("foo");
+ try {
+ handler.handleException(new RepeatContextSupport(null), throwable);
+ fail("Exception was swallowed.");
+ }
+ catch (RuntimeException expected) {
+ assertTrue("Exception is rethrown, ignoring the exception limit", true);
+ assertSame(expected, throwable);
+ }
+ }
+
+ /**
+ * Other than nominated exception type should be rethrown, ignoring the
+ * exception limit.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testNormalExceptionThrown() throws Throwable {
+ Throwable throwable = new RuntimeException("foo");
+
+ final int MORE_THAN_ZERO = 1;
+ handler.setLimit(MORE_THAN_ZERO);
+ handler.setExceptionClasses(Collections.> singleton(IllegalArgumentException.class));
+ handler.afterPropertiesSet();
+
+ try {
+ handler.handleException(new RepeatContextSupport(null), throwable);
+ fail("Exception was swallowed.");
+ }
+ catch (RuntimeException expected) {
+ assertTrue("Exception is rethrown, ignoring the exception limit", true);
+ assertSame(expected, throwable);
+ }
+ }
+
+ /**
+ * TransactionInvalidException should only be rethrown below the exception
+ * limit.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testLimitedExceptionTypeNotThrown() throws Throwable {
+ final int MORE_THAN_ZERO = 1;
+ handler.setLimit(MORE_THAN_ZERO);
+ handler.setExceptionClasses(Collections.> singleton(RuntimeException.class));
+ handler.afterPropertiesSet();
+
+ try {
+ handler.handleException(new RepeatContextSupport(null), new RuntimeException("foo"));
+ }
+ catch (RuntimeException expected) {
+ fail("Unexpected exception.");
+ }
+ }
+
+ /**
+ * TransactionInvalidException should only be rethrown below the exception
+ * limit.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testLimitedExceptionNotThrownFromSiblings() throws Throwable {
+ Throwable throwable = new RuntimeException("foo");
+
+ final int MORE_THAN_ZERO = 1;
+ handler.setLimit(MORE_THAN_ZERO);
+ handler.setExceptionClasses(Collections.> singleton(RuntimeException.class));
+ handler.afterPropertiesSet();
+
+ RepeatContextSupport parent = new RepeatContextSupport(null);
+
+ try {
+ RepeatContextSupport context = new RepeatContextSupport(parent);
+ handler.handleException(context, throwable);
+ context = new RepeatContextSupport(parent);
+ handler.handleException(context, throwable);
+ }
+ catch (RuntimeException expected) {
+ fail("Unexpected exception.");
+ }
+ }
+
+ /**
+ * TransactionInvalidException should only be rethrown below the exception
+ * limit.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testLimitedExceptionThrownFromSiblingsWhenUsingParent() throws Throwable {
+ Throwable throwable = new RuntimeException("foo");
+
+ final int MORE_THAN_ZERO = 1;
+ handler.setLimit(MORE_THAN_ZERO);
+ handler.setExceptionClasses(Collections.> singleton(RuntimeException.class));
+ handler.setUseParent(true);
+ handler.afterPropertiesSet();
+
+ RepeatContextSupport parent = new RepeatContextSupport(null);
+
+ try {
+ RepeatContextSupport context = new RepeatContextSupport(parent);
+ handler.handleException(context, throwable);
+ context = new RepeatContextSupport(parent);
+ handler.handleException(context, throwable);
+ fail("Expected exception.");
+ }
+ catch (RuntimeException expected) {
+ assertSame(throwable, expected);
+ }
+ }
+
+ /**
+ * Exceptions are swallowed until the exception limit is exceeded. After the
+ * limit is exceeded exceptions are rethrown
+ */
+ @Test
+ public void testExceptionNotThrownBelowLimit() throws Throwable {
+
+ final int EXCEPTION_LIMIT = 3;
+ handler.setLimit(EXCEPTION_LIMIT);
+ handler.afterPropertiesSet();
+
+ List throwables = new ArrayList() {
+ {
+ for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
+ add(new RuntimeException("below exception limit"));
+ }
+ }
+ };
+
+ RepeatContextSupport context = new RepeatContextSupport(null);
+
+ try {
+ for (Throwable throwable : throwables) {
+
+ handler.handleException(context, throwable);
+ assertTrue("exceptions up to limit are swallowed", true);
+
+ }
+ }
+ catch (RuntimeException unexpected) {
+ fail("exception rethrown although exception limit was not exceeded");
+ }
+
+ }
+
+ /**
+ * TransactionInvalidExceptions are swallowed until the exception limit is
+ * exceeded. After the limit is exceeded exceptions are rethrown as
+ * BatchCriticalExceptions
+ */
+ @Test
+ public void testExceptionThrownAboveLimit() throws Throwable {
+
+ final int EXCEPTION_LIMIT = 3;
+ handler.setLimit(EXCEPTION_LIMIT);
+ handler.afterPropertiesSet();
+
+ List throwables = new ArrayList() {
+ {
+ for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
+ add(new RuntimeException("below exception limit"));
+ }
+ }
+ };
+
+ throwables.add(new RuntimeException("above exception limit"));
+
+ RepeatContextSupport context = new RepeatContextSupport(null);
+
+ try {
+ for (Throwable throwable : throwables) {
+
+ handler.handleException(context, throwable);
+ assertTrue("exceptions up to limit are swallowed", true);
+
+ }
+ }
+ catch (RuntimeException expected) {
+ assertEquals("above exception limit", expected.getMessage());
+ }
+
+ // after reaching the limit, behaviour should be idempotent
+ try {
+ handler.handleException(context, new RuntimeException("foo"));
+ assertTrue("exceptions up to limit are swallowed", true);
+
+ }
+ catch (RuntimeException expected) {
+ assertEquals("foo", expected.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptorTests.java b/src/test/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptorTests.java
new file mode 100644
index 0000000..125ba55
--- /dev/null
+++ b/src/test/java/org/springframework/repeat/interceptor/RepeatOperationsInterceptorTests.java
@@ -0,0 +1,270 @@
+/*
+ * 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.repeat.interceptor;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.repeat.RepeatCallback;
+import org.springframework.repeat.RepeatException;
+import org.springframework.repeat.RepeatOperations;
+import org.springframework.repeat.RepeatStatus;
+import org.springframework.repeat.policy.SimpleCompletionPolicy;
+import org.springframework.repeat.support.RepeatTemplate;
+
+public class RepeatOperationsInterceptorTests extends TestCase {
+
+ private RepeatOperationsInterceptor interceptor;
+
+ private Service service;
+
+ private ServiceImpl target;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ interceptor = new RepeatOperationsInterceptor();
+ target = new ServiceImpl();
+ ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
+ factory.setInterfaces(new Class[] { Service.class });
+ factory.setTarget(target);
+ service = (Service) factory.getProxy();
+ }
+
+ public void testDefaultInterceptorSunnyDay() throws Exception {
+ ((Advised) service).addAdvice(interceptor);
+ service.service();
+ assertEquals(3, target.count);
+ }
+
+ public void testCompleteOnFirstInvocation() throws Exception {
+ ((Advised) service).addAdvice(interceptor);
+ target.setMaxService(0);
+ service.service();
+ assertEquals(1, target.count);
+ }
+
+ public void testSetTemplate() throws Exception {
+ final List