BATCH-220: Rationalise the exception classifiers so they can be used in a retry
This commit is contained in:
@@ -60,7 +60,7 @@ public class SimpleRetryExceptionHandler extends RetryListenerSupport implements
|
||||
this.retryPolicy = retryPolicy;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
this.fatalExceptionClassifier = new BinaryExceptionClassifier();
|
||||
fatalExceptionClassifier.setExceptionClasses(fatalExceptionClasses);
|
||||
fatalExceptionClassifier.setTypes(fatalExceptionClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,7 @@ public class SimpleRetryExceptionHandler extends RetryListenerSupport implements
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
// Only bother to check the delegate exception handler if we know that
|
||||
// retry is exhausted
|
||||
if (!fatalExceptionClassifier.isDefault(throwable) || context.hasAttribute(EXHAUSTED)) {
|
||||
if (fatalExceptionClassifier.classify(throwable) || context.hasAttribute(EXHAUSTED)) {
|
||||
exceptionHandler.handleException(context, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,9 @@ import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.RetryContextCache;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
import org.springframework.batch.support.SubclassExceptionClassifier;
|
||||
|
||||
/**
|
||||
* Factory bean for step that provides options for configuring skip behaviour.
|
||||
@@ -214,17 +212,11 @@ public class SkipLimitStepFactoryBean<T, S> extends SimpleStepFactoryBean<T, S>
|
||||
simpleRetryPolicy.setFatalExceptionClasses(fatalExceptionClasses);
|
||||
|
||||
ExceptionClassifierRetryPolicy classifierRetryPolicy = new ExceptionClassifierRetryPolicy();
|
||||
SubclassExceptionClassifier exceptionClassifier = new SubclassExceptionClassifier();
|
||||
HashMap<Class<? extends Throwable>, String> exceptionTypeMap = new HashMap<Class<? extends Throwable>, String>();
|
||||
HashMap<Class<? extends Throwable>, RetryPolicy> exceptionTypeMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
|
||||
for (Class<? extends Throwable> cls : retryableExceptionClasses) {
|
||||
exceptionTypeMap.put(cls, "retry");
|
||||
exceptionTypeMap.put(cls, simpleRetryPolicy);
|
||||
}
|
||||
exceptionClassifier.setTypeMap(exceptionTypeMap);
|
||||
HashMap<String, RetryPolicy> retryPolicyMap = new HashMap<String, RetryPolicy>();
|
||||
retryPolicyMap.put("retry", simpleRetryPolicy);
|
||||
retryPolicyMap.put("default", new NeverRetryPolicy());
|
||||
classifierRetryPolicy.setPolicyMap(retryPolicyMap);
|
||||
classifierRetryPolicy.setExceptionClassifier(exceptionClassifier);
|
||||
classifierRetryPolicy.setPolicyMap(exceptionTypeMap);
|
||||
retryPolicy = classifierRetryPolicy;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,15 +18,11 @@ package org.springframework.batch.core.step.skip;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.file.FlatFileParseException;
|
||||
import org.springframework.batch.support.BinaryExceptionClassifier;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
import org.springframework.batch.support.SubclassExceptionClassifier;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -58,30 +54,20 @@ import org.springframework.batch.support.SubclassExceptionClassifier;
|
||||
*/
|
||||
public class LimitCheckingItemSkipPolicy implements ItemSkipPolicy {
|
||||
|
||||
/**
|
||||
* Label for classifying skippable exceptions.
|
||||
*/
|
||||
private static final String SKIP = "skip";
|
||||
|
||||
/**
|
||||
* Label for classifying fatal exceptions - these are never skipped.
|
||||
*/
|
||||
private static final String NEVER_SKIP = "neverSkip";
|
||||
|
||||
private final int skipLimit;
|
||||
|
||||
private Classifier<Throwable,String> exceptionClassifier;
|
||||
private final Classifier<Throwable, Boolean> fatalExceptionClassifier;
|
||||
|
||||
private final Classifier<Throwable, Boolean> skippableExceptionClassifier;
|
||||
|
||||
/**
|
||||
* Convenience constructor that assumes all exception types are skippable
|
||||
* and none are fatal.
|
||||
* @param skipLimit the number of exceptions allowed to skip
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public LimitCheckingItemSkipPolicy(int skipLimit) {
|
||||
this(skipLimit,
|
||||
new HashSet<Class<? extends Throwable>>(){{add(Exception.class);}},
|
||||
Collections.EMPTY_LIST);
|
||||
this(skipLimit, Collections.<Class<? extends Throwable>> singleton(Exception.class), Collections
|
||||
.<Class<? extends Throwable>> emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,18 +78,11 @@ public class LimitCheckingItemSkipPolicy implements ItemSkipPolicy {
|
||||
* (non-critical)
|
||||
* @param fatalExceptions exception classes that should never be skipped
|
||||
*/
|
||||
public LimitCheckingItemSkipPolicy(int skipLimit, Collection<Class<? extends Throwable>> skippableExceptions, Collection<Class<? extends Throwable>>fatalExceptions) {
|
||||
public LimitCheckingItemSkipPolicy(int skipLimit, Collection<Class<? extends Throwable>> skippableExceptions,
|
||||
Collection<Class<? extends Throwable>> fatalExceptions) {
|
||||
this.skipLimit = skipLimit;
|
||||
SubclassExceptionClassifier exceptionClassifier = new SubclassExceptionClassifier();
|
||||
Map<Class<? extends Throwable>, String> typeMap = new HashMap<Class<? extends Throwable>, String>();
|
||||
for (Class<? extends Throwable> throwable : skippableExceptions) {
|
||||
typeMap.put(throwable, SKIP);
|
||||
}
|
||||
for (Class<? extends Throwable> throwable : fatalExceptions) {
|
||||
typeMap.put(throwable, NEVER_SKIP);
|
||||
}
|
||||
exceptionClassifier.setTypeMap(typeMap);
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
skippableExceptionClassifier = new BinaryExceptionClassifier(skippableExceptions);
|
||||
fatalExceptionClassifier = new BinaryExceptionClassifier(fatalExceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +95,10 @@ public class LimitCheckingItemSkipPolicy implements ItemSkipPolicy {
|
||||
* {@link SkipLimitExceededException} will be thrown.
|
||||
*/
|
||||
public boolean shouldSkip(Throwable t, int skipCount) {
|
||||
if (exceptionClassifier.classify(t).equals(NEVER_SKIP)) {
|
||||
if (fatalExceptionClassifier.classify(t)) {
|
||||
return false;
|
||||
}
|
||||
if (exceptionClassifier.classify(t).equals(SKIP)) {
|
||||
if (skippableExceptionClassifier.classify(t)) {
|
||||
if (skipCount < skipLimit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import static org.easymock.EasyMock.createMock;
|
||||
import static org.easymock.EasyMock.replay;
|
||||
import static org.easymock.EasyMock.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -74,11 +74,8 @@ public class CompositeItemProcessListenerTests {
|
||||
|
||||
@Test
|
||||
public void testSetListeners() throws Exception {
|
||||
compositeListener.setListeners(new ArrayList<ItemProcessListener<? super Object, ? super Object>>() {
|
||||
{
|
||||
add(listener);
|
||||
}
|
||||
});
|
||||
compositeListener.setListeners(Collections
|
||||
.<ItemProcessListener<? super Object, ? super Object>> singletonList(listener));
|
||||
listener.beforeProcess(null);
|
||||
replay(listener);
|
||||
compositeListener.beforeProcess(null);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.springframework.batch.core.repository.dao;
|
||||
|
||||
import java.util.HashMap;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -17,32 +18,30 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
/**
|
||||
* Tests for {@link ExecutionContextDao} implementations.
|
||||
*/
|
||||
public abstract class AbstractExecutionContextDaoTests extends AbstractTransactionalJUnit4SpringContextTests{
|
||||
|
||||
public abstract class AbstractExecutionContextDaoTests extends AbstractTransactionalJUnit4SpringContextTests {
|
||||
|
||||
private ExecutionContextDao dao;
|
||||
|
||||
|
||||
private JobExecution jobExecution = new JobExecution(new JobInstance(1L, new JobParameters(), "jobName"), 1L);
|
||||
|
||||
|
||||
private StepExecution stepExecution = new StepExecution("stepName", jobExecution, 1L);
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
dao = getExecutionContextDao();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Configured {@link ExecutionContextDao} implementation ready for use.
|
||||
* @return Configured {@link ExecutionContextDao} implementation ready for
|
||||
* use.
|
||||
*/
|
||||
protected abstract ExecutionContextDao getExecutionContextDao();
|
||||
|
||||
@Transactional @Test
|
||||
|
||||
@Transactional
|
||||
@Test
|
||||
public void testSaveAndFindContext() {
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(new HashMap<String, Object>() {
|
||||
{
|
||||
put("key", "value");
|
||||
}
|
||||
});
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(Collections.<String, Object> singletonMap("key", "value"));
|
||||
jobExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(jobExecution);
|
||||
|
||||
@@ -50,9 +49,10 @@ public abstract class AbstractExecutionContextDaoTests extends AbstractTransacti
|
||||
assertEquals(ctx, retrieved);
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
@Transactional
|
||||
@Test
|
||||
public void testSaveAndFindEmptyContext() {
|
||||
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext();
|
||||
jobExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(jobExecution);
|
||||
@@ -61,14 +61,12 @@ public abstract class AbstractExecutionContextDaoTests extends AbstractTransacti
|
||||
assertEquals(ctx, retrieved);
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
@Transactional
|
||||
@Test
|
||||
public void testUpdateContext() {
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(new HashMap<String, Object>() {
|
||||
{
|
||||
put("key", "value");
|
||||
}
|
||||
});
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(Collections
|
||||
.<String, Object> singletonMap("key", "value"));
|
||||
jobExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(jobExecution);
|
||||
|
||||
@@ -79,25 +77,23 @@ public abstract class AbstractExecutionContextDaoTests extends AbstractTransacti
|
||||
assertEquals(ctx, retrieved);
|
||||
assertEquals(7, retrieved.getLong("longKey"));
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
|
||||
@Transactional
|
||||
@Test
|
||||
public void testSaveAndFindStepContext() {
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(new HashMap<String, Object>() {
|
||||
{
|
||||
put("key", "value");
|
||||
}
|
||||
});
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(Collections.<String, Object> singletonMap("key", "value"));
|
||||
stepExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(stepExecution);
|
||||
|
||||
ExecutionContext retrieved = dao.getExecutionContext(stepExecution);
|
||||
assertEquals(ctx, retrieved);
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
|
||||
@Transactional
|
||||
@Test
|
||||
public void testSaveAndFindEmptyStepContext() {
|
||||
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext();
|
||||
stepExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(stepExecution);
|
||||
@@ -106,14 +102,11 @@ public abstract class AbstractExecutionContextDaoTests extends AbstractTransacti
|
||||
assertEquals(ctx, retrieved);
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
@Transactional
|
||||
@Test
|
||||
public void testUpdateStepContext() {
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(new HashMap<String, Object>() {
|
||||
{
|
||||
put("key", "value");
|
||||
}
|
||||
});
|
||||
|
||||
ExecutionContext ctx = new ExecutionContext(Collections.<String, Object> singletonMap("key", "value"));
|
||||
stepExecution.setExecutionContext(ctx);
|
||||
dao.persistExecutionContext(stepExecution);
|
||||
|
||||
@@ -124,10 +117,11 @@ public abstract class AbstractExecutionContextDaoTests extends AbstractTransacti
|
||||
assertEquals(ctx, retrieved);
|
||||
assertEquals(7, retrieved.getLong("longKey"));
|
||||
}
|
||||
|
||||
@Transactional @Test
|
||||
public void testStoreInteger(){
|
||||
|
||||
|
||||
@Transactional
|
||||
@Test
|
||||
public void testStoreInteger() {
|
||||
|
||||
ExecutionContext ec = new ExecutionContext();
|
||||
ec.put("intValue", new Integer(343232));
|
||||
stepExecution.setExecutionContext(ec);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@@ -57,20 +57,15 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}
|
||||
* .
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)} .
|
||||
*/
|
||||
public void testRethrowWhenRetryExhausted() throws Throwable {
|
||||
|
||||
RetryPolicy retryPolicy = new NeverRetryPolicy();
|
||||
RuntimeException ex = new RuntimeException("foo");
|
||||
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex,
|
||||
new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Error.class);
|
||||
}
|
||||
});
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, Collections
|
||||
.<Class<? extends Throwable>> singleton(Error.class));
|
||||
|
||||
// Then pretend to handle the exception in the parent context...
|
||||
try {
|
||||
@@ -89,20 +84,15 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}
|
||||
* .
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)} .
|
||||
*/
|
||||
public void testNoRethrowWhenRetryNotExhausted() throws Throwable {
|
||||
|
||||
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
|
||||
RuntimeException ex = new RuntimeException("foo");
|
||||
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex,
|
||||
new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Error.class);
|
||||
}
|
||||
});
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, Collections
|
||||
.<Class<? extends Throwable>> singleton(Error.class));
|
||||
|
||||
// Then pretend to handle the exception in the parent context...
|
||||
handler.handleException(context.getParent(), ex);
|
||||
@@ -113,20 +103,15 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}
|
||||
* .
|
||||
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)} .
|
||||
*/
|
||||
public void testRethrowWhenFatal() throws Throwable {
|
||||
|
||||
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
|
||||
RuntimeException ex = new RuntimeException("foo");
|
||||
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex,
|
||||
new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(RuntimeException.class);
|
||||
}
|
||||
});
|
||||
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, Collections
|
||||
.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
|
||||
// Then pretend to handle the exception in the parent context...
|
||||
try {
|
||||
|
||||
@@ -108,7 +108,7 @@ public class SimpleStepFactoryBeanTests {
|
||||
public void testSimpleConcurrentJob() throws Exception {
|
||||
|
||||
job.setSteps(new ArrayList<Step>());
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory("foo", "bar");
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory("foo", "bar");
|
||||
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
factory.setThrottleLimit(1);
|
||||
|
||||
@@ -127,14 +127,14 @@ public class SimpleStepFactoryBeanTests {
|
||||
@Test
|
||||
public void testSimpleJobWithItemListeners() throws Exception {
|
||||
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
|
||||
factory.setItemWriter(new ItemWriter<String>() {
|
||||
public void write(List<? extends String> data) throws Exception {
|
||||
throw new RuntimeException("Error!");
|
||||
}
|
||||
});
|
||||
factory.setListeners(new StepListener[] { new ItemListenerSupport<String,String>() {
|
||||
factory.setListeners(new StepListener[] { new ItemListenerSupport<String, String>() {
|
||||
@Override
|
||||
public void onReadError(Exception ex) {
|
||||
listened.add(ex);
|
||||
@@ -154,7 +154,8 @@ public class SimpleStepFactoryBeanTests {
|
||||
try {
|
||||
job.execute(jobExecution);
|
||||
fail("Expected RuntimeException");
|
||||
} catch (RuntimeException e) {
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
// expected
|
||||
assertEquals("Error!", e.getMessage());
|
||||
}
|
||||
@@ -168,7 +169,7 @@ public class SimpleStepFactoryBeanTests {
|
||||
|
||||
@Test
|
||||
public void testExceptionTerminates() throws Exception {
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
factory.setBeanName("exceptionStep");
|
||||
factory.setItemWriter(new ItemWriter<String>() {
|
||||
public void write(List<? extends String> data) throws Exception {
|
||||
@@ -192,9 +193,11 @@ public class SimpleStepFactoryBeanTests {
|
||||
|
||||
@Test
|
||||
public void testExceptionHandler() throws Exception {
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory(new String[] { "foo", "bar", "spam" });
|
||||
factory.setBeanName("exceptionStep");
|
||||
factory.setExceptionHandler(new SimpleLimitExceptionHandler(1));
|
||||
SimpleLimitExceptionHandler exceptionHandler = new SimpleLimitExceptionHandler(1);
|
||||
exceptionHandler.afterPropertiesSet();
|
||||
factory.setExceptionHandler(exceptionHandler);
|
||||
factory.setItemWriter(new ItemWriter<String>() {
|
||||
int count = 0;
|
||||
|
||||
@@ -208,9 +211,9 @@ public class SimpleStepFactoryBeanTests {
|
||||
job.setSteps(Collections.singletonList((Step) step));
|
||||
|
||||
JobExecution jobExecution = repository.createJobExecution(job, new JobParameters());
|
||||
|
||||
|
||||
job.execute(jobExecution);
|
||||
|
||||
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
}
|
||||
|
||||
@@ -219,7 +222,7 @@ public class SimpleStepFactoryBeanTests {
|
||||
String[] items = new String[] { "1", "2", "3", "4", "5", "6", "7" };
|
||||
int commitInterval = 3;
|
||||
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory(items);
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory(items);
|
||||
class CountingChunkListener implements ChunkListener {
|
||||
int beforeCount = 0;
|
||||
|
||||
@@ -259,7 +262,7 @@ public class SimpleStepFactoryBeanTests {
|
||||
*/
|
||||
@Test
|
||||
public void testCommitIntervalMustBeGreaterThanZero() throws Exception {
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory("foo");
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory("foo");
|
||||
// nothing wrong here
|
||||
factory.getObject();
|
||||
|
||||
@@ -281,7 +284,7 @@ public class SimpleStepFactoryBeanTests {
|
||||
*/
|
||||
@Test
|
||||
public void testCommitIntervalAndCompletionPolicyBothSet() throws Exception {
|
||||
SimpleStepFactoryBean<String,String> factory = getStepFactory("foo");
|
||||
SimpleStepFactoryBean<String, String> factory = getStepFactory("foo");
|
||||
|
||||
// but exception expected after setting commit interval and completion
|
||||
// policy
|
||||
@@ -296,9 +299,9 @@ public class SimpleStepFactoryBeanTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SimpleStepFactoryBean<String,String> getStepFactory(String... args) throws Exception {
|
||||
SimpleStepFactoryBean<String,String> factory = new SimpleStepFactoryBean<String,String>();
|
||||
|
||||
private SimpleStepFactoryBean<String, String> getStepFactory(String... args) throws Exception {
|
||||
SimpleStepFactoryBean<String, String> factory = new SimpleStepFactoryBean<String, String>();
|
||||
|
||||
List<String> items = new ArrayList<String>();
|
||||
items.addAll(Arrays.asList(args));
|
||||
@@ -311,5 +314,5 @@ public class SimpleStepFactoryBeanTests {
|
||||
factory.setBeanName("stepName");
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -46,12 +46,9 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
|
||||
private SkipLimitStepFactoryBean<String, String> factory = new SkipLimitStepFactoryBean<String, String>();
|
||||
|
||||
private Collection<Class<? extends Throwable>> skippableExceptions = new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(SkippableException.class);
|
||||
add(SkippableRuntimeException.class);
|
||||
}
|
||||
};
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<Class<? extends Throwable>> skippableExceptions = new HashSet<Class<? extends Throwable>>(Arrays
|
||||
.<Class<? extends Throwable>> asList(SkippableException.class, SkippableRuntimeException.class));
|
||||
|
||||
private SkipReaderStub reader = new SkipReaderStub();
|
||||
|
||||
@@ -139,11 +136,8 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
*/
|
||||
@Test
|
||||
public void testFatalException() throws Exception {
|
||||
factory.setFatalExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(FatalRuntimeException.class);
|
||||
}
|
||||
});
|
||||
factory.setFatalExceptionClasses(Collections
|
||||
.<Class<? extends Throwable>> singleton(FatalRuntimeException.class));
|
||||
factory.setItemWriter(new SkipWriterStub() {
|
||||
public void write(List<? extends String> items) {
|
||||
throw new FatalRuntimeException("Ouch!");
|
||||
@@ -204,11 +198,7 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
|
||||
factory.setSkipLimit(3);
|
||||
factory.setItemReader(reader);
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Exception.class);
|
||||
}
|
||||
});
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
@@ -246,17 +236,13 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
|
||||
factory.setSkipLimit(3);
|
||||
factory.setItemReader(reader);
|
||||
factory.setListeners(new StepListener[] { new SkipListenerSupport<String,String>() {
|
||||
factory.setListeners(new StepListener[] { new SkipListenerSupport<String, String>() {
|
||||
@Override
|
||||
public void onSkipInRead(Throwable t) {
|
||||
throw new RuntimeException("oops");
|
||||
}
|
||||
} });
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Exception.class);
|
||||
}
|
||||
});
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
@@ -287,17 +273,13 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
|
||||
factory.setSkipLimit(3);
|
||||
factory.setItemReader(reader);
|
||||
factory.setListeners(new StepListener[] { new SkipListenerSupport<String,String>() {
|
||||
factory.setListeners(new StepListener[] { new SkipListenerSupport<String, String>() {
|
||||
@Override
|
||||
public void onSkipInWrite(String item, Throwable t) {
|
||||
throw new RuntimeException("oops");
|
||||
}
|
||||
} });
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Exception.class);
|
||||
}
|
||||
});
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
@@ -377,11 +359,7 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
|
||||
@Test
|
||||
public void testDefaultSkipPolicy() throws Exception {
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Exception.class);
|
||||
}
|
||||
});
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
factory.setSkipLimit(1);
|
||||
List<String> items = Arrays.asList(new String[] { "a", "b", "c" });
|
||||
ItemReader<String> provider = new ListItemReader<String>(items) {
|
||||
@@ -411,17 +389,13 @@ public class SkipLimitStepFactoryBeanTests {
|
||||
@Test
|
||||
public void testSkipOverLimitOnReadWithAllSkipsAtEnd() throws Exception {
|
||||
|
||||
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"), Arrays
|
||||
.asList(StringUtils.commaDelimitedListToStringArray("6,12,13,14,15")));
|
||||
reader = new SkipReaderStub(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"),
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("6,12,13,14,15")));
|
||||
|
||||
factory.setCommitInterval(5);
|
||||
factory.setSkipLimit(3);
|
||||
factory.setItemReader(reader);
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(Exception.class);
|
||||
}
|
||||
});
|
||||
factory.setSkippableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(Exception.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.batch.core.step.tasklet;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -16,36 +17,38 @@ import org.springframework.batch.repeat.ExitStatus;
|
||||
public class ConfigurableSystemProcessExitCodeMapperTests {
|
||||
|
||||
private ConfigurableSystemProcessExitCodeMapper mapper = new ConfigurableSystemProcessExitCodeMapper();
|
||||
|
||||
|
||||
/**
|
||||
* Regular usage scenario - mapping adheres to injected values
|
||||
*/
|
||||
@Test
|
||||
public void testMapping() {
|
||||
Map<Object, ExitStatus> mappings = new HashMap<Object, ExitStatus>() {{
|
||||
put(0, ExitStatus.FINISHED);
|
||||
put(1, ExitStatus.FAILED);
|
||||
put(2, ExitStatus.CONTINUABLE);
|
||||
put(3, ExitStatus.NOOP);
|
||||
put(4, ExitStatus.UNKNOWN);
|
||||
put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.UNKNOWN);
|
||||
}};
|
||||
|
||||
Map<Object, ExitStatus> mappings = new HashMap<Object, ExitStatus>() {
|
||||
{
|
||||
put(0, ExitStatus.FINISHED);
|
||||
put(1, ExitStatus.FAILED);
|
||||
put(2, ExitStatus.CONTINUABLE);
|
||||
put(3, ExitStatus.NOOP);
|
||||
put(4, ExitStatus.UNKNOWN);
|
||||
put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.UNKNOWN);
|
||||
}
|
||||
};
|
||||
|
||||
mapper.setMappings(mappings);
|
||||
|
||||
//check explicitly defined values
|
||||
|
||||
// check explicitly defined values
|
||||
for (Map.Entry<Object, ExitStatus> entry : mappings.entrySet()) {
|
||||
if (entry.getKey().equals(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY)) continue;
|
||||
|
||||
if (entry.getKey().equals(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY))
|
||||
continue;
|
||||
|
||||
int exitCode = (Integer) entry.getKey();
|
||||
assertSame(entry.getValue(), mapper.getExitStatus(exitCode));
|
||||
}
|
||||
|
||||
//check the else clause
|
||||
assertSame(mappings.get(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY),
|
||||
mapper.getExitStatus(5));
|
||||
|
||||
// check the else clause
|
||||
assertSame(mappings.get(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY), mapper.getExitStatus(5));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Else clause is required in the injected map - setter checks its presence.
|
||||
*/
|
||||
@@ -59,12 +62,10 @@ public class ConfigurableSystemProcessExitCodeMapperTests {
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
Map<Object, ExitStatus> containsElse = new HashMap<Object, ExitStatus>() {{
|
||||
put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.FAILED);
|
||||
}};
|
||||
|
||||
Map<Object, ExitStatus> containsElse = Collections.<Object, ExitStatus> singletonMap(
|
||||
ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.FAILED);
|
||||
// no error expected now
|
||||
mapper.setMappings(containsElse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ExecutionContext implements Serializable {
|
||||
* @param map Initial contents of context.
|
||||
*/
|
||||
public ExecutionContext(Map<String, Object> map) {
|
||||
this.map = map;
|
||||
this.map = new HashMap<String, Object>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,15 +33,15 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class RepeatContextCounter {
|
||||
|
||||
private String countKey;
|
||||
final private String countKey;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether the count is stored at the level of the parent
|
||||
* context, or just local to the current context. Default value is false.
|
||||
*/
|
||||
private boolean useParent = false;
|
||||
final private boolean useParent;
|
||||
|
||||
private RepeatContext context;
|
||||
final private RepeatContext context;
|
||||
|
||||
/**
|
||||
* Increment the counter.
|
||||
@@ -82,7 +82,7 @@ public class RepeatContextCounter {
|
||||
|
||||
super();
|
||||
|
||||
Assert.notNull(context, "The context must be provided");
|
||||
Assert.notNull(context, "The context must be provided to initialize a counter");
|
||||
|
||||
this.countKey = countKey;
|
||||
this.useParent = useParent;
|
||||
|
||||
@@ -21,12 +21,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.RepeatException;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.batch.support.ClassifierSupport;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ExceptionHandler} based on an {@link Classifier}. The classifier determines
|
||||
* whether to log the exception or rethrow it. The keys in the classifier must be the same as the static contants in
|
||||
* this class.
|
||||
* Implementation of {@link ExceptionHandler} based on an {@link Classifier}.
|
||||
* The classifier determines whether to log the exception or rethrow it. The
|
||||
* keys in the classifier must be the same as the static enum in this class.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -34,46 +34,57 @@ import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
public class LogOrRethrowExceptionHandler implements ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be rethrown. If the throwable is not a
|
||||
* RuntimeException it is wrapped in a {@link RepeatException}.
|
||||
* Logging levels for the handler.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public static final String RETHROW = "rethrow";
|
||||
public static enum Level {
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be logged at debug level.
|
||||
*/
|
||||
public static final String DEBUG = "debug";
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* rethrown. If the throwable is not a RuntimeException it is wrapped in
|
||||
* a {@link RepeatException}.
|
||||
*/
|
||||
RETHROW,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be logged at warn level.
|
||||
*/
|
||||
public static final String WARN = "warn";
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at debug level.
|
||||
*/
|
||||
DEBUG,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be logged at error level.
|
||||
*/
|
||||
public static final String ERROR = "error";
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at warn level.
|
||||
*/
|
||||
WARN,
|
||||
|
||||
/**
|
||||
* Key for {@link Classifier} signalling that the throwable should be
|
||||
* logged at error level.
|
||||
*/
|
||||
ERROR
|
||||
|
||||
}
|
||||
|
||||
protected final Log logger = LogFactory.getLog(LogOrRethrowExceptionHandler.class);
|
||||
|
||||
private Classifier<Throwable, String> exceptionClassifier = new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return RETHROW;
|
||||
}
|
||||
};
|
||||
private Classifier<Throwable, Level> exceptionClassifier = new ClassifierSupport<Throwable, Level>(Level.RETHROW);
|
||||
|
||||
/**
|
||||
* Setter for the {@link Classifier} used by this handler. The default is to map all throwable instances to
|
||||
* {@link #RETHROW}.
|
||||
* Setter for the {@link Classifier} used by this handler. The default is to
|
||||
* map all throwable instances to {@link Level#RETHROW}.
|
||||
*
|
||||
* @param exceptionClassifier the ExceptionClassifier to use
|
||||
*/
|
||||
public void setExceptionClassifier(Classifier<Throwable,String> exceptionClassifier) {
|
||||
public void setExceptionClassifier(Classifier<Throwable, Level> exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the throwables and decide whether to rethrow based on the result. The context is not used.
|
||||
* Classify the throwables and decide whether to rethrow based on the
|
||||
* result. The context is not used.
|
||||
*
|
||||
* @throws Throwable
|
||||
*
|
||||
@@ -81,18 +92,18 @@ public class LogOrRethrowExceptionHandler implements ExceptionHandler {
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
|
||||
String key = exceptionClassifier.classify(throwable);
|
||||
if (ERROR.equals(key)) {
|
||||
Level key = exceptionClassifier.classify(throwable);
|
||||
if (Level.ERROR.equals(key)) {
|
||||
logger.error("Exception encountered in batch repeat.", throwable);
|
||||
} else if (WARN.equals(key)) {
|
||||
}
|
||||
else if (Level.WARN.equals(key)) {
|
||||
logger.warn("Exception encountered in batch repeat.", throwable);
|
||||
} else if (DEBUG.equals(key) && logger.isDebugEnabled()) {
|
||||
}
|
||||
else if (Level.DEBUG.equals(key) && logger.isDebugEnabled()) {
|
||||
logger.debug("Exception encountered in batch repeat.", throwable);
|
||||
} else if (RETHROW.equals(key)) {
|
||||
}
|
||||
else if (Level.RETHROW.equals(key)) {
|
||||
throw throwable;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Unclassified exception encountered. Did you mean to classifiy this as 'rethrow'?");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,24 +24,27 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.context.RepeatContextCounter;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.batch.support.SubclassClassifier;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ExceptionHandler} that rethrows when exceptions of a
|
||||
* given type reach a threshold. Requires an {@link Classifier} that
|
||||
* maps exception types to unique keys, and also a map from those keys to
|
||||
* threshold values (Integer type).
|
||||
* given type reach a threshold. Requires an {@link Classifier} that maps
|
||||
* exception types to unique keys, and also a map from those keys to threshold
|
||||
* values (Integer type).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
|
||||
|
||||
protected static final IntegerHolder ZERO = new IntegerHolder(0);
|
||||
|
||||
protected final Log logger = LogFactory.getLog(RethrowOnThresholdExceptionHandler.class);
|
||||
|
||||
private Classifier<Throwable,String> exceptionClassifier = new ExceptionClassifierSupport();
|
||||
|
||||
private Map<String, Integer> thresholds = new HashMap<String, Integer>();
|
||||
private Classifier<? super Throwable, IntegerHolder> exceptionClassifier = new Classifier<Throwable, IntegerHolder>() {
|
||||
public RethrowOnThresholdExceptionHandler.IntegerHolder classify(Throwable classifiable) { return ZERO;}
|
||||
};
|
||||
|
||||
private boolean useParent = false;
|
||||
|
||||
@@ -63,30 +66,19 @@ public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
|
||||
*/
|
||||
public RethrowOnThresholdExceptionHandler() {
|
||||
super();
|
||||
thresholds.put(ExceptionClassifierSupport.DEFAULT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* A map from classifier keys to a threshold value of type Integer. The keys
|
||||
* are usually String literals, depending on the {@link Classifier}
|
||||
* implementation used.
|
||||
* A map from exception classes to a threshold value of type Integer.
|
||||
*
|
||||
* @param thresholds the threshold value map.
|
||||
*/
|
||||
public void setThresholds(Map<String, Integer> thresholds) {
|
||||
this.thresholds = thresholds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the {@link Classifier} used by this handler. The
|
||||
* default is to map all throwable instances to
|
||||
* {@link ExceptionClassifierSupport#DEFAULT}, which are then mapped to a
|
||||
* threshold of 0 by the {@link #setThresholds(Map)} map.
|
||||
*
|
||||
* @param exceptionClassifier ExceptionClassifier to use
|
||||
*/
|
||||
public void setExceptionClassifier(Classifier<Throwable, String> exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
public void setThresholds(Map<Class<? extends Throwable>, Integer> thresholds) {
|
||||
Map<Class<? extends Throwable>, IntegerHolder> typeMap = new HashMap<Class<? extends Throwable>, IntegerHolder>();
|
||||
for (Class<? extends Throwable> type : thresholds.keySet()) {
|
||||
typeMap.put(type, new IntegerHolder(thresholds.get(type)));
|
||||
}
|
||||
exceptionClassifier = new SubclassClassifier<Throwable, IntegerHolder>(typeMap, ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,21 +91,55 @@ public class RethrowOnThresholdExceptionHandler implements ExceptionHandler {
|
||||
*/
|
||||
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
|
||||
|
||||
Object key = exceptionClassifier.classify(throwable);
|
||||
IntegerHolder key = exceptionClassifier.classify(throwable);
|
||||
|
||||
RepeatContextCounter counter = getCounter(context, key);
|
||||
counter.increment();
|
||||
int count = counter.getCount();
|
||||
Integer threshold = thresholds.get(key);
|
||||
if (threshold == null || count > threshold) {
|
||||
int threshold = key.getValue();
|
||||
if (count > threshold) {
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private RepeatContextCounter getCounter(RepeatContext context, Object key) {
|
||||
String attribute = RethrowOnThresholdExceptionHandler.class.getName() + "." + key.toString();
|
||||
private RepeatContextCounter getCounter(RepeatContext context, IntegerHolder key) {
|
||||
String attribute = RethrowOnThresholdExceptionHandler.class.getName() + "." + key;
|
||||
// Creates a new counter and stores it in the correct context:
|
||||
return new RepeatContextCounter(context, attribute, useParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class IntegerHolder {
|
||||
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* @param value
|
||||
*/
|
||||
public IntegerHolder(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the value.
|
||||
* @return the value
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return ObjectUtils.getIdentityHexString(this)+"."+value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.batch.repeat.exception;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* Simple implementation of exception handler which looks for given exception
|
||||
@@ -32,24 +35,37 @@ import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
* @author Dave Syer
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class SimpleLimitExceptionHandler implements ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Name of exception classifier key for the nominated exception types.
|
||||
*/
|
||||
private static final String TX_INVALID = "TX_INVALID";
|
||||
|
||||
/**
|
||||
* Name of exception classifier key for the fatal exception types (not
|
||||
* counted, immediately rethrown).
|
||||
*/
|
||||
private static final String FATAL = "FATAL";
|
||||
public class SimpleLimitExceptionHandler implements ExceptionHandler, InitializingBean {
|
||||
|
||||
private RethrowOnThresholdExceptionHandler delegate = new RethrowOnThresholdExceptionHandler();
|
||||
|
||||
private Class<?>[] exceptionClasses = new Class[] { Exception.class };
|
||||
private Collection<Class<? extends Throwable>> exceptionClasses = Collections
|
||||
.<Class<? extends Throwable>> singleton(Exception.class);
|
||||
|
||||
private Class<?>[] fatalExceptionClasses = new Class[] { Error.class };
|
||||
private Collection<Class<? extends Throwable>> fatalExceptionClasses = Collections
|
||||
.<Class<? extends Throwable>> singleton(Error.class);
|
||||
|
||||
private int limit = 0;
|
||||
|
||||
/**
|
||||
* Apply the provided properties to create a delegate handler.
|
||||
*
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (limit <= 0) {
|
||||
return;
|
||||
}
|
||||
Map<Class<? extends Throwable>, Integer> thresholds = new HashMap<Class<? extends Throwable>, Integer>();
|
||||
for (Class<? extends Throwable> type : exceptionClasses) {
|
||||
thresholds.put(type, limit);
|
||||
}
|
||||
// do the fatalExceptionClasses last so they override the others
|
||||
for (Class<? extends Throwable> type : fatalExceptionClasses) {
|
||||
thresholds.put(type, 0);
|
||||
}
|
||||
delegate.setThresholds(thresholds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to indicate the the exception counters should be shared between
|
||||
@@ -67,12 +83,12 @@ public class SimpleLimitExceptionHandler implements ExceptionHandler {
|
||||
/**
|
||||
* Convenience constructor for the {@link SimpleLimitExceptionHandler} to
|
||||
* set the limit.
|
||||
*
|
||||
*
|
||||
* @param limit the limit
|
||||
*/
|
||||
public SimpleLimitExceptionHandler(int limit) {
|
||||
this();
|
||||
setLimit(limit);
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,28 +96,13 @@ public class SimpleLimitExceptionHandler implements ExceptionHandler {
|
||||
*/
|
||||
public SimpleLimitExceptionHandler() {
|
||||
super();
|
||||
delegate.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
for (Class<?> fatalExceptionClass : fatalExceptionClasses) {
|
||||
if (fatalExceptionClass.isAssignableFrom(throwable.getClass())) {
|
||||
return FATAL;
|
||||
}
|
||||
}
|
||||
for (Class<?> exceptionClass : exceptionClasses) {
|
||||
if (exceptionClass.isAssignableFrom(throwable.getClass())) {
|
||||
return TX_INVALID;
|
||||
}
|
||||
}
|
||||
return super.classify(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rethrows only if the limit is breached for this context on the exception
|
||||
* type specified.
|
||||
*
|
||||
* @see #setExceptionClasses(Class[])
|
||||
* @see #setExceptionClasses(Collection)
|
||||
* @see #setLimit(int)
|
||||
*
|
||||
* @see org.springframework.batch.repeat.exception.ExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext,
|
||||
@@ -118,34 +119,28 @@ public class SimpleLimitExceptionHandler implements ExceptionHandler {
|
||||
* @param limit the limit
|
||||
*/
|
||||
public void setLimit(final int limit) {
|
||||
delegate.setThresholds(new HashMap<String, Integer>() {
|
||||
{
|
||||
put(ExceptionClassifierSupport.DEFAULT, 0);
|
||||
put(TX_INVALID, limit);
|
||||
put(FATAL, 0);
|
||||
}
|
||||
});
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the Throwable exceptionClasses that this handler counts.
|
||||
* Defaults to {@link Exception}. If more exceptionClasses are specified
|
||||
* handler uses single counter that is incremented when one of the
|
||||
* recognized exception exceptionClasses is handled.
|
||||
* Setter for the exception classes that this handler counts. Defaults to
|
||||
* {@link Exception}. If more exceptionClasses are specified handler uses
|
||||
* single counter that is incremented when one of the recognized exception
|
||||
* exceptionClasses is handled.
|
||||
* @param classes exceptionClasses
|
||||
*/
|
||||
public void setExceptionClasses(Class<?>[] classes) {
|
||||
public void setExceptionClasses(Collection<Class<? extends Throwable>> classes) {
|
||||
this.exceptionClasses = classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the Throwable exceptionClasses that shouldn't be counted, but
|
||||
* rethrown immediately. This list has higher priority than
|
||||
* {@link #setExceptionClasses(Class[])}.
|
||||
* Setter for the exception classes that shouldn't be counted, but rethrown
|
||||
* immediately. This list has higher priority than
|
||||
* {@link #setExceptionClasses(Collection)}.
|
||||
*
|
||||
* @param fatalExceptionClasses defaults to {@link Error}
|
||||
*/
|
||||
public void setFatalExceptionClasses(Class<?>[] fatalExceptionClasses) {
|
||||
public void setFatalExceptionClasses(Collection<Class<? extends Throwable>> fatalExceptionClasses) {
|
||||
this.fatalExceptionClasses = fatalExceptionClasses;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.retry.context.RetryContextSupport;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.batch.support.ClassifierSupport;
|
||||
import org.springframework.batch.support.SubclassClassifier;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -35,34 +36,32 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
private Classifier<Throwable, String> exceptionClassifier = new ExceptionClassifierSupport();
|
||||
|
||||
private Map<String, RetryPolicy> policyMap = new HashMap<String, RetryPolicy>();
|
||||
|
||||
public ExceptionClassifierRetryPolicy() {
|
||||
policyMap.put(ExceptionClassifierSupport.DEFAULT, new NeverRetryPolicy());
|
||||
}
|
||||
private Classifier<Throwable, RetryPolicy> exceptionClassifier = new ClassifierSupport<Throwable, RetryPolicy>(
|
||||
new NeverRetryPolicy());
|
||||
|
||||
/**
|
||||
* Setter for policy map. This property should not be changed dynamically -
|
||||
* set it once, e.g. in configuration, and then don't change it during a
|
||||
* running application.
|
||||
* running application. Either this property or the exception classifier
|
||||
* directly should be set, but not both.
|
||||
*
|
||||
* @param policyMap a map of String to {@link RetryPolicy} that will be
|
||||
* applied to the result of the {@link Classifier} to locate a
|
||||
* policy.
|
||||
* @param policyMap a map of String to {@link RetryPolicy} that will be used
|
||||
* to create a {@link Classifier} to locate a policy.
|
||||
*/
|
||||
public void setPolicyMap(Map<String, RetryPolicy> policyMap) {
|
||||
this.policyMap = policyMap;
|
||||
public void setPolicyMap(Map<Class<? extends Throwable>, RetryPolicy> policyMap) {
|
||||
SubclassClassifier<Throwable, RetryPolicy> subclassClassifier = new SubclassClassifier<Throwable, RetryPolicy>(
|
||||
policyMap, (RetryPolicy) new NeverRetryPolicy());
|
||||
this.exceptionClassifier = subclassClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for an exception classifier. The classifier is responsible for
|
||||
* translating exceptions to keys in the policy map.
|
||||
* translating exceptions to concrete retry policies. Either this property
|
||||
* or the policy map should be used, but not both.
|
||||
*
|
||||
* @param exceptionClassifier ExceptionClassifier to use
|
||||
*/
|
||||
public void setExceptionClassifier(Classifier<Throwable,String> exceptionClassifier) {
|
||||
public void setExceptionClassifier(Classifier<Throwable, RetryPolicy> exceptionClassifier) {
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
}
|
||||
|
||||
@@ -110,22 +109,20 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
|
||||
private class ExceptionClassifierRetryContext extends RetryContextSupport implements RetryPolicy {
|
||||
|
||||
private Classifier<Throwable, String> exceptionClassifier;
|
||||
final private Classifier<Throwable, RetryPolicy> exceptionClassifier;
|
||||
|
||||
// Dynamic: depends on the latest exception:
|
||||
RetryPolicy policy;
|
||||
private RetryPolicy policy;
|
||||
|
||||
// Dynamic: depends on the policy:
|
||||
RetryContext context;
|
||||
private RetryContext context;
|
||||
|
||||
Map<RetryPolicy, RetryContext> contexts = new HashMap<RetryPolicy, RetryContext>();
|
||||
final private Map<RetryPolicy, RetryContext> contexts = new HashMap<RetryPolicy, RetryContext>();
|
||||
|
||||
public ExceptionClassifierRetryContext(RetryContext parent, Classifier<Throwable,String> exceptionClassifier) {
|
||||
public ExceptionClassifierRetryContext(RetryContext parent,
|
||||
Classifier<Throwable, RetryPolicy> exceptionClassifier) {
|
||||
super(parent);
|
||||
this.exceptionClassifier = exceptionClassifier;
|
||||
Object key = exceptionClassifier.getDefault();
|
||||
policy = getPolicy(key);
|
||||
Assert.notNull(policy, "Could not locate default policy: key=[" + key + "].");
|
||||
}
|
||||
|
||||
public boolean canRetry(RetryContext context) {
|
||||
@@ -139,7 +136,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
public void close(RetryContext context) {
|
||||
// Only close those policies that have been used (opened):
|
||||
for (RetryPolicy policy : contexts.keySet()) {
|
||||
policy.close(getContext(policy));
|
||||
policy.close(getContext(policy, context.getParent()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,26 +145,21 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
public void registerThrowable(RetryContext context, Exception throwable) {
|
||||
policy = getPolicy(exceptionClassifier.classify(throwable));
|
||||
this.context = getContext(policy);
|
||||
policy = exceptionClassifier.classify(throwable);
|
||||
Assert.notNull(policy, "Could not locate policy for exception=[" + throwable + "].");
|
||||
this.context = getContext(policy, context.getParent());
|
||||
policy.registerThrowable(this.context, throwable);
|
||||
}
|
||||
|
||||
private RetryContext getContext(RetryPolicy policy) {
|
||||
private RetryContext getContext(RetryPolicy policy, RetryContext parent) {
|
||||
RetryContext context = contexts.get(policy);
|
||||
if (context == null) {
|
||||
context = policy.open(null);
|
||||
context = policy.open(parent);
|
||||
contexts.put(policy, context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private RetryPolicy getPolicy(Object key) {
|
||||
RetryPolicy result = policyMap.get(key);
|
||||
Assert.notNull(result, "Could not locate policy for key=[" + key + "].");
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
* @param retryableExceptionClasses defaults to {@link Exception}.
|
||||
*/
|
||||
public final void setRetryableExceptionClasses(Collection<Class<? extends Throwable>> retryableExceptionClasses) {
|
||||
retryableClassifier.setExceptionClasses(retryableExceptionClasses);
|
||||
retryableClassifier.setTypes(retryableExceptionClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +118,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
* @param fatalExceptionClasses defaults to {@link Exception}.
|
||||
*/
|
||||
public final void setFatalExceptionClasses(Collection<Class<? extends Throwable>> fatalExceptionClasses) {
|
||||
fatalClassifier.setExceptionClasses(fatalExceptionClasses);
|
||||
fatalClassifier.setTypes(fatalExceptionClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +162,6 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
* retryable.
|
||||
*/
|
||||
private boolean retryForException(Throwable ex) {
|
||||
return fatalClassifier.isDefault(ex) && !retryableClassifier.isDefault(ex);
|
||||
return !fatalClassifier.classify(ex) && retryableClassifier.classify(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.batch.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.batch.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.batch.retry.policy.RetryContextCache;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
|
||||
/**
|
||||
* Template class that simplifies the execution of operations with retry
|
||||
@@ -78,6 +79,45 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
|
||||
private Classifier<? super Throwable, Boolean> rollbackClassifier = null;
|
||||
|
||||
/**
|
||||
* Public setter for the rollback classifier. This classifier answers its
|
||||
* default if the exception provided should not cause a rollback. I.e.
|
||||
* anything other than the default will lead to a rollback.<br/><br/>
|
||||
*
|
||||
* The decision whether to rollback or not is unrelated to that of the
|
||||
* {@link RetryPolicy}, but the policy can be accidentally inconsistent
|
||||
* with the rollback decision. E.g. in a stateless retry the policy might be
|
||||
* configured to allow retry on a rollback, but that wouldn't make sense - a
|
||||
* stateful retry should have been used. The best we can do in such
|
||||
* circumstances is throw a {@link RetryException} from
|
||||
* {@link #execute(RetryCallback)} or
|
||||
* {@link #execute(RetryCallback, RecoveryCallback)}. The recovery path
|
||||
* will not be taken in such situations.<br/><br/>
|
||||
*
|
||||
* For stateless retry it is often adequate to use the default behaviour, as
|
||||
* long as one is careful with the retry policy (exceptions which should
|
||||
* cause rollback are still not really retryable in a transactional
|
||||
* setting).<br/><br/>
|
||||
*
|
||||
* For stateful retry adding a classifier will allow an optimisation:
|
||||
* exceptions which are not marked for rollback can still be retried, but
|
||||
* without paying the cost of a rollback. Effectively one is overriding the
|
||||
* stateful quality of the retry dynamically, according to the exception
|
||||
* type.<br/><br/>
|
||||
*
|
||||
* Example usage would be for a stateful retry to specify a validation exception as not for rollback
|
||||
*
|
||||
* If not set then the default is to rollback for all exceptions when the
|
||||
* retry is stateful, and for none when it is stateless.
|
||||
*
|
||||
* @param rollbackClassifier the rollback classifier to set
|
||||
*/
|
||||
public void setRollbackClassifier(Classifier<? super Throwable, Boolean> rollbackClassifier) {
|
||||
this.rollbackClassifier = rollbackClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
* @param retryContextCache the {@link RetryContextCache} to set.
|
||||
@@ -406,7 +446,17 @@ public class RetryTemplate implements RetryOperations {
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, RetryState state) {
|
||||
// TODO: allow stateless behaviour to take over for certain exception types
|
||||
// Allow stateless behaviour to take over for certain exception types
|
||||
if (rollbackClassifier != null) {
|
||||
boolean rollback = rollbackClassifier.classify(context.getLastThrowable());
|
||||
if (rollback && state == null && retryPolicy.canRetry(context)) {
|
||||
throw new RetryException("Inconsistent configuration. The retry policy says we can retry but "
|
||||
+ "the exception has been marked for rollback.", context.getLastThrowable());
|
||||
}
|
||||
return rollback;
|
||||
}
|
||||
// If no classifier is provided, just assume the all exceptions are for
|
||||
// rollback if the execution is stateful, and none otherwise.
|
||||
return state != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,59 +20,65 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link Classifier} that has only two classes of exception.
|
||||
* Provides convenient methods for setting up and querying the classification
|
||||
* with boolean return type.
|
||||
* A {@link Classifier} for exceptions that has only two classes (true and
|
||||
* false). Classifies objects according to their inheritance relation with the
|
||||
* supplied types. If the object to be classified is one of the provided types,
|
||||
* or is a subclass of one of the types, then the non-default value is returned
|
||||
* (usually true).
|
||||
*
|
||||
* @see SubclassClassifier
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BinaryExceptionClassifier extends ExceptionClassifierSupport {
|
||||
public class BinaryExceptionClassifier extends SubclassClassifier<Throwable, Boolean> {
|
||||
|
||||
/**
|
||||
* The classifier result for a non-default exception.
|
||||
* Create a binary exception classifier with the provided default value.
|
||||
* @param defaultValue
|
||||
*/
|
||||
public static final String NON_DEFAULT = "NON_DEFAULT";
|
||||
|
||||
private SubclassExceptionClassifier delegate = new SubclassExceptionClassifier();
|
||||
public BinaryExceptionClassifier(boolean defaultValue) {
|
||||
super(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the special exceptions. Any exception on the list, or subclasses
|
||||
* thereof, will be classified as non-default.
|
||||
* Create a binary exception classifier with the default value false.
|
||||
*/
|
||||
public BinaryExceptionClassifier() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the provided classes and their
|
||||
* subclasses. The mapped value for these exceptions will be the one
|
||||
* provided (which will be the opposite of the default).
|
||||
* @param value
|
||||
*/
|
||||
public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses, boolean value) {
|
||||
this(!value);
|
||||
setTypes(exceptionClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary exception classifier with the default value false and
|
||||
* value mapping true for the provided classes and their subclasses.
|
||||
*/
|
||||
public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses) {
|
||||
this(exceptionClasses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of Throwable class types to keys for the classifier. Any subclass of
|
||||
* the type provided will be classified as of non-default type.
|
||||
*
|
||||
* @param exceptionClasses defaults to {@link Exception}.
|
||||
* @param types the types to classify as non-default
|
||||
*/
|
||||
public final void setExceptionClasses(Collection<Class<? extends Throwable>> exceptionClasses) {
|
||||
Map<Class<? extends Throwable>, String> temp = new HashMap<Class<? extends Throwable>, String>();
|
||||
for (Class<? extends Throwable> exceptionClass : exceptionClasses) {
|
||||
temp.put(exceptionClass, NON_DEFAULT);
|
||||
public final void setTypes(Collection<Class<? extends Throwable>> types) {
|
||||
Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>();
|
||||
for (Class<? extends Throwable> type : types) {
|
||||
map.put(type, !getDefault());
|
||||
}
|
||||
this.delegate.setTypeMap(temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to return boolean if the throwable is classified as
|
||||
* default.
|
||||
*
|
||||
* @param throwable the Throwable to classify
|
||||
* @return true if it is default classified (i.e. not on the list provided
|
||||
* in {@link #setExceptionClasses(Collection)}.
|
||||
*/
|
||||
public boolean isDefault(Throwable throwable) {
|
||||
return classify(throwable).equals(DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either {@link ExceptionClassifierSupport#DEFAULT} or
|
||||
* {@link #NON_DEFAULT} depending on the type of the throwable. If the type
|
||||
* of the throwable or one of its ancestors is on the exception class list
|
||||
* the classification is as {@link #NON_DEFAULT}.
|
||||
*
|
||||
* @see #setExceptionClasses(Collection)
|
||||
* @see ExceptionClassifierSupport#classify(Throwable)
|
||||
*/
|
||||
public String classify(Throwable throwable) {
|
||||
return delegate.classify(throwable);
|
||||
setTypeMap(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,20 +26,12 @@ package org.springframework.batch.support;
|
||||
public interface Classifier<C, T> {
|
||||
|
||||
/**
|
||||
* Get a default value, normally the same as would be returned by
|
||||
* {@link #classify(Object)} with null argument.
|
||||
*
|
||||
* @return the default value.
|
||||
*/
|
||||
T getDefault();
|
||||
|
||||
/**
|
||||
* Classify the given object and return a non-null object. The return type
|
||||
* depends on the implementation but typically would be a key in a map which
|
||||
* the client maintains.
|
||||
* Classify the given object and return an object. The return type depends
|
||||
* on the implementation.
|
||||
*
|
||||
* @param classifiable the input object. Can be null.
|
||||
* @return an object.
|
||||
* @return an object. Can be null, but implementations should declare if
|
||||
* this is the case.
|
||||
*/
|
||||
T classify(C classifiable);
|
||||
|
||||
|
||||
@@ -17,35 +17,32 @@
|
||||
package org.springframework.batch.support;
|
||||
|
||||
/**
|
||||
* Base class for {@link Classifier} implementations. Provides default
|
||||
* behaviour and some convenience members, like constants.
|
||||
* Base class for {@link Classifier} implementations. Provides default behaviour
|
||||
* and some convenience members, like constants.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExceptionClassifierSupport implements Classifier<Throwable,String> {
|
||||
public class ClassifierSupport<C, T> implements Classifier<C, T> {
|
||||
|
||||
final private T defaultValue;
|
||||
|
||||
/**
|
||||
* Default classification key.
|
||||
* @param defaultValue
|
||||
*/
|
||||
public static final String DEFAULT = "default";
|
||||
public ClassifierSupport(T defaultValue) {
|
||||
super();
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the value of {@link #DEFAULT}.
|
||||
* Always returns the default value. This is the main extension point for
|
||||
* subclasses, so it must be able to classify null.
|
||||
*
|
||||
* @see org.springframework.batch.support.Classifier#classify(Object)
|
||||
*/
|
||||
public String classify(Throwable throwable) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for a call to {@link #classify(Throwable)} with argument null.
|
||||
*
|
||||
* @see org.springframework.batch.support.Classifier#getDefault()
|
||||
*/
|
||||
public String getDefault() {
|
||||
return classify(null);
|
||||
public T classify(C throwable) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A {@link Classifier} for a parameterised object type based on a map.
|
||||
* Classifies objects according to their inheritance relation with the supplied
|
||||
* type map. If the object to be classified is one of the keys of the provided
|
||||
* map, or is a subclass of one of the keys, then the map entry vale for that
|
||||
* key is returned. Otherwise returns the default value which is null by
|
||||
* default.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SubclassClassifier<T, C> implements Classifier<T, C> {
|
||||
|
||||
private Map<Class<? extends T>, C> classified = new HashMap<Class<? extends T>, C>();
|
||||
|
||||
private C defaultValue = null;
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with null default value.
|
||||
*
|
||||
*/
|
||||
public SubclassClassifier() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue
|
||||
*/
|
||||
public SubclassClassifier(C defaultValue) {
|
||||
this(new HashMap<Class<? extends T>, C>(), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link SubclassClassifier} with supplied default value.
|
||||
*
|
||||
* @param defaultValue
|
||||
*/
|
||||
public SubclassClassifier(Map<Class<? extends T>, C> typeMap, C defaultValue) {
|
||||
super();
|
||||
setTypeMap(typeMap);
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the default value for mapping keys that are not found
|
||||
* in the map (or their subclasses). Defaults to false.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
*/
|
||||
public void setDefaultValue(C defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the classifications up as a map. The keys are types and these will be
|
||||
* mapped along with all their subclasses to the corresponding value. The
|
||||
* most specific types will match first.
|
||||
*
|
||||
* @param map a map from type to class
|
||||
*/
|
||||
public void setTypeMap(Map<Class<? extends T>, C> map) {
|
||||
this.classified = new HashMap<Class<? extends T>, C>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value from the type map whose key is the class of the given
|
||||
* Throwable, or its nearest ancestor if a subclass.
|
||||
*
|
||||
*/
|
||||
public C classify(T classifiable) {
|
||||
|
||||
if (classifiable == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends T> exceptionClass = (Class<? extends T>) classifiable.getClass();
|
||||
if (classified.containsKey(exceptionClass)) {
|
||||
return classified.get(exceptionClass);
|
||||
}
|
||||
|
||||
// check for subclasses
|
||||
Set<Class<? extends T>> classes = new TreeSet<Class<? extends T>>(new ClassComparator());
|
||||
classes.addAll(classified.keySet());
|
||||
for (Class<? extends T> cls : classes) {
|
||||
if (cls.isAssignableFrom(exceptionClass)) {
|
||||
C value = classified.get(cls);
|
||||
this.classified.put(exceptionClass, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default value supplied in the constructor (default false).
|
||||
*/
|
||||
final public C getDefault() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for classes to order by inheritance.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static class ClassComparator implements Comparator<Class<?>> {
|
||||
/**
|
||||
* @return 1 if arg0 is assignable from arg1, -1 otherwise
|
||||
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public int compare(Class<?> arg0, Class<?> arg1) {
|
||||
if (arg0.isAssignableFrom(arg1)) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SubclassExceptionClassifier extends ExceptionClassifierSupport {
|
||||
|
||||
private Map<Class<? extends Throwable>, String> classified = new HashMap<Class<? extends Throwable>, String>();
|
||||
|
||||
/**
|
||||
* Map of Throwable class types to keys for the classifier. Any subclass of
|
||||
* the type provided will be classified as of the type given by the
|
||||
* corresponding map entry value.
|
||||
*
|
||||
* @param typeMap the typeMap to set
|
||||
*/
|
||||
public final void setTypeMap(Map<Class<? extends Throwable>, String> typeMap) {
|
||||
Map<Class<? extends Throwable>, String> map = new HashMap<Class<? extends Throwable>, String>();
|
||||
for (Map.Entry<Class<? extends Throwable>, String> entry : typeMap.entrySet()) {
|
||||
addRetryableExceptionClass(entry.getKey(), entry.getValue(), map);
|
||||
}
|
||||
this.classified = map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value from the type map whose key is the class of the given
|
||||
* Throwable, or its nearest ancestor if a subclass.
|
||||
*
|
||||
* @see org.springframework.batch.support.ExceptionClassifierSupport#classify(java.lang.Throwable)
|
||||
*/
|
||||
public String classify(Throwable throwable) {
|
||||
|
||||
if (throwable == null) {
|
||||
return super.classify(throwable);
|
||||
}
|
||||
|
||||
Class<? extends Throwable> exceptionClass = throwable.getClass();
|
||||
if (classified.containsKey(exceptionClass)) {
|
||||
return classified.get(exceptionClass);
|
||||
}
|
||||
|
||||
// check for subclasses
|
||||
Set<Class<? extends Throwable>> classes = new TreeSet<Class<? extends Throwable>>(new ClassComparator());
|
||||
classes.addAll(classified.keySet());
|
||||
for (Class<? extends Throwable> cls : classes) {
|
||||
if (cls.isAssignableFrom(exceptionClass)) {
|
||||
String value = classified.get(cls);
|
||||
addRetryableExceptionClass(exceptionClass, value, this.classified);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return super.classify(throwable);
|
||||
}
|
||||
|
||||
private void addRetryableExceptionClass(Class<? extends Throwable> exceptionClass, String classifiedAs, Map<Class<? extends Throwable>, String> map) {
|
||||
Assert.isAssignable(Throwable.class, exceptionClass);
|
||||
map.put(exceptionClass, classifiedAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for classes to order by inheritance.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private class ClassComparator implements Comparator<Class<?>> {
|
||||
/**
|
||||
* @return 1 if arg0 is assignable from arg1, -1 otherwise
|
||||
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public int compare(Class<?> arg0, Class<?> arg1) {
|
||||
if (arg0.isAssignableFrom(arg1)) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import static org.junit.Assert.fail;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.stream.XMLEventFactory;
|
||||
@@ -48,7 +47,7 @@ public class StaxEventItemWriterTests {
|
||||
// test item for writing to output
|
||||
private Object item = new Object() {
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(StaxEventItemWriter.class)+"-testString";
|
||||
return ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,7 +72,7 @@ public class StaxEventItemWriterTests {
|
||||
writer.write(items);
|
||||
writer.close(executionContext);
|
||||
String content = outputFileContent();
|
||||
assertTrue("Wrong content: "+content, content.contains(TEST_STRING));
|
||||
assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +95,7 @@ public class StaxEventItemWriterTests {
|
||||
// check the output is concatenation of 'before restart' and 'after
|
||||
// restart' writes.
|
||||
String outputFile = outputFileContent();
|
||||
|
||||
|
||||
assertEquals(2, StringUtils.countOccurrencesOf(outputFile, TEST_STRING));
|
||||
}
|
||||
|
||||
@@ -107,13 +106,13 @@ public class StaxEventItemWriterTests {
|
||||
public void testWriteWithHeader() throws Exception {
|
||||
Object header1 = new Object();
|
||||
Object header2 = new Object();
|
||||
writer.setHeaderItems(new Object[] {header1, header2});
|
||||
writer.setHeaderItems(new Object[] { header1, header2 });
|
||||
writer.open(executionContext);
|
||||
writer.write(items);
|
||||
String content = outputFileContent();
|
||||
assertTrue("Wrong content: "+content, content.contains(("<!--" + header1 + "-->")));
|
||||
assertTrue("Wrong content: "+content, content.contains(("<!--" + header2 + "-->")));
|
||||
assertTrue("Wrong content: "+content, content.contains(TEST_STRING));
|
||||
assertTrue("Wrong content: " + content, content.contains(("<!--" + header1 + "-->")));
|
||||
assertTrue("Wrong content: " + content, content.contains(("<!--" + header2 + "-->")));
|
||||
assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,8 +122,7 @@ public class StaxEventItemWriterTests {
|
||||
public void testStreamContext() throws Exception {
|
||||
writer.open(executionContext);
|
||||
final int NUMBER_OF_RECORDS = 10;
|
||||
assertFalse(executionContext.containsKey(ClassUtils.getShortName(StaxEventItemWriter.class)
|
||||
+ ".record.count"));
|
||||
assertFalse(executionContext.containsKey(ClassUtils.getShortName(StaxEventItemWriter.class) + ".record.count"));
|
||||
for (int i = 1; i <= NUMBER_OF_RECORDS; i++) {
|
||||
writer.write(items);
|
||||
writer.update(executionContext);
|
||||
@@ -141,25 +139,21 @@ public class StaxEventItemWriterTests {
|
||||
@Test
|
||||
public void testOpenAndClose() throws Exception {
|
||||
writer.setRootTagName("testroot");
|
||||
writer.setRootElementAttributes(new HashMap<String, String>() {
|
||||
{
|
||||
put("attribute", "value");
|
||||
}
|
||||
});
|
||||
writer.setRootElementAttributes(Collections.<String, String> singletonMap("attribute", "value"));
|
||||
writer.open(executionContext);
|
||||
writer.close(null);
|
||||
String content = outputFileContent();
|
||||
assertTrue(content.contains("<testroot attribute=\"value\">"));
|
||||
assertTrue(content.endsWith("</testroot>"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNonExistantResource() throws Exception {
|
||||
Resource doesntExist = createMock(Resource.class);
|
||||
expect(doesntExist.getFile()).andReturn(File.createTempFile("arbitrary", null));
|
||||
expect(doesntExist.exists()).andReturn(false);
|
||||
replay(doesntExist);
|
||||
|
||||
|
||||
writer.setResource(doesntExist);
|
||||
|
||||
try {
|
||||
|
||||
@@ -20,34 +20,37 @@ import java.io.StringWriter;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.SimpleLayout;
|
||||
import org.apache.log4j.WriterAppender;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.batch.repeat.exception.LogOrRethrowExceptionHandler.Level;
|
||||
import org.springframework.batch.support.ClassifierSupport;
|
||||
|
||||
public class LogOrRethrowExceptionHandlerTests extends TestCase {
|
||||
|
||||
private LogOrRethrowExceptionHandler handler = new LogOrRethrowExceptionHandler();
|
||||
|
||||
private StringWriter writer;
|
||||
|
||||
private RepeatContext context = null;
|
||||
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
Logger logger = Logger.getLogger(LogOrRethrowExceptionHandler.class);
|
||||
logger.setLevel(Level.DEBUG);
|
||||
logger.setLevel(org.apache.log4j.Level.DEBUG);
|
||||
writer = new StringWriter();
|
||||
logger.removeAllAppenders();
|
||||
logger.getParent().removeAllAppenders();
|
||||
logger.addAppender(new WriterAppender(new SimpleLayout(), writer));
|
||||
}
|
||||
|
||||
|
||||
public void testRuntimeException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
} catch (RuntimeException e) {
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -56,15 +59,16 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected Error");
|
||||
} catch (Error e) {
|
||||
}
|
||||
catch (Error e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testNotRethrownErrorLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return LogOrRethrowExceptionHandler.ERROR;
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.ERROR;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
@@ -73,20 +77,9 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testNotRethrownWarnLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return LogOrRethrowExceptionHandler.WARN;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
public void testNotRethrownDebugLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return LogOrRethrowExceptionHandler.DEBUG;
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.WARN;
|
||||
}
|
||||
});
|
||||
// No exception...
|
||||
@@ -94,18 +87,15 @@ public class LogOrRethrowExceptionHandlerTests extends TestCase {
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
public void testUnclassifiedException() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return "DEFAULT";
|
||||
public void testNotRethrownDebugLevel() throws Throwable {
|
||||
handler.setExceptionClassifier(new ClassifierSupport<Throwable,Level>(Level.RETHROW) {
|
||||
public Level classify(Throwable throwable) {
|
||||
return Level.DEBUG;
|
||||
}
|
||||
});
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
assertTrue(e.getMessage().toLowerCase().indexOf("unclassified")>=0);
|
||||
}
|
||||
// No exception...
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
assertNotNull(writer.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,60 +16,60 @@
|
||||
|
||||
package org.springframework.batch.repeat.exception;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.context.RepeatContextCounter;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
|
||||
public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
|
||||
public class RethrowOnThresholdExceptionHandlerTests {
|
||||
|
||||
private RethrowOnThresholdExceptionHandler handler = new RethrowOnThresholdExceptionHandler();
|
||||
|
||||
private RepeatContext parent = new RepeatContextSupport(null);
|
||||
|
||||
private RepeatContext context = new RepeatContextSupport(parent);
|
||||
|
||||
|
||||
@Test
|
||||
public void testRuntimeException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
fail("Expected RuntimeException");
|
||||
} catch (RuntimeException e) {
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testError() throws Throwable {
|
||||
try {
|
||||
handler.handleException(context, new Error("Foo"));
|
||||
fail("Expected Error");
|
||||
} catch (Error e) {
|
||||
}
|
||||
catch (Error e) {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotRethrownWithThreshold() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return "RuntimeException";
|
||||
}
|
||||
});
|
||||
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
RepeatContextCounter counter = new RepeatContextCounter(context, RethrowOnThresholdExceptionHandler.class.getName() + ".RuntimeException");
|
||||
AtomicInteger counter = (AtomicInteger) context.getAttribute(context.attributeNames()[0]);
|
||||
assertNotNull(counter);
|
||||
assertEquals(1, counter.getCount());
|
||||
assertEquals(1, counter.get());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRethrowOnThreshold() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return "RuntimeException";
|
||||
}
|
||||
});
|
||||
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(2)));
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 2));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
@@ -81,14 +81,10 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
|
||||
assertEquals("Foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotUseParent() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return "RuntimeException";
|
||||
}
|
||||
});
|
||||
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
context = new RepeatContextSupport(parent);
|
||||
@@ -101,13 +97,9 @@ public class RethrowOnThresholdExceptionHandlerTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseParent() throws Throwable {
|
||||
handler.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
return "RuntimeException";
|
||||
}
|
||||
});
|
||||
handler.setThresholds(Collections.singletonMap("RuntimeException", new Integer(1)));
|
||||
handler.setThresholds(Collections.<Class<? extends Throwable>, Integer> singletonMap(Exception.class, 1));
|
||||
handler.setUseParent(true);
|
||||
// No exception...
|
||||
handler.handleException(context, new RuntimeException("Foo"));
|
||||
|
||||
@@ -16,11 +16,17 @@
|
||||
|
||||
package org.springframework.batch.repeat.exception;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
|
||||
/**
|
||||
@@ -29,77 +35,110 @@ import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
* @author Robert Kasanicky
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
public class SimpleLimitExceptionHandlerTests {
|
||||
|
||||
// object under test
|
||||
private SimpleLimitExceptionHandler handler = new SimpleLimitExceptionHandler();
|
||||
|
||||
@Before
|
||||
public void initializeHandler() throws Exception {
|
||||
handler.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeWithNullContext() throws Throwable {
|
||||
try {
|
||||
handler.handleException(null, new RuntimeException("foo"));
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeWithNullContextAndNullException() throws Throwable {
|
||||
try {
|
||||
handler.handleException(null, null);
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Other than nominated exception type should be rethrown, ignoring the exception limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testNormalExceptionThrown() throws Throwable {
|
||||
@Test
|
||||
public void testDefaultBehaviour() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(new Class<?>[] { IllegalArgumentException.class });
|
||||
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), throwable);
|
||||
fail("Exception swallowed.");
|
||||
} catch (RuntimeException expected) {
|
||||
fail("Exception was swallowed.");
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertTrue("Exception is rethrown, ignoring the exception limit", true);
|
||||
assertSame(expected, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidException should only be rethrown below the exception limit.
|
||||
* Other than nominated exception type should be rethrown, ignoring the
|
||||
* exception limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testNormalExceptionThrown() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(IllegalArgumentException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), throwable);
|
||||
fail("Exception was swallowed.");
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertTrue("Exception is rethrown, ignoring the exception limit", true);
|
||||
assertSame(expected, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidException should only be rethrown below the exception
|
||||
* limit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionTypeNotThrown() throws Throwable {
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(new Class[] {RuntimeException.class} );
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
try {
|
||||
handler.handleException(new RepeatContextSupport(null), new RuntimeException("foo"));
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionNotThrownFromSiblings() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(new Class[] {RuntimeException.class});
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
RepeatContextSupport parent = new RepeatContextSupport(null);
|
||||
|
||||
@@ -108,23 +147,27 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
handler.handleException(context, throwable);
|
||||
context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
public void testLimitedExceptionThrownFromSiblingsWhenUsingParent() throws Throwable {
|
||||
Throwable throwable = new RuntimeException("foo");
|
||||
|
||||
final int MORE_THAN_ZERO = 1;
|
||||
handler.setLimit(MORE_THAN_ZERO);
|
||||
handler.setExceptionClasses(new Class[] { RuntimeException.class } );
|
||||
handler.setExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
handler.setUseParent(true);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
RepeatContextSupport parent = new RepeatContextSupport(null);
|
||||
|
||||
@@ -134,19 +177,22 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
context = new RepeatContextSupport(parent);
|
||||
handler.handleException(context, throwable);
|
||||
fail("Expected exception.");
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertSame(throwable, expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TransactionInvalidExceptions are swallowed until the exception limit is exceeded. After the limit is exceeded
|
||||
* exceptions are rethrown as BatchCriticalExceptions
|
||||
* Exceptions are swallowed until the exception limit is exceeded. After the
|
||||
* limit is exceeded exceptions are rethrown
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionNotThrownBelowLimit() throws Throwable {
|
||||
|
||||
final int EXCEPTION_LIMIT = 3;
|
||||
handler.setLimit(EXCEPTION_LIMIT);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
List<Throwable> throwables = new ArrayList<Throwable>() {
|
||||
{
|
||||
@@ -165,20 +211,24 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
}
|
||||
} catch (RuntimeException unexpected) {
|
||||
}
|
||||
catch (RuntimeException unexpected) {
|
||||
fail("exception rethrown although exception limit was not exceeded");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Test
|
||||
public void testExceptionThrownAboveLimit() throws Throwable {
|
||||
|
||||
final int EXCEPTION_LIMIT = 3;
|
||||
handler.setLimit(EXCEPTION_LIMIT);
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
List<Throwable> throwables = new ArrayList<Throwable>() {
|
||||
{
|
||||
@@ -199,7 +249,8 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
}
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertEquals("above exception limit", expected.getMessage());
|
||||
}
|
||||
|
||||
@@ -208,7 +259,8 @@ public class SimpleLimitExceptionHandlerTests extends TestCase {
|
||||
handler.handleException(context, new RuntimeException("foo"));
|
||||
assertTrue("exceptions up to limit are swallowed", true);
|
||||
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
catch (RuntimeException expected) {
|
||||
assertEquals("foo", expected.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,15 @@ package org.springframework.batch.retry.policy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
import org.springframework.batch.support.Classifier;
|
||||
|
||||
public class ExceptionClassifierRetryPolicyTests extends TestCase {
|
||||
|
||||
ExceptionClassifierRetryPolicy policy = new ExceptionClassifierRetryPolicy();
|
||||
private ExceptionClassifierRetryPolicy policy = new ExceptionClassifierRetryPolicy();
|
||||
|
||||
public void testDefaultPolicies() throws Exception {
|
||||
RetryContext context = policy.open(null);
|
||||
@@ -36,28 +34,22 @@ public class ExceptionClassifierRetryPolicyTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testTrivialPolicies() throws Exception {
|
||||
policy.setPolicyMap(Collections.singletonMap(ExceptionClassifierSupport.DEFAULT,
|
||||
(RetryPolicy) new MockRetryPolicySupport()));
|
||||
policy.setPolicyMap(Collections.<Class<? extends Throwable>, RetryPolicy> singletonMap(Exception.class,
|
||||
new MockRetryPolicySupport()));
|
||||
RetryContext context = policy.open(null);
|
||||
assertNotNull(context);
|
||||
assertTrue(policy.canRetry(context));
|
||||
}
|
||||
|
||||
public void testNullPolicies() throws Exception {
|
||||
policy.setPolicyMap(new HashMap<String, RetryPolicy>());
|
||||
try {
|
||||
policy.open(null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
policy.setPolicyMap(new HashMap<Class<? extends Throwable>, RetryPolicy>());
|
||||
RetryContext context = policy.open(null);
|
||||
assertNotNull(context);
|
||||
}
|
||||
|
||||
public void testNullContext() throws Exception {
|
||||
Map<String, RetryPolicy> map = new HashMap<String, RetryPolicy>();
|
||||
map.put(ExceptionClassifierSupport.DEFAULT, new NeverRetryPolicy());
|
||||
policy.setPolicyMap(map);
|
||||
policy.setPolicyMap(Collections.<Class<? extends Throwable>, RetryPolicy> singletonMap(Exception.class,
|
||||
new NeverRetryPolicy()));
|
||||
|
||||
RetryContext context = policy.open(null);
|
||||
assertNotNull(context);
|
||||
@@ -67,53 +59,52 @@ public class ExceptionClassifierRetryPolicyTests extends TestCase {
|
||||
|
||||
public void testClassifierOperates() throws Exception {
|
||||
|
||||
Map<String, RetryPolicy> map = new HashMap<String, RetryPolicy>();
|
||||
map.put(ExceptionClassifierSupport.DEFAULT, new AlwaysRetryPolicy());
|
||||
map.put("foo", new NeverRetryPolicy());
|
||||
policy.setPolicyMap(map);
|
||||
|
||||
RetryContext context = policy.open(null);
|
||||
assertNotNull(context);
|
||||
|
||||
assertTrue(policy.canRetry(context));
|
||||
policy.registerThrowable(context, new IllegalArgumentException());
|
||||
assertTrue(policy.canRetry(context));
|
||||
assertFalse(policy.canRetry(context)); // NeverRetryPolicy is the
|
||||
// default
|
||||
|
||||
policy.setExceptionClassifier(new ExceptionClassifierSupport() {
|
||||
public String classify(Throwable throwable) {
|
||||
policy.setExceptionClassifier(new Classifier<Throwable, RetryPolicy>() {
|
||||
public RetryPolicy classify(Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
return "foo";
|
||||
return new AlwaysRetryPolicy();
|
||||
}
|
||||
return super.classify(throwable);
|
||||
return new NeverRetryPolicy();
|
||||
}
|
||||
});
|
||||
|
||||
// The context saves the classifier, so changing it now has no effect
|
||||
assertTrue(policy.canRetry(context));
|
||||
assertFalse(policy.canRetry(context));
|
||||
policy.registerThrowable(context, new IllegalArgumentException());
|
||||
assertTrue(policy.canRetry(context));
|
||||
assertFalse(policy.canRetry(context));
|
||||
|
||||
// But now the classifier will be active in the new context...
|
||||
context = policy.open(null);
|
||||
assertTrue(policy.canRetry(context));
|
||||
policy.registerThrowable(context, new IllegalArgumentException());
|
||||
assertFalse(policy.canRetry(context));
|
||||
assertTrue(policy.canRetry(context));
|
||||
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
public void testClose() throws Exception {
|
||||
policy.setPolicyMap(Collections.singletonMap(ExceptionClassifierSupport.DEFAULT,
|
||||
(RetryPolicy) new MockRetryPolicySupport() {
|
||||
policy.setExceptionClassifier(new Classifier<Throwable, RetryPolicy>() {
|
||||
public RetryPolicy classify(Throwable throwable) {
|
||||
return new MockRetryPolicySupport() {
|
||||
public void close(RetryContext context) {
|
||||
count++;
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
});
|
||||
RetryContext context = policy.open(null);
|
||||
|
||||
// The mapped (child) policy hasn't been used yet, so if we close now
|
||||
// we don't incur the possible expense of ceating the child context.
|
||||
// we don't incur the possible expense of creating the child context.
|
||||
policy.close(context);
|
||||
assertEquals(0, count); // not classified yet
|
||||
// This forces a child context to be created and the child policy is
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
package org.springframework.batch.retry.policy;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@@ -38,12 +39,10 @@ public class FatalExceptionRetryPolicyTests extends TestCase {
|
||||
retryTemplate.setRetryPolicy(policy);
|
||||
|
||||
// ...but make sure certain exceptions are fatal
|
||||
policy.setFatalExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(IllegalArgumentException.class);
|
||||
add(IllegalStateException.class);
|
||||
}
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends Throwable>> list = Arrays.<Class<? extends Throwable>> asList(IllegalArgumentException.class,
|
||||
IllegalStateException.class);
|
||||
policy.setFatalExceptionClasses(list);
|
||||
RecoveryCallback<String> recoveryCallback = new RecoveryCallback<String>() {
|
||||
public String recover(RetryContext context) throws Exception {
|
||||
return "bar";
|
||||
@@ -71,13 +70,11 @@ public class FatalExceptionRetryPolicyTests extends TestCase {
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy(3);
|
||||
retryTemplate.setRetryPolicy(policy);
|
||||
|
||||
policy.setFatalExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(IllegalArgumentException.class);
|
||||
add(IllegalStateException.class);
|
||||
}
|
||||
});
|
||||
RecoveryCallback<String>recoveryCallback = new RecoveryCallback<String>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends Throwable>> list = Arrays.<Class<? extends Throwable>> asList(
|
||||
IllegalArgumentException.class, IllegalStateException.class);
|
||||
policy.setFatalExceptionClasses(list);
|
||||
RecoveryCallback<String> recoveryCallback = new RecoveryCallback<String>() {
|
||||
public String recover(RetryContext context) throws Exception {
|
||||
return "bar";
|
||||
}
|
||||
|
||||
@@ -22,19 +22,21 @@ import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.retry.ExhaustedRetryException;
|
||||
import org.springframework.batch.retry.RecoveryCallback;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.backoff.BackOffContext;
|
||||
import org.springframework.batch.retry.backoff.BackOffInterruptedException;
|
||||
import org.springframework.batch.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.batch.retry.backoff.StatelessBackOffPolicy;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.support.BinaryExceptionClassifier;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
@@ -71,7 +73,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
});
|
||||
assertEquals(2, callback.attempts);
|
||||
assertEquals(value, result);
|
||||
assertEquals(value, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,16 +119,28 @@ public class RetryTemplateTests {
|
||||
assertEquals(attempts, callback.attempts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollbackClassifierOverridesRetryPolicy() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
int attempts = 3;
|
||||
callback.setAttemptsBeforeSuccess(attempts);
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts));
|
||||
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalArgumentException.class), false);
|
||||
retryTemplate.setRollbackClassifier(classifier);
|
||||
retryTemplate.execute(callback, new RetryState("foo"));
|
||||
assertEquals(attempts, callback.attempts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetExceptions() throws Exception {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy();
|
||||
template.setRetryPolicy(policy);
|
||||
policy.setRetryableExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(RuntimeException.class);
|
||||
}
|
||||
});
|
||||
policy.setRetryableExceptionClasses(Collections.<Class<? extends Throwable>> singleton(RuntimeException.class));
|
||||
|
||||
int attempts = 3;
|
||||
|
||||
@@ -243,7 +257,7 @@ public class RetryTemplateTests {
|
||||
assertEquals("foo", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MockRetryCallback implements RetryCallback<Object> {
|
||||
|
||||
private int attempts;
|
||||
|
||||
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -39,6 +40,8 @@ import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.batch.support.BinaryExceptionClassifier;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
public class StatefulRecoveryRetryTests {
|
||||
|
||||
@@ -121,6 +124,37 @@ public class StatefulRecoveryRetryTests {
|
||||
assertEquals(input, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchToStatelessForNoRollback() throws Exception {
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
// Roll back for these:
|
||||
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(DataAccessException.class));
|
||||
// ...but not these:
|
||||
assertFalse(classifier.classify(new RuntimeException()));
|
||||
retryTemplate.setRollbackClassifier(classifier);
|
||||
final String input = "foo";
|
||||
RetryState state = new RetryState(input);
|
||||
RetryCallback<String> callback = new RetryCallback<String>() {
|
||||
public String doWithRetry(RetryContext context) throws Exception {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
RecoveryCallback<String> recoveryCallback = new RecoveryCallback<String>() {
|
||||
public String recover(RetryContext context) {
|
||||
count++;
|
||||
list.add(input);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
Object result = null;
|
||||
// On the second retry, the recovery path is taken...
|
||||
result = retryTemplate.execute(callback, recoveryCallback, state);
|
||||
assertEquals(input, result); // default result is the item
|
||||
assertEquals(1, count);
|
||||
assertEquals(input, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustedClearsHistoryAfterLastAttempt() throws Exception {
|
||||
RetryPolicy retryPolicy = new SimpleRetryPolicy(1);
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import java.util.Collections;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class BinaryExceptionClassifierTests extends TestCase {
|
||||
@@ -25,19 +24,36 @@ public class BinaryExceptionClassifierTests extends TestCase {
|
||||
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier();
|
||||
|
||||
public void testClassifyNullIsDefault() {
|
||||
assertTrue(classifier.isDefault(null));
|
||||
assertFalse(classifier.classify(null));
|
||||
}
|
||||
|
||||
public void testFalseIsDefault() {
|
||||
assertFalse(classifier.getDefault());
|
||||
}
|
||||
|
||||
public void testDefaultProvided() {
|
||||
classifier = new BinaryExceptionClassifier(true);
|
||||
assertTrue(classifier.getDefault());
|
||||
}
|
||||
|
||||
public void testClassifyRandomException() {
|
||||
assertTrue(classifier.isDefault(new IllegalStateException("foo")));
|
||||
assertFalse(classifier.classify(new IllegalStateException("foo")));
|
||||
}
|
||||
|
||||
public void testClassifyExactMatch() {
|
||||
classifier.setExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(IllegalStateException.class);
|
||||
}
|
||||
});
|
||||
assertEquals(false, classifier.isDefault(new IllegalStateException("Foo")));
|
||||
classifier.setTypes(Collections.<Class<? extends Throwable>> singleton(IllegalStateException.class));
|
||||
assertTrue(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
public void testTypesProvidedInConstructor() {
|
||||
classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class));
|
||||
assertTrue(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
public void testTypesProvidedInConstructorWithNonDefault() {
|
||||
classifier = new BinaryExceptionClassifier(Collections
|
||||
.<Class<? extends Throwable>> singleton(IllegalStateException.class), false);
|
||||
assertFalse(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,18 @@
|
||||
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import org.springframework.batch.support.ExceptionClassifierSupport;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class ExceptionClassifierSupportTests extends TestCase {
|
||||
public class ClassifierSupportTests extends TestCase {
|
||||
|
||||
public void testClassifyNullIsDefault() {
|
||||
ExceptionClassifierSupport classifier = new ExceptionClassifierSupport();
|
||||
assertEquals(classifier.classify(null), classifier.getDefault());
|
||||
ClassifierSupport<String,String> classifier = new ClassifierSupport<String,String>("foo");
|
||||
assertEquals(classifier.classify(null), "foo");
|
||||
}
|
||||
|
||||
public void testClassifyRandomException() {
|
||||
ExceptionClassifierSupport classifier = new ExceptionClassifierSupport();
|
||||
assertEquals(classifier.classify(new IllegalStateException("Foo")), classifier.getDefault());
|
||||
ClassifierSupport<Throwable,String> classifier = new ClassifierSupport<Throwable,String>("foo");
|
||||
assertEquals(classifier.classify(new IllegalStateException("Foo")), classifier.classify(null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,49 +16,69 @@
|
||||
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class SubclassExceptionClassifierTests extends TestCase {
|
||||
import org.junit.Test;
|
||||
|
||||
SubclassExceptionClassifier classifier = new SubclassExceptionClassifier();
|
||||
public class SubclassExceptionClassifierTests {
|
||||
|
||||
SubclassClassifier<Throwable, String> classifier = new SubclassClassifier<Throwable, String>();
|
||||
|
||||
@Test
|
||||
public void testClassifyNullIsDefault() {
|
||||
assertEquals(classifier.classify(null), classifier.getDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyNull() {
|
||||
assertNull(classifier.classify(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyNullNonDefault() {
|
||||
classifier = new SubclassClassifier<Throwable, String>("foo");
|
||||
assertEquals("foo", classifier.classify(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyRandomException() {
|
||||
assertEquals(classifier.classify(new IllegalStateException("Foo")), classifier.getDefault());
|
||||
assertNull(classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyExactMatch() {
|
||||
classifier.setTypeMap(new LinkedHashMap<Class<? extends Throwable>, String>() {{
|
||||
put(IllegalStateException.class, "foo");
|
||||
}});
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifySubclassMatch() {
|
||||
classifier.setTypeMap(new LinkedHashMap<Class<? extends Throwable>, String>() {{
|
||||
put(RuntimeException.class, "foo");
|
||||
}});
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(RuntimeException.class,
|
||||
"foo"));
|
||||
assertEquals("foo", classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifySuperclassDoesNotMatch() {
|
||||
classifier.setTypeMap(new LinkedHashMap<Class<? extends Throwable>, String>() {{
|
||||
put(IllegalStateException.class, "foo");
|
||||
}});
|
||||
classifier.setTypeMap(Collections.<Class<? extends Throwable>, String> singletonMap(
|
||||
IllegalStateException.class, "foo"));
|
||||
assertEquals(classifier.getDefault(), classifier.classify(new RuntimeException("Foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassifyAncestorMatch() {
|
||||
classifier.setTypeMap(new LinkedHashMap<Class<? extends Throwable>, String>() {{
|
||||
put(Exception.class, "bar");
|
||||
put(IllegalArgumentException.class, "foo");
|
||||
put(RuntimeException.class, "bucket");
|
||||
}});
|
||||
assertEquals("bucket", classifier.classify(new IllegalStateException("Foo")));
|
||||
classifier.setTypeMap(new HashMap<Class<? extends Throwable>, String>() {
|
||||
{
|
||||
put(Exception.class, "foo");
|
||||
put(IllegalArgumentException.class, "bar");
|
||||
put(RuntimeException.class, "spam");
|
||||
}
|
||||
});
|
||||
assertEquals("spam", classifier.classify(new IllegalStateException("Foo")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
class="org.springframework.batch.item.database.JdbcCursorItemReader">
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
<property name="sql"
|
||||
value="SELECT id, quantity, price, customer from TRADE" />
|
||||
value="SELECT isin, quantity, price, customer from TRADE" />
|
||||
<property name="mapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.domain.trade.internal.TradeRowMapper" />
|
||||
|
||||
@@ -28,7 +28,7 @@ log4j.logger.org.springframework=info
|
||||
#log4j.logger.org.springframework.orm=debug
|
||||
|
||||
### debug your specific package or classes with the following example
|
||||
log4j.logger.org.springframework.batch=info
|
||||
log4j.logger.org.springframework.batch=debug
|
||||
log4j.logger.org.springframework.batch.sample=debug
|
||||
log4j.logger.org.springframework.batch.sample.module.OrderDataProvider=debug
|
||||
log4j.logger.org.springframework.batch.container.common.module.process.support.DefaultXmlDataProvider=debug
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class AbstractBatchLauncherTests implements ApplicationContextAw
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
protected JobLauncher launcher;
|
||||
private JobLauncher launcher;
|
||||
|
||||
private Job job;
|
||||
|
||||
@@ -77,6 +77,14 @@ public abstract class AbstractBatchLauncherTests implements ApplicationContextAw
|
||||
|
||||
@Test
|
||||
public void testLaunchJob() throws Exception {
|
||||
launcher.run(job, jobParameters);
|
||||
getLauncher().run(job, jobParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the launcher.
|
||||
* @return the launcher
|
||||
*/
|
||||
protected JobLauncher getLauncher() {
|
||||
return launcher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class DatabaseShutdownFunctionalTests extends AbstractBatchLauncherTests
|
||||
|
||||
final JobParameters jobParameters = new JobParameters();
|
||||
|
||||
JobExecution jobExecution = launcher.run(getJob(), jobParameters);
|
||||
JobExecution jobExecution = getLauncher().run(getJob(), jobParameters);
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public class GracefulShutdownFunctionalTests extends AbstractBatchLauncherTests
|
||||
|
||||
final JobParameters jobParameters = new JobParameters();
|
||||
|
||||
JobExecution jobExecution = launcher.run(getJob(), jobParameters);
|
||||
JobExecution jobExecution = getLauncher().run(getJob(), jobParameters);
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ public class RestartFunctionalTests extends AbstractBatchLauncherTests {
|
||||
private void runJobForRestartTest() throws Exception {
|
||||
// The second time we run the job it needs to be a new instance so we
|
||||
// need to make the parameters unique...
|
||||
launcher.run(getJob(), new DefaultJobParametersConverter().getJobParameters(PropertiesConverter
|
||||
getLauncher().run(getJob(), new DefaultJobParametersConverter().getJobParameters(PropertiesConverter
|
||||
.stringToProperties("force.new.job.parameters=true")));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package org.springframework.batch.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Deletes files in the given directory.
|
||||
@@ -19,13 +21,13 @@ import org.springframework.util.Assert;
|
||||
@ContextConfiguration()
|
||||
public class TaskletJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
|
||||
|
||||
private Resource directory = new FileSystemResource("target/test-outputs/test-dir");
|
||||
private static Resource directory = new FileSystemResource("target/test-outputs/test-dir");
|
||||
|
||||
/*
|
||||
* Create the directory and some files in it.
|
||||
*/
|
||||
@Before
|
||||
public void onSetUp() throws Exception {
|
||||
@BeforeClass
|
||||
public static void onSetUp() throws Exception {
|
||||
File dir = directory.getFile();
|
||||
dir.mkdirs();
|
||||
new File(dir, "file1").createNewFile();
|
||||
@@ -37,8 +39,8 @@ public class TaskletJobFunctionalTests extends AbstractValidatingBatchLauncherTe
|
||||
*/
|
||||
@Override
|
||||
protected void validatePreConditions() throws Exception {
|
||||
Assert.state(directory.getFile().isDirectory());
|
||||
Assert.state(directory.getFile().listFiles().length > 0);
|
||||
assertTrue(directory.getFile().isDirectory());
|
||||
assertTrue(directory.getFile().listFiles().length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,8 +48,8 @@ public class TaskletJobFunctionalTests extends AbstractValidatingBatchLauncherTe
|
||||
*/
|
||||
@Override
|
||||
protected void validatePostConditions() throws Exception {
|
||||
Assert.state(directory.getFile().isDirectory());
|
||||
Assert.state(directory.getFile().listFiles().length == 0);
|
||||
assertTrue(directory.getFile().isDirectory());
|
||||
assertEquals(0, directory.getFile().listFiles().length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -80,16 +80,16 @@ public class TradeJobFunctionalTests extends AbstractValidatingBatchLauncherTest
|
||||
|
||||
// assertTrue(((Resource)applicationContext.getBean("customerFileLocator")).exists());
|
||||
|
||||
customers = new ArrayList<Customer>() {{add(new Customer("customer1", (credits.get("customer1") - 98.34)));
|
||||
add(new Customer("customer2", (credits.get("customer2") - 18.12 - 12.78)));
|
||||
add(new Customer("customer3", (credits.get("customer3") - 109.25)));
|
||||
add(new Customer("customer4", credits.get("customer4") - 123.39));}};
|
||||
customers = Arrays.asList(new Customer("customer1", (credits.get("customer1") - 98.34)),
|
||||
new Customer("customer2", (credits.get("customer2") - 18.12 - 12.78)),
|
||||
new Customer("customer3", (credits.get("customer3") - 109.25)),
|
||||
new Customer("customer4", credits.get("customer4") - 123.39));
|
||||
|
||||
trades = new ArrayList<Trade>() {{add(new Trade("UK21341EAH45", 978, new BigDecimal("98.34"), "customer1"));
|
||||
add(new Trade("UK21341EAH46", 112, new BigDecimal("18.12"), "customer2"));
|
||||
add(new Trade("UK21341EAH47", 245, new BigDecimal("12.78"), "customer2"));
|
||||
add(new Trade("UK21341EAH48", 108, new BigDecimal("109.25"), "customer3"));
|
||||
add(new Trade("UK21341EAH49", 854, new BigDecimal("123.39"), "customer4"));}};
|
||||
trades = Arrays.asList(new Trade("UK21341EAH45", 978, new BigDecimal("98.34"), "customer1"),
|
||||
new Trade("UK21341EAH46", 112, new BigDecimal("18.12"), "customer2"),
|
||||
new Trade("UK21341EAH47", 245, new BigDecimal("12.78"), "customer2"),
|
||||
new Trade("UK21341EAH48", 108, new BigDecimal("109.25"), "customer3"),
|
||||
new Trade("UK21341EAH49", 854, new BigDecimal("123.39"), "customer4"));
|
||||
|
||||
// check content of the trade table
|
||||
simpleJdbcTemplate.getJdbcOperations().query(GET_TRADES, new RowCallbackHandler() {
|
||||
|
||||
Reference in New Issue
Block a user