IN PROGRESS - issue BATCH-201: Move responsibility for deciding if an exception terminates a batch to ExceptionHandler

http://opensource.atlassian.com/projects/spring/browse/BATCH-201

Nearly finished - ExceptionHandler is the owner of abnormal termination.
This commit is contained in:
dsyer
2007-11-20 18:55:15 +00:00
parent 2b175e21e7
commit 07be8cf6d8
27 changed files with 442 additions and 378 deletions

View File

@@ -162,7 +162,7 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
try {
jobExecutorFacade.start(jobExecution);
} catch (NoSuchJobConfigurationException e) {
System.err.println("Shouldn't happen");
throw new IllegalStateException("Shouldn't happen");
}
}
}).start();

View File

@@ -18,7 +18,6 @@ package org.springframework.batch.execution.launch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import junit.framework.TestCase;
@@ -71,15 +70,15 @@ public class SimpleJobTests extends TestCase {
private ItemProvider provider;
private DefaultJobExecutor jobLifecycle = new DefaultJobExecutor();;
private DefaultJobExecutor jobExecutor = new DefaultJobExecutor();;
private DefaultStepExecutor stepLifecycle = new DefaultStepExecutor();
protected void setUp() throws Exception {
super.setUp();
jobLifecycle.setJobRepository(repository);
jobExecutor.setJobRepository(repository);
stepLifecycle.setRepository(repository);
jobLifecycle.setStepExecutorFactory(new StepExecutorFactory() {
jobExecutor.setStepExecutorFactory(new StepExecutorFactory() {
public StepExecutor getExecutor(StepConfiguration configuration) {
return stepLifecycle;
}
@@ -125,7 +124,7 @@ public class SimpleJobTests extends TestCase {
JobExecution jobExecutionContext = new JobExecution(job);
jobLifecycle.run(jobConfiguration, jobExecutionContext);
jobExecutor.run(jobConfiguration, jobExecutionContext);
assertEquals(BatchStatus.COMPLETED, job.getStatus());
assertEquals(3, processed.size());
assertTrue(processed.contains("foo"));
@@ -136,13 +135,14 @@ public class SimpleJobTests extends TestCase {
JobConfiguration jobConfiguration = new JobConfiguration();
JobIdentifier runtimeInformation = new SimpleJobIdentifier("real.job");
final List throwables = new ArrayList();
RepeatTemplate chunkOperations = new RepeatTemplate();
// Always handle the exception a check it is the right one...
chunkOperations.setExceptionHandler(new ExceptionHandler() {
public void handleExceptions(RepeatContext context, Collection throwables) {
assertEquals(1, throwables.size());
assertEquals("Try again Dummy!", ((Throwable) throwables.iterator().next()).getMessage());
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
throwables.add(throwable);
assertEquals("Try again Dummy!", throwable.getMessage());
}
});
stepLifecycle.setChunkOperations(chunkOperations);
@@ -170,8 +170,8 @@ public class SimpleJobTests extends TestCase {
jobConfiguration.addStep(step);
JobInstance job = repository.findOrCreateJob(jobConfiguration, runtimeInformation);
JobExecution jobExecutionContext = new JobExecution(job);
jobLifecycle.run(jobConfiguration, jobExecutionContext);
JobExecution jobExecution = new JobExecution(job);
jobExecutor.run(jobConfiguration, jobExecution);
assertEquals(BatchStatus.COMPLETED, job.getStatus());
assertEquals(0, processed.size());
@@ -196,7 +196,7 @@ public class SimpleJobTests extends TestCase {
JobInstance job = repository.findOrCreateJob(jobConfiguration, runtimeInformation);
JobExecution jobExecutionContext = new JobExecution(job);
try {
jobLifecycle.run(jobConfiguration, jobExecutionContext);
jobExecutor.run(jobConfiguration, jobExecutionContext);
fail("Expected RuntimeException");
}
catch (RuntimeException e) {

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.execution.step.simple;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import junit.framework.TestCase;
@@ -55,9 +54,9 @@ public class SimpleStepExecutorFactoryTests extends TestCase {
SimpleStepConfiguration configuration = new SimpleStepConfiguration();
final List list = new ArrayList();
configuration.setExceptionHandler(new ExceptionHandler() {
public void handleExceptions(RepeatContext context,
Collection throwables) throws RuntimeException {
list.addAll(throwables);
public void handleException(RepeatContext context,
Throwable throwable) throws RuntimeException {
list.add(throwable);
throw new RuntimeException("Oops");
}
});

View File

@@ -143,7 +143,7 @@ public class RestartableItemProviderTaskletTests extends TestCase {
assertNotNull(data);
// restore from restart data (see asserts in mock classes)
module.restoreFrom(data);
System.err.println(data.getProperties());
//System.err.println(data.getProperties());
}
}

View File

@@ -29,20 +29,22 @@ public interface RepeatInterceptor {
* 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.
* @param context
* the current batch context.
*/
void before(RepeatContext context);
/**
* Called by the framework after each item has been processed, including if
* the item processing results in an exception, in which case result is an
* Exception. This method is called as soon as the result is known, whereas
* {@link #onError(RepeatContext, Throwable)} is only guaranteed to be
* called at some time after the failure occurred.
* 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 item - ExitStatus in normal
* circumstances, but might be null or an Exception in abnormal cases.
* @param context
* the current batch context
* @param result
* the result of the callback item - ExitStatus in normal
* circumstances, but might be null or an Exception in abnormal
* cases.
*/
void after(RepeatContext context, Object result);
@@ -54,20 +56,23 @@ public interface RepeatInterceptor {
* enclosing batches (the whole job), the would need to use the parent
* context (recursively).
*
* @param context the current batch context
* @param context
* the current batch context
*/
void open(RepeatContext context);
/**
* Called at the end of a batch if any 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/>
* 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.
* @param context
* the current batch context
* @param e
* the error that was encountered in an item callback.
*/
void onError(RepeatContext context, Throwable e);
@@ -76,8 +81,8 @@ public interface RepeatInterceptor {
* completion (i.e. even after an exception). Implementers can use this
* method to clean up any resources.
*
* @param context the current batch context.
* @return TODO
* @param context
* the current batch context.
*/
void close(RepeatContext context);
}

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import org.springframework.batch.repeat.RepeatContext;
@@ -38,12 +37,12 @@ public class CompositeExceptionHandler implements ExceptionHandler {
* Iterate over the handlers delegating the call to each in turn. The chain
* ends if an exception is thrown.
*
* @see ExceptionHandler#handleExceptions(RepeatContext, Collection)
* @see ExceptionHandler#handleException(RepeatContext, Throwable)
*/
public void handleExceptions(RepeatContext context, Collection throwables) throws RuntimeException {
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
for (int i = 0; i < handlers.length; i++) {
ExceptionHandler handler = handlers[i];
handler.handleExceptions(context, throwables);
handler.handleException(context, throwable);
}
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.exception.RepeatException;
@@ -31,26 +30,25 @@ import org.springframework.batch.repeat.exception.RepeatException;
public class DefaultExceptionHandler implements ExceptionHandler {
/**
* Rethrow the first throwable in the collection's iterator. Wrap in a
* Rethrow the throwable in the collection's iterator. Wrap in a
* {@link RepeatException} if the first instance is not a
* {@link RuntimeException}.
*
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleExceptions(RepeatContext,
* java.util.Collection)
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleException(RepeatContext,
* Throwable)
*/
public void handleExceptions(RepeatContext context, Collection throwables) throws RuntimeException {
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
Throwable t = (Throwable) throwables.iterator().next();
rethrow(t);
rethrow(throwable);
}
/**
* Convenience method to rethrow the Throwable instance. Wraps it in a
* {@link RepeatException} if it is not a {@link RuntimeException}.
* {@link RepeatException} if it is not a {@link Exception}.
*
* @param throwable a Throwable.
* @throws RuntimeException if the throwable is a RuntimeException just
* @throws RuntimeException if the throwable is a {@link RuntimeException} just
* rethrow, otherwise wrap in a {@link RepeatException}
*/
public static void rethrow(Throwable throwable) throws RuntimeException {

View File

@@ -16,18 +16,16 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.RepeatContext;
/**
* Policy to allow strategies for rethrowing exceptions in the case of
* termination. Normally a {@link CompletionPolicy} will be used to decide
* whether to end a batch including when there is an exception, and the
* {@link ExceptionHandler} is used to distinguish between normal and abnormal
* ending. An abnormal ending would normally result in an
* {@link ExceptionHandler} throwing an exception.
* Handler to allow strategies for rethrowing 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 normally result in an
* {@link ExceptionHandler} throwing an exception. The caller will catch and
* rethrow it if necessary.
*
* @author Dave Syer
*
@@ -35,17 +33,21 @@ import org.springframework.batch.repeat.RepeatContext;
public interface ExceptionHandler {
/**
* Deal with a collection of throwables accumulated during a batch. The
* collection may consist of RuntimeExceptions or other unchecked
* exceptions.
* @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 throwables a collection of non-checked exceptions.
* Deal with a Throwable during a batch. The input might be
* RuntimeExceptions or other unchecked exceptions.
*
* @throws any or all of the exception types passed in, or a wrapped
* exception if one is an error or checked exception.
* @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 RuntimeException
* implementations must wrap and rethrow other Throwables
* appropriately.
*/
void handleExceptions(RepeatContext context, Collection throwables) throws RuntimeException;
void handleException(RepeatContext context, Throwable throwable)
throws RuntimeException;
}

View File

@@ -16,9 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.common.ExceptionClassifier;
@@ -85,33 +82,26 @@ public class LogOrRethrowExceptionHandler implements ExceptionHandler {
* Classify the throwables and decide whether to rethrow based on the
* result. The context is not used.
*
* @throws Exception
* @throws RuntimeException
*
* @see {@link ExceptionHandler#handleExceptions(RepeatContext, Collection)}
* @see {@link ExceptionHandler#handleException(RepeatContext, Throwable)}
*/
public void handleExceptions(RepeatContext context, Collection throwables)
public void handleException(RepeatContext context, Throwable throwable)
throws RuntimeException {
for (Iterator iter = throwables.iterator(); iter.hasNext();) {
Throwable throwable = (Throwable) iter.next();
Object key = exceptionClassifier.classify(throwable);
if (ERROR.equals(key)) {
logger.error("Exception encountered in batch repeat.",
throwable);
} else if (WARN.equals(key)) {
logger
.warn("Exception encountered in batch repeat.",
throwable);
} else if (DEBUG.equals(key) && logger.isDebugEnabled()) {
logger.debug("Exception encountered in batch repeat.",
throwable);
} else if (RETHROW.equals(key)) {
DefaultExceptionHandler.rethrow(throwable);
} else {
throw new IllegalStateException(
"Unclassified exception encountered. Did you mean to classifiy this as 'rethrow'?",
throwable);
}
Object key = exceptionClassifier.classify(throwable);
if (ERROR.equals(key)) {
logger.error("Exception encountered in batch repeat.", throwable);
} else if (WARN.equals(key)) {
logger.warn("Exception encountered in batch repeat.", throwable);
} else if (DEBUG.equals(key) && logger.isDebugEnabled()) {
logger.debug("Exception encountered in batch repeat.", throwable);
} else if (RETHROW.equals(key)) {
DefaultExceptionHandler.rethrow(throwable);
} else {
throw new IllegalStateException(
"Unclassified exception encountered. Did you mean to classifiy this as 'rethrow'?",
throwable);
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -40,7 +39,8 @@ import org.springframework.util.Assert;
*/
public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
protected final Log logger = LogFactory.getLog(RethrowOnThresholdExceptionHandler.class);
protected final Log logger = LogFactory
.getLog(RethrowOnThresholdExceptionHandler.class);
private ExceptionClassifier exceptionClassifier = new ExceptionClassifierSupport();
@@ -50,10 +50,11 @@ public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
/**
* Flag to indicate the the exception counters should be shared between
* sibling contexts in a nested batch. Default is false.
* sibling contexts in a nested batch. Default is false.
*
* @param useParent true if the parent context should be used to store the
* counters.
* @param useParent
* true if the parent context should be used to store the
* counters.
*/
public void setUseParent(boolean useParent) {
this.useParent = useParent;
@@ -74,16 +75,21 @@ public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
* are usually String literals, depending on the {@link ExceptionClassifier}
* implementation used.
*
* @param thresholds the threshold value map.
* @param thresholds
* the threshold value map.
*/
public void setThresholds(Map thresholds) {
for (Iterator iter = thresholds.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
if (!(entry.getKey() instanceof String)) {
logger.warn("Key in thresholds map is not of type String: " + entry.getKey());
logger.warn("Key in thresholds map is not of type String: "
+ entry.getKey());
}
Assert.state(entry.getValue() instanceof Integer, "Threshold value must be of type Integer. "
+ "Try using the value-type attribute if you care configuring this map via xml.");
Assert
.state(
entry.getValue() instanceof Integer,
"Threshold value must be of type Integer. "
+ "Try using the value-type attribute if you care configuring this map via xml.");
}
this.thresholds = thresholds;
}
@@ -104,28 +110,27 @@ public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
* Classify the throwables and decide whether to rethrow based on the
* result. The context is used to accumulate the number of exceptions of the
* same type according to the classifier.
* @throws Exception
*
* @see {@link ExceptionHandler#handleExceptions(RepeatContext, Collection)}
* @throws Exception
* @see {@link ExceptionHandler#handleException(RepeatContext, Throwable)}
*/
public void handleExceptions(RepeatContext context, Collection throwables) throws RuntimeException {
public void handleException(RepeatContext context, Throwable throwable)
throws RuntimeException {
for (Iterator iter = throwables.iterator(); iter.hasNext();) {
Throwable throwable = (Throwable) iter.next();
Object key = exceptionClassifier.classify(throwable);
RepeatContextCounter counter = getCounter(context, key);
counter.increment();
int count = counter.getCount();
Integer threshold = (Integer) thresholds.get(key);
if (threshold == null || count > threshold.intValue()) {
DefaultExceptionHandler.rethrow(throwable);
}
Object key = exceptionClassifier.classify(throwable);
RepeatContextCounter counter = getCounter(context, key);
counter.increment();
int count = counter.getCount();
Integer threshold = (Integer) thresholds.get(key);
if (threshold == null || count > threshold.intValue()) {
DefaultExceptionHandler.rethrow(throwable);
}
}
private RepeatContextCounter getCounter(RepeatContext context, Object key) {
String attribute = RethrowOnThresholdExceptionHandler.class + "." + key.toString();
String attribute = RethrowOnThresholdExceptionHandler.class + "."
+ key.toString();
// Creates a new counter and stores it in the correct context:
return new RepeatContextCounter(context, attribute, useParent);
}

View File

@@ -16,7 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collection;
import java.util.HashMap;
import org.springframework.batch.common.ExceptionClassifierSupport;
@@ -78,11 +77,11 @@ public class SimpleLimitExceptionHandler implements ExceptionHandler {
* @see #setType(Class)
* @see #setLimit(int)
*
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleExceptions(org.springframework.batch.repeat.RepeatContext,
* java.util.Collection)
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext,
* Throwable)
*/
public void handleExceptions(RepeatContext context, Collection throwables) throws RuntimeException {
delegate.handleExceptions(context, throwables);
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
delegate.handleException(context, throwable);
}
/**

View File

@@ -17,16 +17,16 @@
package org.springframework.batch.repeat.support;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatInterceptor;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.exception.RepeatException;
import org.springframework.batch.repeat.exception.handler.DefaultExceptionHandler;
import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
import org.springframework.batch.repeat.policy.DefaultResultCompletionPolicy;
@@ -87,7 +87,8 @@ public class RepeatTemplate implements RepeatOperations {
* @see DefaultExceptionHandler
* @see #setCompletionPolicy(CompletionPolicy)
*
* @param exceptionHandler the {@link ExceptionHandler} to use.
* @param exceptionHandler
* the {@link ExceptionHandler} to use.
*/
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
@@ -102,8 +103,10 @@ public class RepeatTemplate implements RepeatOperations {
*
* @see #setExceptionHandler(ExceptionHandler)
*
* @param terminationPolicy a TerminationPolicy.
* @throws IllegalArgumentException if the argument is null
* @param terminationPolicy
* a TerminationPolicy.
* @throws IllegalArgumentException
* if the argument is null
*/
public void setCompletionPolicy(CompletionPolicy terminationPolicy) {
Assert.notNull(terminationPolicy);
@@ -126,8 +129,7 @@ public class RepeatTemplate implements RepeatOperations {
// This works with an asynchronous TaskExecutor: the
// interceptors have to wait for the child processes.
result = executeInternal(callback);
}
finally {
} finally {
RepeatSynchronizationManager.clear();
if (outer != null) {
RepeatSynchronizationManager.register(outer);
@@ -141,10 +143,11 @@ public class RepeatTemplate implements RepeatOperations {
* Internal convenience method to loop over interceptors and batch
* callbacks.
*
* @param callback the callback to process each element of the loop.
* @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.
* for all the results from the callback.
*
*/
private ExitStatus executeInternal(final RepeatCallback callback) {
@@ -190,26 +193,41 @@ public class RepeatTemplate implements RepeatOperations {
// Check that we are still running...
if (running) {
logger.debug("Batch operation about to start at count=" + context.getStartedCount());
logger.debug("Batch operation about to start at count="
+ context.getStartedCount());
Object value = getNextResult(context, callback, state);
try {
// Save a throwable for later...
// TODO: hide this in internal state?
if (value instanceof Throwable) {
throwables.add(value);
}
result = getNextResult(context, callback, state);
executeAfterInterceptors(context, result);
executeAfterInterceptors(context, value);
// An exception alone is not sufficient grounds for not
// continuing
if(value instanceof ExitStatus){
result = (ExitStatus)value;
} catch (Throwable throwable) {
// An exception alone is not sufficient grounds for not
// continuing
try {
for (int i = interceptors.length; i-- > 0;) {
RepeatInterceptor interceptor = interceptors[i];
interceptor.onError(context, throwable);
// This is not an error - only log at debug
// level.
logger.debug("Exception intercepted ("
+ (i + 1) + " of "
+ interceptors.length + ")", throwable);
}
exceptionHandler.handleException(context,
throwable);
} catch (Throwable handled) {
throwables.add(handled);
}
}
// N.B. the order may be important here:
if (isComplete(context, value) || isMarkedComplete(context)) {
if (isComplete(context, result) || isMarkedComplete(context) || !throwables.isEmpty()) {
running = false;
}
}
@@ -230,32 +248,23 @@ public class RepeatTemplate implements RepeatOperations {
finally {
try {
for (Iterator iter = throwables.iterator(); iter.hasNext();) {
Throwable t = (Throwable) iter.next();
for (int i = interceptors.length; i-- > 0;) {
RepeatInterceptor interceptor = interceptors[i];
interceptor.onError(context, t);
// This is not an error - only log at debug level.
logger.debug("Exception intercepted (" + (i + 1) + " of " + interceptors.length + ")", t);
}
}
if (!throwables.isEmpty()) {
exceptionHandler.handleExceptions(context, throwables);
rethrow((Throwable) throwables.iterator().next());
}
}
finally {
} finally {
try {
for (int i = interceptors.length; i-- > 0;) {
RepeatInterceptor interceptor = interceptors[i];
interceptor.close(context);
}
}
finally {
} finally {
// TODO: extend this to the completion policy?
context.close();
}
}
}
@@ -264,6 +273,17 @@ public class RepeatTemplate implements RepeatOperations {
}
/**
* @param next
* @return
*/
private static Exception rethrow(Throwable next) throws RuntimeException {
if (next instanceof RuntimeException) {
throw (RuntimeException) next;
};
throw new RepeatException("Rethrowing exception that is no RuntimeException.", next);
}
/**
* 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
@@ -271,7 +291,8 @@ public class RepeatTemplate implements RepeatOperations {
* an accumulation of Throwable instances for processing at the end of the
* batch.
*
* @param context the current {@link RepeatContext}
* @param context
* the current {@link RepeatContext}
* @return a {@link RepeatInternalState} instance.
*/
protected RepeatInternalState createInternalState(RepeatContext context) {
@@ -282,21 +303,23 @@ public class RepeatTemplate implements RepeatOperations {
* Get the next completed result, possibly executing several callbacks until
* one finally finishes.
*
* @param context current BatchContext.
* @param callback the callback to execute.
* @param state maintained by the implementation.
* @return a finished result (possibly a Throwable instance if there is an
* error).
* @param context
* current BatchContext.
* @param callback
* the callback to execute.
* @param state
* maintained by the implementation.
* @return a finished result.
*
* @see {@link #isComplete(RepeatContext)}
*/
protected Object getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state) {
protected ExitStatus getNextResult(RepeatContext context,
RepeatCallback callback, RepeatInternalState state) throws Throwable {
try {
update(context);
return callback.doInIteration(context);
}
catch (Throwable t) {
return t;
} catch (Throwable t) {
throw t;
}
}
@@ -304,9 +327,10 @@ public class RepeatTemplate implements RepeatOperations {
* 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.
* @param state
* the internal state.
* @return true if {@link #canContinue(Object)} is true for all results
* retrieved.
* retrieved.
*/
protected boolean waitForResults(RepeatInternalState state) {
// no-op by default
@@ -314,17 +338,14 @@ public class RepeatTemplate implements RepeatOperations {
}
/**
* Check return value from batch operation. It's either RepeatStatus or
* Throwable at this point, so we just check the type and continue as
* expected.
* @param value the last callback result.
* @return true if the value is Throwable or RepeatStatus.CONTINUABLE.
* Check return value from batch operation.
*
* @param value
* the last callback result.
* @return true if the value is {@link ExitStatus#CONTINUABLE}.
*/
protected final boolean canContinue(Object value) {
if (value instanceof ExitStatus) {
return ((ExitStatus) value).isContinuable();
}
return true; // it's an exception
protected final boolean canContinue(ExitStatus value) {
return ((ExitStatus) value).isContinuable();
}
private boolean isMarkedComplete(RepeatContext context) {
@@ -342,16 +363,20 @@ public class RepeatTemplate implements RepeatOperations {
/**
* 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.
* @param context
* the current batch context.
* @param value
* the result of the callback to process.
*/
protected void executeAfterInterceptors(final RepeatContext context, Object value) {
protected void executeAfterInterceptors(final RepeatContext context,
Object value) {
// Don't re-throw exceptions here: let the exception handler deal with
// that...
if (value != null
&& ((value instanceof ExitStatus) && ((ExitStatus) value).isContinuable() || (value instanceof Throwable))) {
&& ((value instanceof ExitStatus)
&& ((ExitStatus) value).isContinuable() || (value instanceof Throwable))) {
for (int i = interceptors.length; i-- > 0;) {
RepeatInterceptor interceptor = interceptors[i];
interceptor.after(context, value);
@@ -365,12 +390,13 @@ public class RepeatTemplate implements RepeatOperations {
* Delegate to the {@link CompletionPolicy}.
*
* @see org.springframework.batch.repeat.CompletionPolicy#isComplete(RepeatContext,
* Object)
* Object)
*/
public boolean isComplete(RepeatContext context, Object result) {
boolean complete = completionPolicy.isComplete(context, result);
if (complete) {
logger.debug("Batch is complete according to policy and result value.");
logger
.debug("Batch is complete according to policy and result value.");
}
return complete;
}
@@ -383,7 +409,8 @@ public class RepeatTemplate implements RepeatOperations {
public boolean isComplete(RepeatContext context) {
boolean complete = completionPolicy.isComplete(context);
if (complete) {
logger.debug("Batch is complete according to policy alone not including result.");
logger
.debug("Batch is complete according to policy alone not including result.");
}
return complete;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.batch.repeat.support;
import java.util.List;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatOperations;
@@ -68,8 +69,10 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
/**
* Setter for task executor to be used to run the individual item callbacks.
*
* @param taskExecutor a TaskExecutor
* @throws IllegalArgumentException if the argument is null
* @param taskExecutor
* a TaskExecutor
* @throws IllegalArgumentException
* if the argument is null
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
Assert.notNull(taskExecutor);
@@ -84,10 +87,12 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
* method so there is no need to synchronize access.
*
* @see org.springframework.batch.repeat.support.AbstracBatchemplate#getNextResult(org.springframework.batch.item.RepeatContext,
* org.springframework.batch.repeat.RepeatCallback,
* org.springframework.batch.TerminationContext, java.util.List)
* org.springframework.batch.repeat.RepeatCallback,
* org.springframework.batch.TerminationContext, java.util.List)
*/
protected Object getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state) {
protected ExitStatus getNextResult(RepeatContext context,
RepeatCallback callback, RepeatInternalState state)
throws Throwable {
ExecutingRunnable runnable = null;
@@ -121,12 +126,14 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
Object result;
try {
result = queue.take().getResult();
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
result = e;
throw e;
}
return result;
if (result instanceof Throwable) {
throw (Throwable) result;
}
return (ExitStatus) result;
}
/**
@@ -152,22 +159,22 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
Object value;
try {
value = future.getResult();
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
// TODO: cancel batch?
Thread.currentThread().interrupt();
value = e;
}
if (value instanceof Throwable) {
state.getThrowables().add(value);
} else {
result = result && canContinue((ExitStatus) value);
executeAfterInterceptors(future.getContext(), value);
}
executeAfterInterceptors(future.getContext(), value);
result = result && canContinue(value);
}
Assert.state(futures.isEmpty(), "Future results should be empty at end of batch.");
Assert.state(futures.isEmpty(),
"Future results should be empty at end of batch.");
return result;
}
@@ -192,7 +199,8 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
Object result;
public ExecutingRunnable(RepeatCallback callback, RepeatContext context, ResultQueue queue) {
public ExecutingRunnable(RepeatCallback callback,
RepeatContext context, ResultQueue queue) {
super();
@@ -216,22 +224,19 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
public void run() {
try {
result = callback.doInIteration(context);
}
catch (Exception e) {
} catch (Exception e) {
result = e;
}
finally {
} finally {
queue.put(this);
}
}
// TODO: Should we support cancellations?
/**
* Get the result - never blocks because the queue manages waiting for
* the task to finish.
*
* @throws InterruptedException if the thread is interrupted.
* @throws InterruptedException
* if the thread is interrupted.
*/
public Object getResult() throws InterruptedException {
return result;
@@ -291,10 +296,10 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
synchronized (lock) {
count++;
}
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RepeatException("InterruptedException waiting for to acquire lock on input.");
throw new RepeatException(
"InterruptedException waiting for to acquire lock on input.");
}
}
@@ -314,10 +319,10 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
// Decrement the counter only when the result is collected.
count--;
}
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RepeatException("Interrupted while waiting for result.");
throw new RepeatException(
"Interrupted while waiting for result.");
}
return value;
}
@@ -338,14 +343,15 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
* until it is ready.
*
* @return the result.
* @throws InterruptedException if the thread is interrupted while
* waiting for the result.
* @throws InterruptedException
* if the thread is interrupted while waiting for the
* result.
* @throws IllegalStateException
*/
Object getResult() throws InterruptedException;
/**
* Get the context in which the result evaluation is execututing.
* Get the context in which the result evaluation is executing.
*
* @return the context of the result evaluation.
*/
@@ -361,7 +367,8 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate {
* N.B. when used with a thread pooled {@link TaskExecutor} it doesn't make
* sense for the throttle limit to be less than the thread pool size.
*
* @param throttleLimit the throttleLimit to set.
* @param throttleLimit
* the throttleLimit to set.
*/
public void setThrottleLimit(int throttleLimit) {
this.throttleLimit = throttleLimit;

View File

@@ -1,9 +0,0 @@
import junit.framework.TestCase;
public class SpringBatchTests extends TestCase {
public void testPlaceholder() throws Exception {
System.err.println("This is just a placeholder...keep an eye out for the real code coming soon.");
}
}

View File

@@ -17,8 +17,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import junit.framework.TestCase;
@@ -31,7 +29,7 @@ public class CompositeExceptionHandlerTests extends TestCase {
public void testNewHandler() throws Exception {
try {
handler.handleExceptions(null, Collections.singleton(new RuntimeException()));
handler.handleException(null, new RuntimeException());
}
catch (RuntimeException e) {
fail("Unexpected RuntimeException");
@@ -42,17 +40,17 @@ public class CompositeExceptionHandlerTests extends TestCase {
final List list = new ArrayList();
handler.setHandlers(new ExceptionHandler[] {
new ExceptionHandler() {
public void handleExceptions(RepeatContext context, Collection throwables) {
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
list.add("1");
}
},
new ExceptionHandler() {
public void handleExceptions(RepeatContext context, Collection throwables) {
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
list.add("2");
}
}
});
handler.handleExceptions(null, Collections.singleton(new RuntimeException()));
handler.handleException(null, new RuntimeException());
assertEquals(2, list.size());
assertEquals("1", list.get(0));
assertEquals("2", list.get(1));

View File

@@ -16,8 +16,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.Collections;
import junit.framework.TestCase;
import org.springframework.batch.repeat.RepeatContext;
@@ -30,7 +28,7 @@ public class DefaultExceptionHandlerTests extends TestCase {
public void testRuntimeException() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
fail("Expected RuntimeException");
} catch (RuntimeException e) {
assertEquals("Foo", e.getMessage());
@@ -39,7 +37,7 @@ public class DefaultExceptionHandlerTests extends TestCase {
public void testError() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
fail("Expected BatchException");
} catch (RepeatException e) {
assertEquals("Foo", e.getCause().getMessage());

View File

@@ -17,7 +17,6 @@
package org.springframework.batch.repeat.exception.handler;
import java.io.StringWriter;
import java.util.Collections;
import junit.framework.TestCase;
@@ -47,7 +46,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
public void testRuntimeException() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
fail("Expected RuntimeException");
} catch (RuntimeException e) {
assertEquals("Foo", e.getMessage());
@@ -56,7 +55,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
public void testError() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
fail("Expected BatchException");
} catch (RepeatException e) {
assertEquals("Foo", e.getCause().getMessage());
@@ -70,7 +69,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
}
});
// No exception...
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
assertNotNull(writer.toString());
}
@@ -81,7 +80,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
}
});
// No exception...
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
assertNotNull(writer.toString());
}
@@ -92,7 +91,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
}
});
// No exception...
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
assertNotNull(writer.toString());
}
@@ -103,7 +102,7 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
}
});
try {
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
assertTrue(e.getMessage().toLowerCase().indexOf("unclassified")>=0);

View File

@@ -34,7 +34,7 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
public void testRuntimeException() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
fail("Expected RuntimeException");
} catch (RuntimeException e) {
assertEquals("Foo", e.getMessage());
@@ -43,7 +43,7 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
public void testError() throws Exception {
try {
handler.handleExceptions(context, Collections.singleton(new Error("Foo")));
handler.handleException(context, new Error("Foo"));
fail("Expected BatchException");
} catch (RepeatException e) {
assertEquals("Foo", e.getCause().getMessage());
@@ -58,7 +58,7 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
});
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
// No exception...
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
RepeatContextCounter counter = new RepeatContextCounter(context, RethrowOnThresholdExceptionHandler.class + ".RuntimeException");
assertNotNull(counter);
assertEquals(1, counter.getCount());
@@ -72,9 +72,9 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
});
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
// No exception...
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
try {
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
@@ -100,11 +100,11 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
});
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
// No exception...
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
context = new RepeatContextSupport(parent);
try {
// No exception again - context is changed...
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
}
catch (RuntimeException e) {
fail("Unexpected Error");
@@ -120,10 +120,10 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
handler.setUseParent(true);
// No exception...
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
context = new RepeatContextSupport(parent);
try {
handler.handleExceptions(context, Collections.singleton(new RuntimeException("Foo")));
handler.handleException(context, new RuntimeException("Foo"));
fail("Expected Error");
}
catch (RuntimeException e) {

View File

@@ -17,14 +17,13 @@
package org.springframework.batch.repeat.exception.handler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.batch.io.exception.TransactionInvalidException;
import org.springframework.batch.repeat.context.RepeatContextSupport;
import org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler;
/**
* Unit tests for {@link SimpleLimitExceptionHandler}
@@ -36,144 +35,195 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
// object under test
private SimpleLimitExceptionHandler handler = new SimpleLimitExceptionHandler();
public void testInitializeWithNullContext() throws Exception {
try {
handler.handleExceptions(null, Collections.singleton(new RuntimeException("foo")));
handler.handleException(null, new RuntimeException("foo"));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// expected
}
}
public void testInitializeWithNullContextAndEmptyList() throws Exception {
public void testInitializeWithNullContextAndNullException()
throws Exception {
try {
handler.handleExceptions(null, Collections.EMPTY_LIST);
} catch (Exception e) {
fail("Unexpected IllegalArgumentException");
handler.handleException(null, null);
} catch (NullPointerException e) {
// expected;
}
}
/**
* Other than TransactionInvalidException should be rethrown, ignoring the exception limit.
* Other than TransactionInvalidException should be rethrown, ignoring the
* exception limit.
*
* @throws Exception
*/
public void testNormalExceptionThrown() {
List throwables = Collections.singletonList(new RuntimeException("foo"));
public void testNormalExceptionThrown() throws Exception {
Throwable throwable = new RuntimeException("foo");
final int MORE_THAN_ZERO = 1;
handler.setLimit(MORE_THAN_ZERO);
try{
handler.handleExceptions(new RepeatContextSupport(null), throwables);
try {
handler.handleException(new RepeatContextSupport(null), throwable);
fail("Exception swallowed.");
} catch (RuntimeException expected) {
assertTrue("Exception is rethrown, ignoring the exception limit",true);
assertSame(throwables.get(0), expected);
assertTrue("Exception is rethrown, ignoring the exception limit",
true);
assertSame(expected, throwable);
}
}
/**
* TransactionInvalidException should only be rethrown below the exception limit.
* TransactionInvalidException should only be rethrown below the exception
* limit.
*
* @throws Exception
*/
public void testLimitedExceptionTypeNotThrown() {
List throwables = Collections.singletonList(new RuntimeException("foo"));
public void testLimitedExceptionTypeNotThrown() throws Exception {
final int MORE_THAN_ZERO = 1;
handler.setLimit(MORE_THAN_ZERO);
handler.setType(RuntimeException.class);
try{
handler.handleExceptions(new RepeatContextSupport(null), throwables);
try {
handler.handleException(new RepeatContextSupport(null),
new RuntimeException("foo"));
} catch (RuntimeException expected) {
fail("Unexpected exception.");
}
}
/**
* TransactionInvalidException should only be rethrown below the exception limit.
* TransactionInvalidException should only be rethrown below the exception
* limit.
*
* @throws Exception
*/
public void testLimitedExceptionNotThrownFromSiblings() {
List throwables = Collections.singletonList(new RuntimeException("foo"));
public void testLimitedExceptionNotThrownFromSiblings() throws Exception {
Throwable throwable = new RuntimeException("foo");
final int MORE_THAN_ZERO = 1;
handler.setLimit(MORE_THAN_ZERO);
handler.setType(RuntimeException.class);
RepeatContextSupport parent = new RepeatContextSupport(null);
try{
try {
RepeatContextSupport context = new RepeatContextSupport(parent);
handler.handleExceptions(context, throwables);
handler.handleException(context, throwable);
context = new RepeatContextSupport(parent);
handler.handleExceptions(context, throwables);
handler.handleException(context, throwable);
} catch (RuntimeException expected) {
fail("Unexpected exception.");
}
}
/**
* TransactionInvalidException should only be rethrown below the exception limit.
* TransactionInvalidException should only be rethrown below the exception
* limit.
*
* @throws Exception
*/
public void testLimitedExceptionThrownFromSiblingsWhenUsingParent() {
List throwables = Collections.singletonList(new RuntimeException("foo"));
public void testLimitedExceptionThrownFromSiblingsWhenUsingParent()
throws Exception {
Throwable throwable = new RuntimeException("foo");
final int MORE_THAN_ZERO = 1;
handler.setLimit(MORE_THAN_ZERO);
handler.setType(RuntimeException.class);
handler.setUseParent(true);
RepeatContextSupport parent = new RepeatContextSupport(null);
try{
try {
RepeatContextSupport context = new RepeatContextSupport(parent);
handler.handleExceptions(context, throwables);
handler.handleException(context, throwable);
context = new RepeatContextSupport(parent);
handler.handleExceptions(context, throwables);
handler.handleException(context, throwable);
fail("Expected exception.");
} catch (RuntimeException expected) {
assertSame(throwables.get(0), expected);
assertSame(throwable, expected);
}
}
/**
* TransactionInvalidExceptions are swallowed until the exception limit is exceeded.
* After the limit is exceeded exceptions are rethrown as BatchCriticalExceptions
* TransactionInvalidExceptions are swallowed until the exception limit is
* exceeded. After the limit is exceeded exceptions are rethrown as
* BatchCriticalExceptions
*/
public void testExceptionThrownAboveLimit() {
public void testExceptionNotThrownBelowLimit() throws Exception {
final int EXCEPTION_LIMIT = 3;
handler.setLimit(EXCEPTION_LIMIT);
List throwables = new ArrayList() {{
for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
add(new TransactionInvalidException("below exception limit"));
List throwables = new ArrayList() {
{
for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
add(new TransactionInvalidException("below exception limit"));
}
}
}};
};
RepeatContextSupport context = new RepeatContextSupport(null);
try {
handler.handleExceptions(context, throwables);
assertTrue("exceptions up to limit are swallowed", true);
for (Iterator iterator = throwables.iterator(); iterator.hasNext();) {
Throwable throwable = (Throwable) iterator.next();
handler.handleException(context, throwable);
assertTrue("exceptions up to limit are swallowed", true);
}
} catch (RuntimeException unexpected) {
fail("exception rethrown although exception limit was not exceeded");
}
throwables = new ArrayList() {{
add(new TransactionInvalidException("above exception limit"));
}};
// after reaching the limit, behaviour should be idempotent
final int ARBITRARY_REPEAT_COUNT = 2;
for (int i = 0; i < ARBITRARY_REPEAT_COUNT; i++) {
try {
handler.handleExceptions(context, throwables);
fail("exception above exception limit swallowed");
} catch (TransactionInvalidException expected) {
assertSame(throwables.get(0), expected);
}
/**
* TransactionInvalidExceptions are swallowed until the exception limit is
* exceeded. After the limit is exceeded exceptions are rethrown as
* BatchCriticalExceptions
*/
public void testExceptionThrownAboveLimit() throws Exception {
final int EXCEPTION_LIMIT = 3;
handler.setLimit(EXCEPTION_LIMIT);
List throwables = new ArrayList() {
{
for (int i = 0; i < (EXCEPTION_LIMIT); i++) {
add(new TransactionInvalidException("below exception limit"));
}
}
};
throwables
.add(new TransactionInvalidException("above exception limit"));
RepeatContextSupport context = new RepeatContextSupport(null);
try {
for (Iterator iterator = throwables.iterator(); iterator.hasNext();) {
Throwable throwable = (Throwable) iterator.next();
handler.handleException(context, throwable);
assertTrue("exceptions up to limit are swallowed", true);
}
} catch (TransactionInvalidException expected) {
assertEquals("above exception limit", expected.getMessage());
}
// after reaching the limit, behaviour should be idempotent
try {
handler.handleException(context, new RuntimeException("foo"));
assertTrue("exceptions up to limit are swallowed", true);
} catch (RuntimeException expected) {
assertEquals("foo", expected.getMessage());
}
}
}

View File

@@ -21,10 +21,10 @@ import java.util.List;
import junit.framework.TestCase;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatInterceptor;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@@ -221,8 +221,8 @@ public class RepeatInterceptorTests extends TestCase {
// expected
}
assertEquals(0, count);
// The after is executed, then the on error...
assertEquals("[1, 2]", calls.toString());
// The after is not executed, if there is an error...
assertEquals("[2]", calls.toString());
}
public void testAsynchronousOnErrorInterceptorsPrecedence() throws Exception {
@@ -250,10 +250,14 @@ public class RepeatInterceptorTests extends TestCase {
}
catch (IllegalStateException e) {
// expected
assertEquals("Bogus", e.getMessage());
}
assertEquals(0, count);
// The after is executed, then the on error...
assertEquals(calls.lastIndexOf("1") + 1, calls.indexOf("2"));
assertEquals(fails.size() * 2, calls.size());
System.err.println(calls);
// The after is not executed on error...
assertEquals("2", calls.get(0));
assertEquals("2", calls.get(calls.size()-1));
assertFalse(calls.contains("1"));
assertEquals(fails.size(), calls.size());
}
}

View File

@@ -38,7 +38,7 @@ public abstract class AbstractTradeBatchTests extends TestCase {
Resource resource = new ClassPathResource("trades.csv", getClass());
protected TradeProcessor executor = new TradeProcessor();
protected TradeProcessor processor = new TradeProcessor();
protected TradeItemProvider provider;

View File

@@ -41,7 +41,7 @@ public class AsynchronousRepeatTests extends AbstractTradeBatchTests {
final String threadName = Thread.currentThread().getName();
final Set threadNames = new HashSet();
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, executor) {
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
assertNotSame(threadName, Thread.currentThread().getName());
threadNames.add(Thread.currentThread().getName());
@@ -53,7 +53,7 @@ public class AsynchronousRepeatTests extends AbstractTradeBatchTests {
template.iterate(callback);
// Shouldn't be necessary to wait:
// Thread.sleep(500);
assertEquals(NUMBER_OF_ITEMS, executor.count);
assertEquals(NUMBER_OF_ITEMS, processor.count);
assertTrue(threadNames.size() > 1);
}
@@ -72,7 +72,7 @@ public class AsynchronousRepeatTests extends AbstractTradeBatchTests {
final String threadName = Thread.currentThread().getName();
final Set threadNames = new HashSet();
final RepeatCallback stepCallback = new ItemProviderRepeatCallback(provider, executor) {
final RepeatCallback stepCallback = new ItemProviderRepeatCallback(provider, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
assertNotSame(threadName, Thread.currentThread().getName());
threadNames.add(Thread.currentThread().getName());
@@ -90,7 +90,7 @@ public class AsynchronousRepeatTests extends AbstractTradeBatchTests {
jobTemplate.iterate(jobCallback);
// Shouldn't be necessary to wait:
// Thread.sleep(500);
assertEquals(NUMBER_OF_ITEMS, executor.count);
assertEquals(NUMBER_OF_ITEMS, processor.count);
// Because of the throttling and queing internally to a TaskExecutor,
// more than one thread wil be used - the number used is (as of writing)
// one less than the throttle limit of the template.

View File

@@ -46,7 +46,7 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
public void testChunkedBatchWithTerminationPolicy() throws Exception {
RepeatTemplate repeatTemplate = new RepeatTemplate();
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, executor);
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, processor);
final RepeatTemplate chunkTemplate = new RepeatTemplate();
// The policy is resettable so we only have to resolve this dependency
@@ -62,7 +62,7 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
});
assertEquals(NUMBER_OF_ITEMS, executor.count);
assertEquals(NUMBER_OF_ITEMS, processor.count);
// The chunk executes 3 times because the last one
// returns false. We terminate the main batch when
// we encounter a partially empty chunk.
@@ -77,10 +77,10 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
*
* @throws Exception
*/
public void testAsynchronousChunkedBatchWithTerminationPolicy() throws Exception {
public void testAsynchronousChunkedBatchWithCompletionPolicy() throws Exception {
RepeatTemplate repeatTemplate = new RepeatTemplate();
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, executor);
final RepeatCallback callback = new ItemProviderRepeatCallback(provider, processor);
final TaskExecutorRepeatTemplate chunkTemplate = new TaskExecutorRepeatTemplate();
// The policy is resettable so we only have to resolve this dependency
@@ -97,9 +97,9 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
});
assertEquals(NUMBER_OF_ITEMS, executor.count);
assertEquals(NUMBER_OF_ITEMS, processor.count);
assertFalse(result.isContinuable());
assertEquals(3, count);
assertTrue("Expected at least 3 chunks but found: "+count, count>=3);
}
@@ -158,7 +158,7 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
}
};
chunker.reset();
template.iterate(new ItemProviderRepeatCallback(truncated, executor) {
template.iterate(new ItemProviderRepeatCallback(truncated, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
ExitStatus result = super.doInIteration(context);
@@ -173,7 +173,7 @@ public class ChunkedRepeatTests extends AbstractTradeBatchTests {
}
assertEquals(NUMBER_OF_ITEMS, executor.count);
assertEquals(NUMBER_OF_ITEMS, processor.count);
}

View File

@@ -17,7 +17,6 @@
package org.springframework.batch.repeat.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.batch.repeat.RepeatCallback;
@@ -45,8 +44,8 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
}
public void testExecute() throws Exception {
template.iterate(new ItemProviderRepeatCallback(provider, executor));
assertEquals(NUMBER_OF_ITEMS, executor.count);
template.iterate(new ItemProviderRepeatCallback(provider, processor));
assertEquals(NUMBER_OF_ITEMS, processor.count);
}
/**
@@ -58,9 +57,9 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
template.iterate(new ItemProviderRepeatCallback(provider, executor));
template.iterate(new ItemProviderRepeatCallback(provider, processor));
assertEquals(2, executor.count);
assertEquals(2, processor.count);
}
@@ -168,8 +167,9 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
final List list = new ArrayList();
template.setExceptionHandler(new ExceptionHandler() {
public void handleExceptions(RepeatContext context, Collection throwables) {
list.addAll(throwables);
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
list.add(throwable);
throw (RuntimeException) throwable;
}
});
@@ -197,11 +197,11 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
*/
public void testEarlyCompletionWithContext() throws Exception {
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, executor) {
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
ExitStatus result = super.doInIteration(context);
if (executor.count >= 2) {
if (processor.count >= 2) {
context.setCompleteOnly();
// If we return null the batch will terminate anyway
// without an exception...
@@ -211,7 +211,7 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
});
// 2 items were processed before completion signalled
assertEquals(2, executor.count);
assertEquals(2, processor.count);
// Not all items processed
assertTrue(result.isContinuable());
@@ -225,11 +225,11 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
*/
public void testEarlyCompletionWithContextTerminated() throws Exception {
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, executor) {
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
ExitStatus result = super.doInIteration(context);
if (executor.count >= 2) {
if (processor.count >= 2) {
context.setTerminateOnly();
// If we return null the batch will terminate anyway
// without an exception...
@@ -239,7 +239,7 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
});
// 2 items were processed before completion signalled
assertEquals(2, executor.count);
assertEquals(2, processor.count);
// Not all items processed
assertTrue(result.isContinuable());
@@ -321,8 +321,8 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
* @throws Exception
*/
public void testResult() throws Exception {
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, executor));
assertEquals(NUMBER_OF_ITEMS, executor.count);
ExitStatus result = template.iterate(new ItemProviderRepeatCallback(provider, processor));
assertEquals(NUMBER_OF_ITEMS, processor.count);
// We are complete - do not expect to be called again
assertFalse(result.isContinuable());
}
@@ -360,13 +360,13 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
ExitStatus result = ExitStatus.FINISHED;
try {
result = template.iterate(new ItemProviderRepeatCallback(provider, executor) {
result = template.iterate(new ItemProviderRepeatCallback(provider, processor) {
public ExitStatus doInIteration(RepeatContext context) throws Exception {
ExitStatus result = super.doInIteration(context);
if (executor.count >= 2) {
if (processor.count >= 2) {
context.setCompleteOnly();
throw new RuntimeException("Barf second try count=" + executor.count);
throw new RuntimeException("Barf second try count=" + processor.count);
}
return result;
}
@@ -379,7 +379,7 @@ public class SimpleRepeatTemplateTests extends AbstractTradeBatchTests {
}
// 2 items were processed before completion signalled
assertEquals(2, executor.count);
assertEquals(2, processor.count);
System.err.println(result);

View File

@@ -1,8 +1,5 @@
package org.springframework.batch.sample.exception.handler;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.repeat.RepeatContext;
@@ -11,21 +8,17 @@ import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
public class NflExceptionHandler implements ExceptionHandler {
private static final Log logger = LogFactory
.getLog(NflExceptionHandler.class);
public void handleExceptions(RepeatContext context, Collection throwables)
.getLog(NflExceptionHandler.class);
public void handleException(RepeatContext context, Throwable throwable)
throws RuntimeException {
Iterator it = throwables.iterator();
while(it.hasNext()){
Throwable t = (Throwable)it.next();
if(!(t instanceof NumberFormatException)){
throw new RuntimeException(t);
}
else{
logger.error("Number Format Exception!", t);
}
if (!(throwable instanceof NumberFormatException)) {
throw new RuntimeException(throwable);
} else {
logger.error("Number Format Exception!", throwable);
}
}
}

Binary file not shown.