RESOLVED - issue BATCH-1129: Problems with exception classifications
This commit is contained in:
@@ -34,6 +34,8 @@ import org.springframework.batch.retry.listener.RetryListenerSupport;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
|
||||
/**
|
||||
* @author Dan Garrette
|
||||
@@ -65,7 +67,7 @@ public class StepParserStepFactoryBeanTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskletStep_All() throws Exception {
|
||||
public void testTaskletStepAll() throws Exception {
|
||||
StepParserStepFactoryBean<Object, Object> fb = new StepParserStepFactoryBean<Object, Object>();
|
||||
fb.setBeanName("step1");
|
||||
fb.setAllowStartIfComplete(true);
|
||||
@@ -74,7 +76,9 @@ public class StepParserStepFactoryBeanTests {
|
||||
fb.setTasklet(new DummyTasklet());
|
||||
fb.setTransactionManager(new ResourcelessTransactionManager());
|
||||
fb.setListeners(new StepExecutionListenerSupport[] { new StepExecutionListenerSupport() });
|
||||
fb.setTransactionAttributeList(new ArrayList<String>());
|
||||
fb.setIsolation(Isolation.DEFAULT);
|
||||
fb.setTransactionTimeout(-1);
|
||||
fb.setPropagation(Propagation.REQUIRED);
|
||||
Object step = fb.getObject();
|
||||
assertTrue(step instanceof TaskletStep);
|
||||
Object tasklet = ReflectionTestUtils.getField(step, "tasklet");
|
||||
@@ -82,7 +86,7 @@ public class StepParserStepFactoryBeanTests {
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSimpleStep_All() throws Exception {
|
||||
public void testSimpleStepAll() throws Exception {
|
||||
StepParserStepFactoryBean<Object, Object> fb = new StepParserStepFactoryBean<Object, Object>();
|
||||
fb.setBeanName("step1");
|
||||
fb.setAllowStartIfComplete(true);
|
||||
@@ -90,7 +94,9 @@ public class StepParserStepFactoryBeanTests {
|
||||
fb.setStartLimit(5);
|
||||
fb.setTransactionManager(new ResourcelessTransactionManager());
|
||||
fb.setListeners(new StepListener[] { new StepExecutionListenerSupport() });
|
||||
fb.setTransactionAttributeList(new ArrayList<String>());
|
||||
fb.setIsolation(Isolation.DEFAULT);
|
||||
fb.setTransactionTimeout(-1);
|
||||
fb.setPropagation(Propagation.REQUIRED);
|
||||
fb.setChunkCompletionPolicy(new DummyCompletionPolicy());
|
||||
fb.setCommitInterval(5);
|
||||
fb.setTaskExecutor(new SyncTaskExecutor());
|
||||
@@ -113,7 +119,9 @@ public class StepParserStepFactoryBeanTests {
|
||||
fb.setStartLimit(5);
|
||||
fb.setTransactionManager(new ResourcelessTransactionManager());
|
||||
fb.setListeners(new StepListener[] { new StepExecutionListenerSupport() });
|
||||
fb.setTransactionAttributeList(new ArrayList<String>());
|
||||
fb.setIsolation(Isolation.DEFAULT);
|
||||
fb.setTransactionTimeout(-1);
|
||||
fb.setPropagation(Propagation.REQUIRED);
|
||||
fb.setChunkCompletionPolicy(new DummyCompletionPolicy());
|
||||
fb.setCommitInterval(5);
|
||||
fb.setTaskExecutor(new SyncTaskExecutor());
|
||||
@@ -145,7 +153,9 @@ public class StepParserStepFactoryBeanTests {
|
||||
fb.setStartLimit(5);
|
||||
fb.setTransactionManager(new ResourcelessTransactionManager());
|
||||
fb.setListeners(new StepListener[] { new StepExecutionListenerSupport() });
|
||||
fb.setTransactionAttributeList(new ArrayList<String>());
|
||||
fb.setIsolation(Isolation.DEFAULT);
|
||||
fb.setTransactionTimeout(-1);
|
||||
fb.setPropagation(Propagation.REQUIRED);
|
||||
fb.setChunkCompletionPolicy(new DummyCompletionPolicy());
|
||||
fb.setTaskExecutor(new SyncTaskExecutor());
|
||||
fb.setItemReader(new DummyItemReader());
|
||||
@@ -169,7 +179,6 @@ public class StepParserStepFactoryBeanTests {
|
||||
fb.setStartLimit(5);
|
||||
fb.setTransactionManager(new ResourcelessTransactionManager());
|
||||
fb.setListeners(new StepListener[] { new StepExecutionListenerSupport() });
|
||||
fb.setTransactionAttributeList(new ArrayList<String>());
|
||||
fb.setChunkCompletionPolicy(new DummyCompletionPolicy());
|
||||
fb.setTaskExecutor(new SyncTaskExecutor());
|
||||
fb.setItemReader(new DummyItemReader());
|
||||
|
||||
@@ -38,8 +38,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
|
||||
/**
|
||||
* @author Thomas Risberg
|
||||
@@ -53,7 +52,8 @@ public class StepParserTests {
|
||||
"org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml");
|
||||
Map<String, Object> beans = ctx.getBeansOfType(StepParserStepFactoryBean.class);
|
||||
String factoryName = (String) beans.keySet().toArray()[0];
|
||||
StepParserStepFactoryBean<Object, Object> factory = (StepParserStepFactoryBean<Object, Object>) beans.get(factoryName);
|
||||
StepParserStepFactoryBean<Object, Object> factory = (StepParserStepFactoryBean<Object, Object>) beans
|
||||
.get(factoryName);
|
||||
TaskletStep bean = (TaskletStep) factory.getObject();
|
||||
assertEquals("wrong start-limit:", 25, bean.getStartLimit());
|
||||
}
|
||||
@@ -127,7 +127,8 @@ public class StepParserTests {
|
||||
try {
|
||||
new XmlBeanFactory(new ClassPathResource(contextLocation));
|
||||
fail("Context should not load!");
|
||||
} catch (BeanDefinitionParsingException e) {
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertTrue(e.getMessage().contains("'ref' and 'class'"));
|
||||
}
|
||||
}
|
||||
@@ -162,43 +163,35 @@ public class StepParserTests {
|
||||
"org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml");
|
||||
|
||||
// On Inline - No Merge
|
||||
validateTransactionAttributesInherited("s1", false, ctx);
|
||||
validateTransactionAttributesInherited("s1", ctx);
|
||||
|
||||
// On Standalone - No Merge
|
||||
validateTransactionAttributesInherited("s2", false, ctx);
|
||||
validateTransactionAttributesInherited("s2", ctx);
|
||||
|
||||
// On Inline With Tasklet Ref - No Merge
|
||||
validateTransactionAttributesInherited("s3", false, ctx);
|
||||
validateTransactionAttributesInherited("s3", ctx);
|
||||
|
||||
// On Standalone With Tasklet Ref - No Merge
|
||||
validateTransactionAttributesInherited("s4", false, ctx);
|
||||
validateTransactionAttributesInherited("s4", ctx);
|
||||
|
||||
// On Inline
|
||||
validateTransactionAttributesInherited("s5", true, ctx);
|
||||
validateTransactionAttributesInherited("s5", ctx);
|
||||
|
||||
// On Standalone
|
||||
validateTransactionAttributesInherited("s6", true, ctx);
|
||||
validateTransactionAttributesInherited("s6", ctx);
|
||||
|
||||
// On Inline With Tasklet Ref
|
||||
validateTransactionAttributesInherited("s7", true, ctx);
|
||||
validateTransactionAttributesInherited("s7", ctx);
|
||||
|
||||
// On Standalone With Tasklet Ref
|
||||
validateTransactionAttributesInherited("s8", true, ctx);
|
||||
validateTransactionAttributesInherited("s8", ctx);
|
||||
}
|
||||
|
||||
private void validateTransactionAttributesInherited(String stepName, boolean inherited, ApplicationContext ctx) {
|
||||
RuleBasedTransactionAttribute txa = getTransactionAttribute(ctx, stepName);
|
||||
private void validateTransactionAttributesInherited(String stepName, ApplicationContext ctx) {
|
||||
DefaultTransactionAttribute txa = getTransactionAttribute(ctx, stepName);
|
||||
assertEquals(TransactionDefinition.PROPAGATION_REQUIRED, txa.getPropagationBehavior());
|
||||
assertEquals(TransactionDefinition.ISOLATION_DEFAULT, txa.getIsolationLevel());
|
||||
if (inherited) {
|
||||
assertEquals(10, txa.getTimeout());
|
||||
RollbackRuleAttribute rra = (RollbackRuleAttribute) txa.getRollbackRules().get(0);
|
||||
assertEquals("org.springframework.dao.DataIntegrityViolationException", rra.getExceptionName());
|
||||
}
|
||||
else {
|
||||
assertTrue(10 != txa.getTimeout());
|
||||
assertTrue(txa.getRollbackRules().isEmpty());
|
||||
}
|
||||
assertEquals(-1, txa.getTimeout());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -224,7 +217,7 @@ public class StepParserTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private RuleBasedTransactionAttribute getTransactionAttribute(ApplicationContext ctx, String stepName) {
|
||||
private DefaultTransactionAttribute getTransactionAttribute(ApplicationContext ctx, String stepName) {
|
||||
Map<String, Object> beans = ctx.getBeansOfType(Step.class);
|
||||
assertTrue(beans.containsKey(stepName));
|
||||
Step step = (Step) ctx.getBean(stepName);
|
||||
@@ -233,7 +226,7 @@ public class StepParserTests {
|
||||
}
|
||||
assertTrue(step instanceof TaskletStep);
|
||||
Object transactionAttribute = ReflectionTestUtils.getField(step, "transactionAttribute");
|
||||
RuleBasedTransactionAttribute txa = (RuleBasedTransactionAttribute) transactionAttribute;
|
||||
DefaultTransactionAttribute txa = (DefaultTransactionAttribute) transactionAttribute;
|
||||
return txa;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,45 +37,43 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
|
||||
/**
|
||||
* @author Thomas Risberg
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class StepWithFaultTolerantProcessTaskJobParserTests {
|
||||
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TestReader reader;
|
||||
|
||||
|
||||
@Autowired
|
||||
@Qualifier("listener")
|
||||
private TestListener listener;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TestRetryListener retryListener;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TestProcessor processor;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TestWriter writer;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Autowired
|
||||
private StepParserStepFactoryBean factory;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MapJobRepositoryFactoryBean.clear();
|
||||
@@ -92,27 +90,21 @@ public class StepWithFaultTolerantProcessTaskJobParserTests {
|
||||
assertEquals("wrong retry-limit:", 3, rl);
|
||||
Object cc = ReflectionTestUtils.getField(factory, "cacheCapacity");
|
||||
assertEquals("wrong cache-capacity:", 100, cc);
|
||||
Object txa = ReflectionTestUtils.getField(factory, "transactionAttribute");
|
||||
assertEquals("wrong transaction-attribute:", TransactionDefinition.PROPAGATION_REQUIRED,
|
||||
((RuleBasedTransactionAttribute)txa).getPropagationBehavior());
|
||||
assertEquals("wrong transaction-attribute:", TransactionDefinition.ISOLATION_DEFAULT,
|
||||
((RuleBasedTransactionAttribute)txa).getIsolationLevel());
|
||||
assertEquals("wrong transaction-attribute:", 10,
|
||||
((RuleBasedTransactionAttribute)txa).getTimeout());
|
||||
RollbackRuleAttribute rra =
|
||||
(RollbackRuleAttribute) ((RuleBasedTransactionAttribute)txa).getRollbackRules().get(0);
|
||||
assertEquals("wrong transaction-attribute:",
|
||||
"org.springframework.dao.DataIntegrityViolationException", rra.getExceptionName());
|
||||
assertEquals("wrong transaction-attribute:", Propagation.REQUIRED, ReflectionTestUtils.getField(factory,
|
||||
"propagation"));
|
||||
assertEquals("wrong transaction-attribute:", Isolation.DEFAULT, ReflectionTestUtils.getField(factory,
|
||||
"isolation"));
|
||||
assertEquals("wrong transaction-attribute:", 10, ReflectionTestUtils.getField(factory, "transactionTimeout"));
|
||||
Object txq = ReflectionTestUtils.getField(factory, "isReaderTransactionalQueue");
|
||||
assertEquals("wrong is-reader-transactional-queue:", true, txq);
|
||||
Object te = ReflectionTestUtils.getField(factory, "taskExecutor");
|
||||
assertEquals("wrong task-executor:", ConcurrentTaskExecutor.class, te.getClass());
|
||||
Object listeners = ReflectionTestUtils.getField(factory, "listeners");
|
||||
assertEquals("wrong number of listeners:", 2, ((StepListener[])listeners).length);
|
||||
assertEquals("wrong number of listeners:", 2, ((StepListener[]) listeners).length);
|
||||
Object retryListeners = ReflectionTestUtils.getField(factory, "retryListeners");
|
||||
assertEquals("wrong number of retry-listeners:", 2, ((RetryListener[])retryListeners).length);
|
||||
assertEquals("wrong number of retry-listeners:", 2, ((RetryListener[]) retryListeners).length);
|
||||
Object streams = ReflectionTestUtils.getField(factory, "streams");
|
||||
assertEquals("wrong number of streams:", 1, ((ItemStream[])streams).length);
|
||||
assertEquals("wrong number of streams:", 1, ((ItemStream[]) streams).length);
|
||||
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
|
||||
job.execute(jobExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeEditor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link FaultTolerantStepFactoryBean}.
|
||||
*/
|
||||
public class FaultTolerantStepFactoryBeanRollbackTests {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private FaultTolerantStepFactoryBean<String, String> factory = new FaultTolerantStepFactoryBean<String, String>();
|
||||
|
||||
private static Collection<String> NO_FAILURES = Collections.emptyList();
|
||||
|
||||
private SkipReaderStub reader = new SkipReaderStub();
|
||||
|
||||
private SkipWriterStub writer = new SkipWriterStub();
|
||||
|
||||
private JobExecution jobExecution;
|
||||
|
||||
private StepExecution stepExecution;
|
||||
|
||||
private JobRepository repository;
|
||||
|
||||
private static boolean runtimeException = false;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
factory.setBeanName("stepName");
|
||||
factory.setTransactionManager(new ResourcelessTransactionManager());
|
||||
factory.setCommitInterval(2);
|
||||
factory.setItemReader(reader);
|
||||
factory.setItemWriter(writer);
|
||||
factory.setSkipLimit(2);
|
||||
|
||||
MapJobRepositoryFactoryBean.clear();
|
||||
MapJobRepositoryFactoryBean repositoryFactory = new MapJobRepositoryFactoryBean();
|
||||
repositoryFactory.setTransactionManager(new ResourcelessTransactionManager());
|
||||
repositoryFactory.afterPropertiesSet();
|
||||
repository = (JobRepository) repositoryFactory.getObject();
|
||||
factory.setJobRepository(repository);
|
||||
|
||||
jobExecution = repository.createJobExecution("skipJob", new JobParameters());
|
||||
stepExecution = jobExecution.createStepExecution(factory.getName());
|
||||
repository.add(stepExecution);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideWithoutChangingRollbackRules() throws Exception {
|
||||
TransactionAttributeEditor editor = new TransactionAttributeEditor();
|
||||
editor.setAsText("-RuntimeException");
|
||||
TransactionAttribute attr = (TransactionAttribute) editor.getValue();
|
||||
assertTrue(attr.rollbackOn(new RuntimeException("")));
|
||||
assertFalse(attr.rollbackOn(new Exception("")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeRollbackRules() throws Exception {
|
||||
TransactionAttributeEditor editor = new TransactionAttributeEditor();
|
||||
editor.setAsText("+RuntimeException");
|
||||
TransactionAttribute attr = (TransactionAttribute) editor.getValue();
|
||||
assertFalse(attr.rollbackOn(new RuntimeException("")));
|
||||
assertFalse(attr.rollbackOn(new Exception("")));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testNonDefaultRollbackRules() throws Exception {
|
||||
TransactionAttributeEditor editor = new TransactionAttributeEditor();
|
||||
editor.setAsText("+RuntimeException,+SkippableException");
|
||||
RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute) editor.getValue();
|
||||
attr.getRollbackRules().add(new RollbackRuleAttribute(Exception.class));
|
||||
assertTrue(attr.rollbackOn(new Exception("")));
|
||||
assertFalse(attr.rollbackOn(new RuntimeException("")));
|
||||
assertFalse(attr.rollbackOn(new SkippableException("")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in reader that should not cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testReaderDefaultNoRollbackOnCheckedException() throws Exception {
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, Arrays.asList("2", "3")));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in reader that should not cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testReaderAttributesOverrideSkippableNoRollback() throws Exception {
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, Arrays.asList("2", "3")));
|
||||
|
||||
// No skips by default
|
||||
factory.setSkippableExceptionClasses(new HashSet<Class<? extends Throwable>>());
|
||||
// But this one is explicit in the tx-attrs so it should be skipped
|
||||
factory.setNoRollbackExceptionClasses(getExceptionList(SkippableException.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that should cause rollback because of
|
||||
* checked exception
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorDefaultRollbackOnCheckedException() throws Exception {
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that should cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorDefaultRollbackOnRuntimeException() throws Exception {
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = true;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessSkipWithNoRollbackForCheckedException() throws Exception {
|
||||
|
||||
reader = new SkipReaderStub(new String[] { "1", "2", "3", "4", "5" }, NO_FAILURES);
|
||||
factory.setItemReader(reader);
|
||||
factory.setNoRollbackExceptionClasses(getExceptionList(SkippableException.class));
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(new String[] { "4" }));
|
||||
factory.setItemProcessor(processor);
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getReadSkipCount());
|
||||
assertEquals(5, stepExecution.getReadCount());
|
||||
assertEquals(1, stepExecution.getProcessSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
|
||||
// skips "4"
|
||||
assertTrue(reader.processed.contains("4"));
|
||||
assertFalse(writer.written.contains("4"));
|
||||
|
||||
List<String> expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,5"));
|
||||
assertEquals(expectedOutput, writer.written);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in writer that should not cause rollback and scan
|
||||
*/
|
||||
@Test
|
||||
public void testWriterDefaultRollbackOnCheckedException() throws Exception {
|
||||
factory.setItemWriter(new SkipWriterStub(Arrays.asList("2", "3")));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(4, stepExecution.getRollbackCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in writer that should not cause rollback and scan
|
||||
*/
|
||||
@Test
|
||||
public void testWriterDefaultRollbackOnRuntimeException() throws Exception {
|
||||
factory.setItemWriter(new SkipWriterStub(Arrays.asList("2", "3")));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = true;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(4, stepExecution.getRollbackCount());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in writer that should not cause rollback and scan
|
||||
*/
|
||||
@Test
|
||||
public void testWriterNoRollbackOnRuntimeException() throws Exception {
|
||||
factory.setItemWriter(new SkipWriterStub(Arrays.asList("2", "3")));
|
||||
factory.setNoRollbackExceptionClasses(getExceptionList(SkippableRuntimeException.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = true;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
// Two multi-item chunks rolled back. When the item was encountered on
|
||||
// its own it can proceed
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in writer that should not cause rollback and scan
|
||||
*/
|
||||
@Test
|
||||
public void testWriterNoRollbackOnCheckedException() throws Exception {
|
||||
factory.setItemWriter(new SkipWriterStub(Arrays.asList("2", "3")));
|
||||
factory.setNoRollbackExceptionClasses(getExceptionList(SkippableException.class));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
runtimeException = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
// Two multi-item chunks rolled back. When the item was encountered on
|
||||
// its own it can proceed
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<Class<? extends Throwable>> getExceptionList(Class<? extends Throwable> args) {
|
||||
return Arrays.<Class<? extends Throwable>> asList(args);
|
||||
}
|
||||
|
||||
private static class SkipProcessorStub implements ItemProcessor<String, String> {
|
||||
private final Collection<String> failures;
|
||||
|
||||
public SkipProcessorStub() {
|
||||
this(NO_FAILURES);
|
||||
}
|
||||
|
||||
public SkipProcessorStub(Collection<String> failures) {
|
||||
this.failures = failures;
|
||||
}
|
||||
|
||||
public String process(String item) throws Exception {
|
||||
if (failures.contains(item)) {
|
||||
if (runtimeException) {
|
||||
throw new SkippableRuntimeException("should cause rollback");
|
||||
}
|
||||
else {
|
||||
throw new SkippableException("shouldn't cause rollback");
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple item reader that supports skip functionality.
|
||||
*/
|
||||
private static class SkipReaderStub implements ItemReader<String> {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final String[] items;
|
||||
|
||||
private Collection<String> processed = new ArrayList<String>();
|
||||
|
||||
private int counter = -1;
|
||||
|
||||
private final Collection<String> failures;
|
||||
|
||||
public SkipReaderStub() {
|
||||
this(new String[] { "1", "2", "3", "4", "5" }, NO_FAILURES);
|
||||
}
|
||||
|
||||
public SkipReaderStub(String[] items, Collection<String> failures) {
|
||||
this.items = items;
|
||||
this.failures = failures;
|
||||
}
|
||||
|
||||
public String read() throws Exception, UnexpectedInputException, ParseException {
|
||||
counter++;
|
||||
if (counter >= items.length) {
|
||||
logger.debug("Returning null at count=" + counter);
|
||||
return null;
|
||||
}
|
||||
String item = items[counter];
|
||||
if (failures.contains(item)) {
|
||||
logger.debug("Throwing exception for [" + item + "] at count=" + counter);
|
||||
if (runtimeException) {
|
||||
throw new SkippableRuntimeException("should cause rollback in reader");
|
||||
}
|
||||
else {
|
||||
throw new SkippableException("shouldn't cause rollback in reader");
|
||||
}
|
||||
}
|
||||
processed.add(item);
|
||||
logger.debug("Returning [" + item + "] at count=" + counter);
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple item writer that supports skip functionality.
|
||||
*/
|
||||
private static class SkipWriterStub implements ItemWriter<String> {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
// simulate transactional output
|
||||
private List<Object> written = TransactionAwareProxyFactory.createTransactionalList();
|
||||
|
||||
private final Collection<String> failures;
|
||||
|
||||
public SkipWriterStub() {
|
||||
this(NO_FAILURES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param failures commaDelimitedListToSet
|
||||
*/
|
||||
public SkipWriterStub(Collection<String> failures) {
|
||||
this.failures = failures;
|
||||
}
|
||||
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
for (String item : items) {
|
||||
if (failures.contains(item)) {
|
||||
logger.debug("Throwing write exception on [" + item + "]");
|
||||
if (runtimeException) {
|
||||
throw new SkippableRuntimeException("should cause rollback in writer");
|
||||
}
|
||||
else {
|
||||
throw new SkippableException("shouldn't cause rollback in writer");
|
||||
}
|
||||
}
|
||||
written.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SkippableException extends Exception {
|
||||
public SkippableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SkippableRuntimeException extends RuntimeException {
|
||||
public SkippableRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -42,7 +41,6 @@ import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
|
||||
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -55,8 +53,8 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
private FaultTolerantStepFactoryBean<String, String> factory = new FaultTolerantStepFactoryBean<String, String>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<Class<? extends Throwable>> skippableExceptions = new HashSet<Class<? extends Throwable>>(Arrays
|
||||
.<Class<? extends Throwable>> asList(SkippableException.class, SkippableRuntimeException.class));
|
||||
private Collection<Class<? extends Throwable>> skippableExceptions = Arrays.<Class<? extends Throwable>> asList(
|
||||
SkippableException.class, SkippableRuntimeException.class);
|
||||
|
||||
private SkipReaderStub reader = new SkipReaderStub();
|
||||
|
||||
@@ -254,33 +252,6 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that rollback write exception does cause rollback when included on
|
||||
* transaction attributes as "no rollback for".
|
||||
*/
|
||||
@Test
|
||||
public void testSkipWithoutRethrow() throws Exception {
|
||||
factory.setTransactionAttribute(new DefaultTransactionAttribute() {
|
||||
public boolean rollbackOn(Throwable ex) {
|
||||
return !(ex instanceof SkippableRuntimeException);
|
||||
};
|
||||
});
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
step.execute(stepExecution);
|
||||
|
||||
assertEquals(1, stepExecution.getSkipCount());
|
||||
assertEquals(1, stepExecution.getReadSkipCount());
|
||||
assertEquals(0, stepExecution.getWriteSkipCount());
|
||||
|
||||
// one rollback for write exception
|
||||
assertEquals(1, stepExecution.getRollbackCount());
|
||||
|
||||
assertEquals(4, stepExecution.getReadCount());
|
||||
assertStepExecutionsAreEqual(stepExecution, repository.getLastStepExecution(jobExecution.getJobInstance(), step
|
||||
.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fatal exception should cause immediate termination regardless of other
|
||||
* skip settings (note the fatal exception is also classified as rollback).
|
||||
@@ -547,51 +518,6 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that shouldn't cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorNoRollback() throws Exception {
|
||||
factory.setTransactionAttribute(new DefaultTransactionAttribute());
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
processor.rollback = false;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(0, stepExecution.getRollbackCount());
|
||||
assertStepExecutionsAreEqual(stepExecution, repository.getLastStepExecution(jobExecution.getJobInstance(), step
|
||||
.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario: Exception in processor that should cause rollback
|
||||
*/
|
||||
@Test
|
||||
public void testProcessorRollback() throws Exception {
|
||||
SkipProcessorStub processor = new SkipProcessorStub(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,3")));
|
||||
factory.setItemProcessor(processor);
|
||||
|
||||
factory.setItemReader(new SkipReaderStub(new String[] { "1", "2", "3", "4" }, NO_FAILURES));
|
||||
factory.setItemWriter(new SkipWriterStub(NO_FAILURES));
|
||||
|
||||
Step step = (Step) factory.getObject();
|
||||
|
||||
processor.rollback = true;
|
||||
step.execute(stepExecution);
|
||||
assertEquals(2, stepExecution.getSkipCount());
|
||||
assertEquals(2, stepExecution.getRollbackCount());
|
||||
assertStepExecutionsAreEqual(stepExecution, repository.getLastStepExecution(jobExecution.getJobInstance(), step
|
||||
.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReprocessingAfterWriterRollback() throws Exception {
|
||||
factory.setItemProcessor(new ItemProcessor<String, String>() {
|
||||
@@ -730,7 +656,7 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
private static class SkipProcessorStub implements ItemProcessor<String, String> {
|
||||
private final Collection<String> failures;
|
||||
|
||||
private boolean rollback = false;
|
||||
private boolean runtimeException = false;
|
||||
|
||||
public SkipProcessorStub(Collection<String> failures) {
|
||||
this.failures = failures;
|
||||
@@ -738,7 +664,7 @@ public class FaultTolerantStepFactoryBeanTests {
|
||||
|
||||
public String process(String item) throws Exception {
|
||||
if (failures.contains(item)) {
|
||||
if (rollback) {
|
||||
if (runtimeException) {
|
||||
throw new SkippableRuntimeException("should cause rollback");
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepExecutionListener;
|
||||
import org.springframework.batch.core.job.JobSupport;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
|
||||
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
|
||||
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
|
||||
import org.springframework.batch.core.repository.support.SimpleJobRepository;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.JobRepositorySupport;
|
||||
import org.springframework.batch.core.step.StepInterruptionPolicy;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
@@ -54,12 +56,14 @@ import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.batch.item.ItemStreamSupport;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.batch.repeat.policy.DefaultResultCompletionPolicy;
|
||||
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
|
||||
import org.springframework.batch.repeat.support.RepeatTemplate;
|
||||
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
|
||||
public class TaskletStepTests {
|
||||
@@ -785,6 +789,30 @@ public class TaskletStepTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoRollbackFor() throws Exception {
|
||||
|
||||
step.setTasklet(new Tasklet() {
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
throw new RuntimeException("Bar");
|
||||
}
|
||||
});
|
||||
|
||||
JobExecution jobExecutionContext = new JobExecution(jobInstance);
|
||||
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
|
||||
|
||||
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute() {
|
||||
@Override
|
||||
public boolean rollbackOn(Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
step.setTransactionAttribute(transactionAttribute);
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
|
||||
private static class JobRepositoryStub extends JobRepositorySupport {
|
||||
|
||||
private int updateCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user