From 07be8cf6d87095ecd32da709df389c3623a0b39b Mon Sep 17 00:00:00 2001 From: dsyer Date: Tue, 20 Nov 2007 18:55:15 +0000 Subject: [PATCH] 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. --- .../launch/SimpleJobExecutorFacadeTests.java | 2 +- .../execution/launch/SimpleJobTests.java | 22 +- .../SimpleStepExecutorFactoryTests.java | 7 +- .../RestartableItemProviderTaskletTests.java | 2 +- .../batch/repeat/RepeatInterceptor.java | 39 ++-- .../handler/CompositeExceptionHandler.java | 7 +- .../handler/DefaultExceptionHandler.java | 16 +- .../exception/handler/ExceptionHandler.java | 38 ++-- .../handler/LogOrRethrowExceptionHandler.java | 42 ++-- .../RethrowOnThresholdExceptionHandler.java | 51 ++--- .../handler/SimpleLimitExceptionHandler.java | 9 +- .../batch/repeat/support/RepeatTemplate.java | 161 +++++++++------ .../support/TaskExecutorRepeatTemplate.java | 73 ++++--- .../src/test/java/SpringBatchTests.java | 9 - .../CompositeExceptionHandlerTests.java | 10 +- .../handler/DefaultExceptionHandlerTests.java | 6 +- .../LogOrRethrowExceptionHandlerTests.java | 13 +- ...throwOnThresholdExceptionHandlerTests.java | 18 +- .../SimpleLimitExceptionHandlerTests.java | 190 +++++++++++------- .../interceptor/RepeatInterceptorTests.java | 16 +- .../support/AbstractTradeBatchTests.java | 2 +- .../support/AsynchronousRepeatTests.java | 8 +- .../repeat/support/ChunkedRepeatTests.java | 16 +- .../support/SimpleRepeatTemplateTests.java | 38 ++-- .../handler/NflExceptionHandler.java | 25 +-- src/site/ppt/Figures.ppt | Bin 67072 -> 90112 bytes src/site/ppt/RuntimeDependendencies.ppt | Bin 20480 -> 19968 bytes 27 files changed, 442 insertions(+), 378 deletions(-) delete mode 100644 infrastructure/src/test/java/SpringBatchTests.java diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java index e2727f3fb..32dc86418 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java @@ -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(); diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobTests.java index 41afbfb91..2630dff6a 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobTests.java @@ -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) { diff --git a/execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorFactoryTests.java b/execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorFactoryTests.java index 287d38ac1..328b0a5ed 100644 --- a/execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorFactoryTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/step/simple/SimpleStepExecutorFactoryTests.java @@ -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"); } }); diff --git a/execution/src/test/java/org/springframework/batch/execution/tasklet/RestartableItemProviderTaskletTests.java b/execution/src/test/java/org/springframework/batch/execution/tasklet/RestartableItemProviderTaskletTests.java index 56ee1d03c..e88e69ebf 100644 --- a/execution/src/test/java/org/springframework/batch/execution/tasklet/RestartableItemProviderTaskletTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/tasklet/RestartableItemProviderTaskletTests.java @@ -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()); } } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/RepeatInterceptor.java b/infrastructure/src/main/java/org/springframework/batch/repeat/RepeatInterceptor.java index a728374d6..97f5ec4ca 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/RepeatInterceptor.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/RepeatInterceptor.java @@ -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).
+ * Called when a repeat callback fails by throwing an exception. There will + * be one call to this method for each exception thrown during a repeat + * operation (e.g. a chunk).
* * There is no need to re-throw the exception here - that will be done by * the enclosing framework. * - * @param context the current batch context - * @param e the error that was encountered in an item callback. + * @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); } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandler.java index 28d14199d..9654dfd59 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandler.java @@ -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); } } } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandler.java index 6f30f2775..2798ad31c 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandler.java @@ -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 { diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/ExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/ExceptionHandler.java index 74e9d432e..e64271477 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/ExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/ExceptionHandler.java @@ -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; } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandler.java index 32fb2eca1..5c321f70f 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandler.java @@ -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); } } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandler.java index 1dbe85365..06594e077 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandler.java @@ -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); } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandler.java b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandler.java index f88ee8718..4c165b9f5 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandler.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandler.java @@ -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); } /** diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java b/infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java index 4a332e773..d8f1e236a 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java @@ -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; } diff --git a/infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java b/infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java index 08b927199..589330227 100644 --- a/infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java +++ b/infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java @@ -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; diff --git a/infrastructure/src/test/java/SpringBatchTests.java b/infrastructure/src/test/java/SpringBatchTests.java deleted file mode 100644 index d2be8c698..000000000 --- a/infrastructure/src/test/java/SpringBatchTests.java +++ /dev/null @@ -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."); - } -} diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandlerTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandlerTests.java index ad6dd02bd..662407e8e 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandlerTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/CompositeExceptionHandlerTests.java @@ -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)); diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandlerTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandlerTests.java index 43c7d8b2a..8617f5751 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandlerTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/DefaultExceptionHandlerTests.java @@ -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()); diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandlerTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandlerTests.java index 39795b534..2ccac73f3 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandlerTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/LogOrRethrowExceptionHandlerTests.java @@ -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); diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandlerTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandlerTests.java index 6b6cde816..82dd7c0c5 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandlerTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/RethrowOnThresholdExceptionHandlerTests.java @@ -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) { diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandlerTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandlerTests.java index be06da072..577af9623 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandlerTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/exception/handler/SimpleLimitExceptionHandlerTests.java @@ -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()); } } - } diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/interceptor/RepeatInterceptorTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/interceptor/RepeatInterceptorTests.java index 7da083f38..86b1eeea6 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/interceptor/RepeatInterceptorTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/interceptor/RepeatInterceptorTests.java @@ -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()); } } diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/support/AbstractTradeBatchTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/support/AbstractTradeBatchTests.java index cff2f2c57..cae0434e2 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/support/AbstractTradeBatchTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/support/AbstractTradeBatchTests.java @@ -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; diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/support/AsynchronousRepeatTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/support/AsynchronousRepeatTests.java index 8b01b7f83..92af4fdbb 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/support/AsynchronousRepeatTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/support/AsynchronousRepeatTests.java @@ -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. diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/support/ChunkedRepeatTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/support/ChunkedRepeatTests.java index 24d6f0092..6e99d0aa2 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/support/ChunkedRepeatTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/support/ChunkedRepeatTests.java @@ -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); } diff --git a/infrastructure/src/test/java/org/springframework/batch/repeat/support/SimpleRepeatTemplateTests.java b/infrastructure/src/test/java/org/springframework/batch/repeat/support/SimpleRepeatTemplateTests.java index 7e406da9e..7b8044caf 100644 --- a/infrastructure/src/test/java/org/springframework/batch/repeat/support/SimpleRepeatTemplateTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/repeat/support/SimpleRepeatTemplateTests.java @@ -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); diff --git a/samples/src/main/java/org/springframework/batch/sample/exception/handler/NflExceptionHandler.java b/samples/src/main/java/org/springframework/batch/sample/exception/handler/NflExceptionHandler.java index b115002d6..3a9bbba24 100644 --- a/samples/src/main/java/org/springframework/batch/sample/exception/handler/NflExceptionHandler.java +++ b/samples/src/main/java/org/springframework/batch/sample/exception/handler/NflExceptionHandler.java @@ -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); } + } } diff --git a/src/site/ppt/Figures.ppt b/src/site/ppt/Figures.ppt index b7ad146a53a2d91887273275d3a6f9c7002ea2b6..e3c0c354e8c9b5fb493891cf536d47b4aec5c9b8 100644 GIT binary patch delta 13055 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 ZZ~ceD9yC3QEhKMP^5)3r&Tuw>{Wsyw?6v>^ delta 5361 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 diff --git a/src/site/ppt/RuntimeDependendencies.ppt b/src/site/ppt/RuntimeDependendencies.ppt index 588a2366fb821c78b2ce8077026885df2b693d06..292fd5b74bf03e2b8ba594cd114598cdecadfea6 100644 GIT binary patch delta 1169 zcmZWoZD>v_*T z=bo-U*7XM)6r5F{m9J-W@jD*TlYA#Tx=EzOcH5)mvh^LIYq(4F@Eih=R$y33nI(1c4E31WpL+hit8^`WVs8gin?zOk5mM?A-<69?q|ozI@V|OlImm zIvguSl4SJ`#~rIMHp>A)FD`esbE3zc-QvL(GqzW)!yjBh8UNiiD)zW;|@c zz&4^d;R|C!%3p%bJ`XDDDiOGR{Zmbw?7O8N76;OwWA%8dAdrjKV#Gf~w-oj<(R8#^ z!C%xU{^<_dIc$&n>Lo?s+338&-zORuLB_!=F3X8YBX8LhaHqE@Fj-iH5 zWxw1d>{2zf`u?N4(eZl0KMU^rWT!su^Ds7sYH_ICZ~-`h8xv&9z;4)S2fc(45hqxT zJx)wVBbX?j7IA;}``Yn-;H*ejn9g{~NpW}Ubwy7GElkAkWy5RrSEWCnHI_Mc389L z@i&fC{xgodtJ?C8fYktoH>;kMGEfb<5l(`Ylxzg4Xd#~-+*hdKSY(Ghg+xp%PzI|8~CiC8xWfPu6|Q_(UPr)t_MV z@m_sZYnis|=YFb>QJZUKY8y*mWs)0zHOouH?1=4HG%*D delta 1678 zcmaKrUrbY19LLW&J*6!$TdvSjtQ9M^w5J8q0K=>r=1c%l;)r7+WUxO`!>~%wYz$+& zAuMEZL4H}5C1h!03@(~zyfJ%Qd|2Z4Fdz0Oi{oWFVt6pxmn|quy5G5{oRK}O&F6l< z=XbupUwcpP%`VVvj^5;q?gTp|zF!?EU&>qk3Z&B`ALR>MJeA47Y^eSt4e6>F1565jI!W&Lx{J$~n{ah07k zn&Eauz%jwy}&2VjVK-HT-)RdaG${gkV z^)U_~8gclkD(D!qh8|KwYoO7)kN-VT+`sG%GPG;{JwSak($4zoxo4=j{lI5!)9QP! zPBO+nmZt<+BLQ~*f9Ag7O4&CCmi)5QT=RP=?FVB^By`$Ch?}td3DjER%scS}eMlIM zo7nQzZipuY%xqcXsh6$$>w~4h4W8>RE|#}m<*6&<+?)&AC?EaVDjzYQZhKDsBRD9; zO4U#t*j`46Ny_qjfrTRQ_4XJX3_aq3GOMQ+PKI0I;*PJl)4QQCS9gPV1~(MG*m;w$ zF6qJp-nn2c`pm15N0cu-)myEw68!}3#}1Tah&}&10U;hjrZNuw@ifbD_&)wjp6$1Z z2EI~(=C4GXJe{V7?X)Hx#{W+KbO)s1(|Es{*rdH(5AQa92DM$;TTPD{KF8*h)dPO(tevQh>Y%lVfMxa;pqQ0t8 zIhpN81~c+vAy5_ls7Ev^D8s|Z+ZushF^oE-Q8}4Cj*Msodc|?nV?{E@ia{ApBV!tY zUXeyUsZlwZJ&U}r5$F|XQO{^pPG-*`@`e=xRSe4uZU{1|Q9&7=LZ&T5QP