For a Batch workshop that I gave last week, I've created a subclass of the RestartableItemProviderTasklet that lets you use an ExceptionHandler to handle exceptions thrown by the InputProvider and skips the item if the exception is handled successfuly. I'm including it here for others that need the same functionality using 1.0M2. The usage would be something like this:
Code:
<bean id="myTasklet" class="com.i21.batchsample.support.ExceptionHandlingItemProviderTasklet">
<property name="itemProvider" ref="myProvider"/>
<property name="itemProcessor" ref="myProcessor"/>
<property name="exceptionHandler">
<bean class="org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler">
<property name="type" value="org.springframework.batch.io.exception.ValidationException"/>
<property name="limit" value="2"/>
</bean>
</property>
</bean>
This will skip at most two invalid items before failing the job on ValidationExceptions. Here's the code:
Code:
package com.i21.batchsample.support;
import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.execution.tasklet.ItemProviderProcessTasklet;
import org.springframework.batch.execution.tasklet.RestartableItemProviderTasklet;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.exception.handler.DefaultExceptionHandler;
import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.util.ClassUtils;
/**
* Deals with Exceptions of a given type from the InputProvider, so processing
* can continue normally, by using a configured ExceptionHandler.
* This class should be considered as a workaround: the final version of Spring Batch
* will definitely make it easier to deal with Exceptions thrown by the InputProvider
* in an easier and more straightforward manner.
*
* @author Joris Kuipers
*
*/
public class ExceptionHandlingItemProviderTasklet extends RestartableItemProviderTasklet {
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
private boolean skipOnHandling = true;
private final Log log = LogFactory.getLog(getClass());
/**
* The ExceptionHandler to use to handle the Exception from the ItemProvider.
* Defaults to a DefaultExceptionHandler.
* @param exceptionHandler
*/
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
/**
* Whether to call skip() on the InputProvider after handling an Exception.
* Defaults to true.
* @param skipOnHandling
*/
public void setSkipOnHandling(boolean skipOnHandling) {
this.skipOnHandling = skipOnHandling;
}
public ExitStatus execute() throws Exception {
try {
return super.execute();
} catch (Exception e) {
RepeatContext context = RepeatSynchronizationManager.getContext();
if (originatesFromProvider(context, e)) {
exceptionHandler.handleExceptions(context, Collections.singleton(e));
// the Exception was handled successfully
log.info("Handled " + ClassUtils.getShortName(e.getClass())
+ ": " + e.getMessage());
if (skipOnHandling && this.itemProvider instanceof Skippable) {
((Skippable) itemProvider).skip();
}
return ExitStatus.CONTINUABLE;
}
// rethrow all other Exceptions
throw e;
}
}
/**
* Determines if an Exception thrown by calling super.execute() from our execute()
* implementation originated in the ItemProvider (and not the ItemProcessor).
* Depends on the implementation detail that there won't be an item
* in the RepeatContext in that case.
* @param context current RepeatContext
* @param e the thrown Exception, can be used by overriding methods
* @return true if it came from the ItemProvider
*/
protected boolean originatesFromProvider(RepeatContext context, Exception e) {
// unfortunately, ItemProviderProcessTasklet.ITEM_KEY is private. We use the same definition.
return !context.hasAttribute(ItemProviderProcessTasklet.class + ".ITEM");
}
}