diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java index 94749f701..e65eb5d68 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.xml; import java.util.ArrayList; import java.util.List; +import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -77,56 +78,70 @@ public class TaskletElementParser { else { bd = new RootBeanDefinition("org.springframework.batch.core.step.item.SimpleStepFactoryBean", null, null); } + + MutablePropertyValues propertyValues = bd.getPropertyValues(); String readerBeanId = element.getAttribute("reader"); if (StringUtils.hasText(readerBeanId)) { RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); - bd.getPropertyValues().addPropertyValue("itemReader", readerRef); + propertyValues.addPropertyValue("itemReader", readerRef); } String processorBeanId = element.getAttribute("processor"); if (StringUtils.hasText(processorBeanId)) { RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); - bd.getPropertyValues().addPropertyValue("itemProcessor", processorRef); + propertyValues.addPropertyValue("itemProcessor", processorRef); } String writerBeanId = element.getAttribute("writer"); if (StringUtils.hasText(writerBeanId)) { RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); - bd.getPropertyValues().addPropertyValue("itemWriter", writerRef); + propertyValues.addPropertyValue("itemWriter", writerRef); } String taskExecutorBeanId = element.getAttribute("task-executor"); if (StringUtils.hasText(taskExecutorBeanId)) { RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); - bd.getPropertyValues().addPropertyValue("taskExecutor", taskExecutorRef); + propertyValues.addPropertyValue("taskExecutor", taskExecutorRef); } String commitInterval = element.getAttribute("commit-interval"); if (StringUtils.hasText(commitInterval)) { - bd.getPropertyValues().addPropertyValue("commitInterval", commitInterval); + propertyValues.addPropertyValue("commitInterval", commitInterval); } - if (StringUtils.hasText(skipLimit)) { - bd.getPropertyValues().addPropertyValue("skipLimit", skipLimit); + String completionPolicyRef = element.getAttribute("chunk-completion-policy"); + if (StringUtils.hasText(completionPolicyRef)) { + RuntimeBeanReference completionPolicy = new RuntimeBeanReference(completionPolicyRef); + propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy); + } + + if (propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) { + parserContext.getReaderContext().error( + "The 'tasklet' element must contain either 'commit-interval' " + + "or 'chunk-completion-policy', but not both.", element); + } + + if (StringUtils.hasText(skipLimit)) { + propertyValues.addPropertyValue("skipLimit", skipLimit); } if (StringUtils.hasText(retryLimit)) { - bd.getPropertyValues().addPropertyValue("retryLimit", retryLimit); + propertyValues.addPropertyValue("retryLimit", retryLimit); } if (StringUtils.hasText(cacheCapacity)) { - bd.getPropertyValues().addPropertyValue("cacheCapacity", cacheCapacity); + propertyValues.addPropertyValue("cacheCapacity", cacheCapacity); } String transactionAttribute = element.getAttribute("transaction-attribute"); if (StringUtils.hasText(transactionAttribute)) { - bd.getPropertyValues().addPropertyValue("transactionAttribute", transactionAttribute); + propertyValues.addPropertyValue("transactionAttribute", transactionAttribute); } if (StringUtils.hasText(isReaderTransactionalQueue)) { if (isFaultTolerant) { - bd.getPropertyValues().addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); + propertyValues.addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); } } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index ae0c3f1c8..47dcf5111 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -356,10 +356,11 @@ - + @@ -437,7 +438,7 @@ @@ -447,6 +448,22 @@ + + + + + + + + + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyCompletionPolicy.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyCompletionPolicy.java new file mode 100644 index 000000000..b9c6e0045 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyCompletionPolicy.java @@ -0,0 +1,33 @@ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.batch.repeat.CompletionPolicy; +import org.springframework.batch.repeat.RepeatContext; +import org.springframework.batch.repeat.RepeatStatus; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class DummyCompletionPolicy implements CompletionPolicy { + + public boolean isComplete(RepeatContext context, RepeatStatus result) { + // TODO Auto-generated method stub + return false; + } + + public boolean isComplete(RepeatContext context) { + // TODO Auto-generated method stub + return false; + } + + public RepeatContext start(RepeatContext parent) { + // TODO Auto-generated method stub + return null; + } + + public void update(RepeatContext context) { + // TODO Auto-generated method stub + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemReader.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemReader.java new file mode 100644 index 000000000..655ace386 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemReader.java @@ -0,0 +1,17 @@ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ParseException; +import org.springframework.batch.item.UnexpectedInputException; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class DummyItemReader implements ItemReader { + + public Object read() throws Exception, UnexpectedInputException, ParseException { + return null; + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemWriter.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemWriter.java new file mode 100644 index 000000000..7e3cf012f --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyItemWriter.java @@ -0,0 +1,16 @@ +package org.springframework.batch.core.configuration.xml; + +import java.util.List; + +import org.springframework.batch.item.ItemWriter; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class DummyItemWriter implements ItemWriter { + + public void write(List items) throws Exception { + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java index 1a16743d6..d78416ee9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java @@ -19,45 +19,94 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.lang.reflect.Field; import java.util.Map; import org.junit.Test; import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.item.ChunkOrientedTasklet; +import org.springframework.batch.core.step.item.ChunkProvider; import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean; +import org.springframework.batch.core.step.item.SimpleChunkProvider; +import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.repeat.CompletionPolicy; +import org.springframework.batch.repeat.RepeatOperations; +import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; +import org.springframework.batch.repeat.support.RepeatTemplate; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; - /** * @author Thomas Risberg */ public class StepParserTests { - + @SuppressWarnings("unchecked") @Test public void testTaskletStepAttributes() throws Exception { - ConfigurableApplicationContext ctx = - new ClassPathXmlApplicationContext("org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml"); + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml"); Map beans = ctx.getBeansOfType(FaultTolerantStepFactoryBean.class); String factoryName = (String) beans.keySet().toArray()[0]; - FaultTolerantStepFactoryBean factory = (FaultTolerantStepFactoryBean) beans.get(factoryName); + FaultTolerantStepFactoryBean factory = (FaultTolerantStepFactoryBean) beans + .get(factoryName); TaskletStep bean = (TaskletStep) factory.getObject(); assertEquals("wrong start-limit:", 25, bean.getStartLimit()); } - + @SuppressWarnings("unchecked") @Test public void testStepParserBeanName() throws Exception { - ConfigurableApplicationContext ctx = - new ClassPathXmlApplicationContext("org/springframework/batch/core/configuration/xml/StepParserBeanNameTests-context.xml"); + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserBeanNameTests-context.xml"); Map beans = ctx.getBeansOfType(Step.class); assertTrue("'s1' bean not found", beans.containsKey("s1")); - Step s1 = (Step)ctx.getBean("s1"); + Step s1 = (Step) ctx.getBean("s1"); assertEquals("wrong name", "s1", s1.getName()); } + @Test(expected = BeanDefinitionParsingException.class) + public void testStepParserCommitIntervalCompletionPolicy() throws Exception { + new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserCommitIntervalCompletionPolicyTests-context.xml"); + } + + @SuppressWarnings("unchecked") + @Test + public void testStepParserCommitInterval() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml"); + Map beans = ctx.getBeansOfType(Step.class); + assertTrue("'s1' bean not found", beans.containsKey("s1")); + Step s1 = (Step) ctx.getBean("s1"); + CompletionPolicy completionPolicy = getCompletionPolicy(s1); + assertTrue(completionPolicy instanceof SimpleCompletionPolicy); + Field chunkSizeField = SimpleCompletionPolicy.class.getDeclaredField("chunkSize"); + chunkSizeField.setAccessible(true); + assertEquals(25, chunkSizeField.get(completionPolicy)); + } + + @SuppressWarnings("unchecked") + @Test + public void testStepParserCompletionPolicy() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml"); + Map beans = ctx.getBeansOfType(Step.class); + assertTrue("'s1' bean not found", beans.containsKey("s1")); + Step s1 = (Step) ctx.getBean("s1"); + CompletionPolicy completionPolicy = getCompletionPolicy(s1); + System.err.println(completionPolicy); + assertTrue(completionPolicy instanceof DummyCompletionPolicy); + } + + @Test(expected = BeanDefinitionParsingException.class) + public void testStepParserNoCommitIntervalOrCompletionPolicy() throws Exception { + new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/StepParserNoCommitIntervalOrCompletionPolicyTests-context.xml"); + } + @Test public void testTaskletStepWithBadStepListener() throws Exception { loadContextWithBadListener("org/springframework/batch/core/configuration/xml/StepParserBadStepListenerTests-context.xml"); @@ -72,10 +121,25 @@ public class StepParserTests { try { new ClassPathXmlApplicationContext(contextLocation); fail("Context should not load!"); - } - catch (BeanDefinitionParsingException e) { + } catch (BeanDefinitionParsingException e) { assertTrue(e.getMessage().contains("'ref' and 'class'")); } } + @SuppressWarnings("unchecked") + private CompletionPolicy getCompletionPolicy(Step s1) throws NoSuchFieldException, IllegalAccessException { + Field taskletField = TaskletStep.class.getDeclaredField("tasklet"); + taskletField.setAccessible(true); + Tasklet tasklet = (Tasklet) taskletField.get(s1); + Field chunkProviderField = ChunkOrientedTasklet.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + ChunkProvider chunkProvider = (ChunkProvider) chunkProviderField.get(tasklet); + Field repeatOperationsField = SimpleChunkProvider.class.getDeclaredField("repeatOperations"); + repeatOperationsField.setAccessible(true); + RepeatOperations repeatOperations = (RepeatOperations) repeatOperationsField.get(chunkProvider); + Field completionPolicyField = RepeatTemplate.class.getDeclaredField("completionPolicy"); + completionPolicyField.setAccessible(true); + return (CompletionPolicy) completionPolicyField.get(repeatOperations); + } + } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalCompletionPolicyTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalCompletionPolicyTests-context.xml new file mode 100644 index 000000000..d4c170986 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalCompletionPolicyTests-context.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml new file mode 100644 index 000000000..9dad4bb5b --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml new file mode 100644 index 000000000..a9c3c247a --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserNoCommitIntervalOrCompletionPolicyTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserNoCommitIntervalOrCompletionPolicyTests-context.xml new file mode 100644 index 000000000..523878166 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserNoCommitIntervalOrCompletionPolicyTests-context.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml index 630008b47..b04b1cafc 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml @@ -34,4 +34,7 @@ + + + \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/jobs/multilineOrderJob.xml b/spring-batch-samples/src/main/resources/jobs/multilineOrderJob.xml index 67e641655..af791ada6 100644 --- a/spring-batch-samples/src/main/resources/jobs/multilineOrderJob.xml +++ b/spring-batch-samples/src/main/resources/jobs/multilineOrderJob.xml @@ -14,7 +14,7 @@ - +