Add test cases and re-org packages

This commit is contained in:
Dave Syer
2010-12-21 17:57:32 +00:00
parent 2215dddc69
commit 9dd0346f31
171 changed files with 11895 additions and 152 deletions

26
pom.xml
View File

@@ -74,6 +74,24 @@
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -85,6 +103,12 @@
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
@@ -128,7 +152,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--forkMode>pertest</forkMode-->
<!--forkMode>pertest</forkMode -->
<includes>
<include>**/*Tests.java</include>
</includes>

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<C, T> implements Classifier<C, T> {
private Classifier<C, String> router;
private Classifier<String, T> 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<C, String> router, Classifier<String, T> matcher) {
super();
this.router = router;
this.matcher = matcher;
}
/**
* A convenience method for creating a pattern matching classifier for the
* matcher component.
*
* @param map maps pattern keys with wildcards to output values
*/
public void setMatcherMap(Map<String, T> map) {
this.matcher = new PatternMatchingClassifier<T>(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. <br/>
*
* @param delegate the delegate object used to create a router classifier
*/
public void setRouterDelegate(Object delegate) {
this.router = new ClassifierAdapter<C,String>(delegate);
}
/**
* Classify the input and map to a String, then take that and put it into a
* pattern matcher to match to an output value.
*/
public T classify(C classifiable) {
return matcher.classify(router.classify(classifiable));
}
}

View File

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

View File

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

View File

@@ -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<C, T> implements Classifier<C, T> {
private MethodInvoker invoker;
private Classifier<C, T> 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<C, T> delegate) {
classifier = delegate;
}
public void setDelegate(Classifier<C, T> 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.<C, T> getMethodInvokerForSingleArgument(delegate);
}
Assert.state(invoker != null, "No single argument public method with or without "
+ "@Classifier was found in delegate of type " + delegate.getClass());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public T classify(C classifiable) {
if (classifier != null) {
return classifier.classify(classifiable);
}
return (T) invoker.invokeMethod(classifiable);
}
}

View File

@@ -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<C, T> implements Classifier<C, T> {
* Always returns the default value. This is the main extension point for
* subclasses, so it must be able to classify null.
*
* @see org.springframework.commons.classify.Classifier#classify(Object)
* @see org.springframework.classify.Classifier#classify(Object)
*/
public T classify(C throwable) {
return defaultValue;

View File

@@ -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<S> {
private Map<String, S> map = new HashMap<String, S>();
private List<String> sorted = new ArrayList<String>();
/**
* Initialize a new {@link PatternMatcher} with a map of patterns to values
* @param map a map from String patterns to values
*/
public PatternMatcher(Map<String, S> map) {
super();
this.map = map;
// Sort keys to start with the most specific
sorted = new ArrayList<String>(map.keySet());
Collections.sort(sorted, new Comparator<String>() {
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:<br>
* '*' means zero or more characters<br>
* '?' means one and only one character
*
* @param pattern pattern to match against. Must not be <code>null</code>.
* @param str string which must be matched against the pattern. Must not be
* <code>null</code>.
* @return <code>true</code> if the string matches against the pattern, or
* <code>false</code> 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;
}
/**
* <p>
* This method takes a String key and a map from Strings to values of any
* type. During processing, the method will identify the most specific key
* in the map that matches the line. Once the correct is identified, its
* value is returned. Note that if the map contains the wildcard string "*"
* as a key, then it will serve as the "default" case, matching every line
* that does not match anything else.
*
* <p>
* If no matching prefix is found, a {@link IllegalStateException} will be
* thrown.
*
* <p>
* Null keys are not allowed in the map.
*
* @param line An input string
* @return the value whose prefix matches the given line
*/
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;
}
}

View File

@@ -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<T> implements Classifier<String, T> {
private PatternMatcher<T> values;
/**
* Default constructor. Use the setter or the other constructor to create a
* sensible classifier, otherwise all inputs will cause an exception.
*/
public PatternMatchingClassifier() {
this(new HashMap<String, T>());
}
/**
* Create a classifier from the provided map. The keys are patterns, using
* '?' as a single character and '*' as multi-character wildcard.
*
* @param values
*/
public PatternMatchingClassifier(Map<String, T> values) {
super();
this.values = new PatternMatcher<T>(values);
}
/**
* A map from pattern to value
* @param values the pattern map to set
*/
public void setPatternMap(Map<String, T> values) {
this.values = new PatternMatcher<T>(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;
}
}

View File

@@ -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.
*

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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 {
}

View File

@@ -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 <em>single</em> Method on the
* given Class that contains the specified annotation type.
*
* @author Mark Fisher
*/
public class AnnotationMethodResolver implements MethodResolver {
private Class<? extends Annotation> annotationType;
/**
* Create a MethodResolver for the specified Method-level annotation type
*/
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 <em>single</em> Method on the Class of the given candidate object
* that contains the annotation type for which this resolver is searching.
*
* @param candidate the instance whose Class will be checked for the
* annotation
*
* @return a single matching Method instance or <code>null</code> if the
* candidate's Class contains no Methods with the specified annotation
*
* @throws IllegalArgumentException if more than one Method has the
* specified annotation
*/
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 <em>single</em> Method on the given Class that contains the
* annotation type for which this resolver is searching.
*
* @param clazz the Class instance to check for the annotation
*
* @return a single matching Method instance or <code>null</code> if the
* Class contains no Methods with the specified annotation
*
* @throws IllegalArgumentException if more than one Method has the
* specified annotation
*/
public Method findMethod(final Class<?> clazz) {
Assert.notNull(clazz, "class must not be null");
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
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();
}
}

View File

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

View File

@@ -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<Method> annotatedMethod = new AtomicReference<Method>();
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 <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object target) {
final AtomicReference<Method> methodHolder = new AtomicReference<Method>();
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);
}
}

View File

@@ -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 <code>null</code> if no Method matching this
* resolver's criteria can be found.
*
* @throws IllegalArgumentException if more than one Method defined on the
* given candidate's Class matches this resolver's criteria
*/
Method findMethod(Object candidate) throws IllegalArgumentException;
/**
* Find a <em>single</em> Method on the given Class that matches this
* resolver's criteria.
*
* @param clazz the Class instance on which to search for a Method
*
* @return a single Method or <code>null</code> if no Method matching this
* resolver's criteria can be found.
*
* @throws IllegalArgumentException if more than one Method defined on the
* given Class matches this resolver's criteria
*/
Method findMethod(Class<?> clazz);
}

View File

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

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.repeat;
/**
* Interface for batch completion policies, to enable batch operations to
* strategise normal completion conditions. Stateful implementations of batch
* iterators should <em>only</em> 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 <em>not</em> 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 <em>once</em> 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);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.repeat;
package org.springframework.repeat;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.repeat;
package org.springframework.repeat;
import org.springframework.core.AttributeAccessor;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.repeat;
package org.springframework.repeat;
import org.springframework.core.NestedRuntimeException;

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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).<br/>
*
* 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);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.repeat;
package org.springframework.repeat;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.repeat;
package org.springframework.repeat;
public enum RepeatStatus {

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat callback concerns.
</p>
</body>
</html>

View File

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

View File

@@ -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<String, Set<Runnable>> callbacks = new HashMap<String, Set<Runnable>>();
/**
* 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<Runnable> set = callbacks.get(name);
if (set == null) {
set = new HashSet<Runnable>();
callbacks.put(name, set);
}
set.add(callback);
}
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.repeat.RepeatContext#close()
*/
public void close() {
List<RuntimeException> errors = new ArrayList<RuntimeException>();
Set<Map.Entry<String, Set<Runnable>>> copy;
synchronized (callbacks) {
copy = new HashSet<Map.Entry<String, Set<Runnable>>>(callbacks.entrySet());
}
for (Map.Entry<String, Set<Runnable>> 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);
}
}

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat context concerns.
</p>
</body>
</html>

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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;
}

View File

@@ -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<Throwable, Level> exceptionClassifier = new ClassifierSupport<Throwable, Level>(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<Throwable, Level> 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;
}
}
}

View File

@@ -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<Throwable, IntegerHolder>() {
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<Class<? extends Throwable>, Integer> thresholds) {
Map<Class<? extends Throwable>, IntegerHolder> typeMap = new HashMap<Class<? extends Throwable>, IntegerHolder>();
for (Entry<Class<? extends Throwable>, Integer> entry : thresholds.entrySet()) {
typeMap.put(entry.getKey(), new IntegerHolder(entry.getValue()));
}
exceptionClassifier = new SubclassClassifier<Throwable, IntegerHolder>(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;
}
}
}

View File

@@ -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<Class<? extends Throwable>> exceptionClasses = Collections
.<Class<? extends Throwable>> singleton(Exception.class);
private Collection<Class<? extends Throwable>> fatalExceptionClasses = Collections
.<Class<? extends Throwable>> 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<Class<? extends Throwable>, Integer> thresholds = new HashMap<Class<? extends Throwable>, 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<Class<? extends Throwable>> 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<Class<? extends Throwable>> fatalExceptionClasses) {
this.fatalExceptionClasses = fatalExceptionClasses;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat exception handler concerns.
</p>
</body>
</html>

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat aop concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<RepeatListener> listeners = new ArrayList<RepeatListener>();
/**
* 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);
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat interceptor concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat concerns.
</p>
</body>
</html>

View File

@@ -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 <code>true</code>, 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();
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<RepeatContext> list = new ArrayList<RepeatContext>();
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<RepeatContext> contexts) {
super(context);
this.contexts = contexts.toArray(new RepeatContext[contexts.size()]);
this.policies = CompositeCompletionPolicy.this.policies;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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).<br/>
*
* 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;
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat policy concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<Throwable> getThrowables();
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<Throwable> throwables = new HashSet<Throwable>();
/* (non-Javadoc)
* @see org.springframework.batch.repeat.support.BatchInternalState#getThrowables()
*/
public Collection<Throwable> getThrowables() {
return throwables;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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<RepeatContext> contextHolder = new ThreadLocal<RepeatContext>();
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();
}
}
}

View File

@@ -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.<br/>
*
* 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.<br/>
*
* 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<RepeatListener> list = new ArrayList<RepeatListener>(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<Throwable> throwables = state.getThrowables();
// Keep a separate list of exceptions we handled that need to be
// rethrown
Collection<Throwable> deferred = new ArrayList<Throwable>();
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<Throwable> 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);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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();
}

View File

@@ -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<ResultHolder> {
// Accumulation of result objects as they finish.
private final BlockingQueue<ResultHolder> 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<ResultHolder>(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. <br/>
* <br/>
* Release result immediately if:
* <ul>
* <li>There is a result that is continuable.</li>
* </ul>
* Otherwise block if either:
* <ul>
* <li>There is no result (as per contract of {@link ResultQueue}).</li>
* <li>The number of results is less than the number expected.</li>
* </ul>
* Error if either:
* <ul>
* <li>Not expecting.</li>
* <li>Interrupted.</li>
* </ul>
*
* @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<ResultHolder> {
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;
}
}
}

View File

@@ -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<T> {
/**
* 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}.<br/><br/>
* 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();
}

View File

@@ -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.<br/>
*
* 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).<br/>
*
* 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).<br/>
*
* @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<ResultHolder> 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<ResultHolder> 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<ResultHolder> queue;
private volatile RepeatStatus result;
private volatile Throwable error;
public ExecutingRunnable(RepeatCallback callback, RepeatContext context, ResultQueue<ResultHolder> 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<ResultHolder> 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<ResultHolder> getResultQueue() {
return results;
}
}
}

View File

@@ -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<T> implements ResultQueue<T> {
// Accumulation of result objects as they finish.
private final BlockingQueue<T> 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<T>();
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;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of repeat support concerns.
</p>
</body>
</html>

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry;
package org.springframework.retry;
public class ExhaustedRetryException extends RetryException {

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry;
package org.springframework.retry;
import org.springframework.core.AttributeAccessor;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry;
package org.springframework.retry;
import org.springframework.core.NestedRuntimeException;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry;
package org.springframework.retry;
/**

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry;
package org.springframework.retry;
public class TerminatedRetryException extends RetryException {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry.backoff;
package org.springframework.retry.backoff;
/**
* @author Rob Harrop

View File

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

View File

@@ -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}.
* <p/> Implementations are expected to be thread-safe and should be designed
* for concurrent access. Configuration for each implementation is also expected
* to be thread-safe but need not be suitable for high load concurrent access.
@@ -29,7 +29,7 @@ import org.springframework.commons.retry.RetryContext;
* {@link BackOffContext} that can be used to track state through subsequent
* back off invocations. <p/> Each back off process is handled via a call to
* {@link #backOff}. The
* {@link org.springframework.commons.retry.support.RetryTemplate} will pass in
* {@link org.springframework.retry.support.RetryTemplate} will pass in
* the corresponding {@link BackOffContext} object created by the call to
* {@link #start}.
*

View File

@@ -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;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry.backoff;
package org.springframework.retry.backoff;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry.backoff;
package org.springframework.retry.backoff;
/**

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of retry backoff concerns.
</p>
</body>
</html>

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of retry context concerns.
</p>
</body>
</html>

View File

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

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.commons.retry.interceptor;
package org.springframework.retry.interceptor;
/**

View File

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

View File

@@ -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;
/**

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of retry aop concerns.
</p>
</body>
</html>

View File

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

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of retry interceptor concerns.
</p>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Infrastructure implementations of retry concerns.
</p>
</body>
</html>

View File

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

View File

@@ -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<RetryContext> list = new ArrayList<RetryContext>();
@@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More