RESOLVED - issue BATCH-409: StatefulRetryStepFactoryBean needs to co-ordinate exception handler with retry policy

http://jira.springframework.org/browse/BATCH-409

Use a special exception handler that is aware of the retry.  Only works in the nested RepeatContext created by the ItemOrientedStep.
This commit is contained in:
dsyer
2008-03-06 19:05:19 +00:00
parent 0dec169d9e
commit eddb4653d7
4 changed files with 232 additions and 13 deletions

View File

@@ -23,6 +23,7 @@ import org.springframework.batch.execution.step.ItemOrientedStep;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
import org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
@@ -54,6 +55,8 @@ public class DefaultStepFactoryBean extends AbstractStepFactoryBean {
private RepeatTemplate stepOperations;
private SimpleLimitExceptionHandler exceptionHandler;
/**
* Set the commit interval.
*
@@ -107,6 +110,22 @@ public class DefaultStepFactoryBean extends AbstractStepFactoryBean {
return stepOperations;
}
/**
* Public setter for the SimpleLimitExceptionHandler.
* @param exceptionHandler the exceptionHandler to set
*/
public void setExceptionHandler(SimpleLimitExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
/**
* Protected getter for the {@link ExceptionHandler}.
* @return the {@link ExceptionHandler}
*/
protected ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
/**
* Public setter for the {@link TaskExecutor}. If this is set, then it will
* be used to execute the chunk processing inside the {@link Step}.
@@ -209,7 +228,8 @@ public class DefaultStepFactoryBean extends AbstractStepFactoryBean {
* will never re-appear after a rollback.
*/
itemProcessor.setItemSkipPolicy(new LimitCheckingItemSkipPolicy(skipLimit));
stepOperations.setExceptionHandler(new SimpleLimitExceptionHandler(skipLimit));
setExceptionHandler(new SimpleLimitExceptionHandler(skipLimit));
stepOperations.setExceptionHandler(getExceptionHandler());
step.setStepOperations(stepOperations);
}
else {

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.execution.step.support;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.exception.handler.ExceptionHandler;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.listener.RetryListenerSupport;
/**
* @author Dave Syer
*
*/
public class SimpleRetryExceptionHandler extends RetryListenerSupport implements ExceptionHandler {
/**
* Attribute key, whose existence signals an exhausted retry.
*/
private static final String EXHAUSTED = SimpleRetryExceptionHandler.class.getName() + ".RETRY_EXHAUSTED";
private RetryPolicy retryPolicy;
private ExceptionHandler exceptionHandler;
/**
* @param retryPolicy
* @param exceptionHandler
*/
public SimpleRetryExceptionHandler(RetryPolicy retryPolicy, ExceptionHandler exceptionHandler) {
this.retryPolicy = retryPolicy;
this.exceptionHandler = exceptionHandler;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.repeat.exception.handler.ExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext,
* java.lang.Throwable)
*/
public void handleException(RepeatContext context, Throwable throwable) throws RuntimeException {
// Only bother to check the delegate exception handler if we know that
// retry is exhausted
if (context.hasAttribute(EXHAUSTED)) {
exceptionHandler.handleException(context, throwable);
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.retry.RetryListener#close(org.springframework.batch.retry.RetryContext,
* org.springframework.batch.retry.RetryCallback, java.lang.Throwable)
*/
public void close(RetryContext context, RetryCallback callback, Throwable throwable) {
if (!retryPolicy.canRetry(context)) {
getRepeatContext().setAttribute(EXHAUSTED, "true");
}
}
/**
* Get the parent context (the retry is in an inner "chunk" loop and we want
* the exception to be handled at the outer "step" level).
* @return the {@link RepeatContext} that should hold the exhausted flag.
*/
private RepeatContext getRepeatContext() {
RepeatContext context = RepeatSynchronizationManager.getContext();
if (context.getParent() != null) {
return context.getParent();
}
return context;
}
}

View File

@@ -23,7 +23,6 @@ import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.backoff.BackOffPolicy;
@@ -49,15 +48,16 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean {
private ItemKeyGenerator itemKeyGenerator;
private ItemRecoverer itemRecoverer;
private int retryLimit;
private Class[] retryableExceptionClasses;
private BackOffPolicy backOffPolicy;
/**
* Public setter for the retry limit. Each item can be retried up to this limit.
* Public setter for the retry limit. Each item can be retried up to this
* limit.
* @param retryLimit the retry limit to set
*/
public void setRetryLimit(int retryLimit) {
@@ -114,16 +114,16 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean {
super.applyConfiguration(step);
if (retryLimit>0) {
if (retryLimit > 0) {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(retryLimit);
if (retryableExceptionClasses!=null) {
if (retryableExceptionClasses != null) {
retryPolicy.setRetryableExceptionClasses(retryableExceptionClasses);
}
// TODO: actually we need to co-ordinate the retry policy with the
// exception handler limit, so this is a hack for now.
getStepOperations().setExceptionHandler(new SimpleLimitExceptionHandler(Integer.MAX_VALUE));
// Co-ordinate the retry policy with the exception handler:
getStepOperations()
.setExceptionHandler(new SimpleRetryExceptionHandler(retryPolicy, getExceptionHandler()));
ItemReaderRetryCallback retryCallback = new ItemReaderRetryCallback(getItemReader(), itemKeyGenerator,
getItemWriter());
@@ -132,7 +132,7 @@ public class StatefulRetryStepFactoryBean extends DefaultStepFactoryBean {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(itemProviderRetryPolicy);
if (backOffPolicy!=null) {
if (backOffPolicy != null) {
retryTemplate.setBackOffPolicy(backOffPolicy);
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.execution.step.support;
import junit.framework.TestCase;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.context.RepeatContextSupport;
import org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.policy.AlwaysRetryPolicy;
import org.springframework.batch.retry.policy.NeverRetryPolicy;
/**
* @author Dave Syer
*
*/
public class SimpleRetryExceptionHandlerTests extends TestCase {
private RepeatContext context = new RepeatContextSupport(new RepeatContextSupport(null));
/*
* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
RepeatSynchronizationManager.register(context);
}
/* (non-Javadoc)
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
RepeatSynchronizationManager.clear();
}
/**
* Test method for
* {@link org.springframework.batch.execution.step.support.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}.
*/
public void testRethrowWhenRetryExhausted() {
RetryPolicy retryPolicy = new NeverRetryPolicy();
RuntimeException ex = new RuntimeException("foo");
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex);
// Then pretend to handle the exception in the parent context...
try {
handler.handleException(context.getParent(), ex);
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals(ex, e);
}
assertEquals(0, context.attributeNames().length);
// One for the retry exhausted flag and one for the counter in the delegate exception handler
assertEquals(2, context.getParent().attributeNames().length);
}
/**
* Test method for
* {@link org.springframework.batch.execution.step.support.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}.
*/
public void testNoRethrowWhenRetryNotExhausted() {
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
RuntimeException ex = new RuntimeException("foo");
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex);
// Then pretend to handle the exception in the parent context...
handler.handleException(context.getParent(), ex);
assertEquals(0, context.attributeNames().length);
assertEquals(0, context.getParent().attributeNames().length);
}
/**
* @param retryPolicy
* @param ex
* @return
*/
private SimpleRetryExceptionHandler getHandlerAfterRetry(RetryPolicy retryPolicy, RuntimeException ex) {
// Always rethrow if the retry is exhausted
SimpleRetryExceptionHandler handler = new SimpleRetryExceptionHandler(retryPolicy, new SimpleLimitExceptionHandler(0));
// Simulate a failed retry...
RetryContext retryContext = retryPolicy.open(null);
retryPolicy.registerThrowable(retryContext, ex);
handler.close(retryContext, null, ex);
return handler;
}
}