diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index 573e5d9bb..3e21095c1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -17,6 +17,7 @@ package org.springframework.batch.core.configuration.xml; import java.util.List; +import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -50,6 +51,10 @@ public abstract class AbstractStepParser { private static final String TASKLET_REF_ATTR = "ref"; + private static final String BEAN_ELE = "bean"; + + private static final String REF_ELE = "ref"; + private static final String TASKLET_ELE = "tasklet"; private static final String CHUNK_ELE = "chunk"; @@ -101,9 +106,9 @@ public abstract class AbstractStepParser { if (StringUtils.hasText(jobFactoryRef)) { bd.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef); } - + Element description = DomUtils.getChildElementByTagName(stepElement, "description"); - if (description!=null) { + if (description != null) { bd.setDescription(description.getTextContent()); } @@ -120,32 +125,88 @@ public abstract class AbstractStepParser { String taskletRef = taskletElement.getAttribute(TASKLET_REF_ATTR); @SuppressWarnings("unchecked") List chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE); - if (StringUtils.hasText(taskletRef)) { - if (chunkElements.size() > 0) { - parserContext.getReaderContext().error( - "The <" + CHUNK_ELE + "/> element can't be combined with the '" + TASKLET_REF_ATTR + "=\"" - + taskletRef + "\"' attribute specification for <" + taskletElement.getNodeName() - + "/>", taskletElement); - } - parseTaskletRef(taskletRef, bd.getPropertyValues()); - } - else if (chunkElements.size() == 1) { + @SuppressWarnings("unchecked") + List beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE); + @SuppressWarnings("unchecked") + List refElements = DomUtils.getChildElementsByTagName(taskletElement, REF_ELE); + + validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef, + chunkElements, beanElements, refElements); + + if (chunkElements.size() == 1) { chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified); } - else if (!stepUnderspecified) { - parserContext.getReaderContext().error( - "Step [" + stepElement.getAttribute(ID_ATTR) + "] has neither a <" + CHUNK_ELE - + "/> element nor a '" + TASKLET_REF_ATTR + "' attribute referencing a Tasklet.", - taskletElement); + else { + BeanMetadataElement bme = null; + if (StringUtils.hasText(taskletRef)) { + bme = new RuntimeBeanReference(taskletRef); + } + else if (beanElements.size() == 1) { + bme = parserContext.getDelegate().parseBeanDefinitionElement(beanElements.get(0)); + } + else if (refElements.size() == 1) { + bme = (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0), + null); + } + + if (bme != null) { + bd.getPropertyValues().addPropertyValue("tasklet", bme); + } } handleTaskletElement(taskletElement, bd, parserContext); } - private void parseTaskletRef(String taskletRef, MutablePropertyValues propertyValues) { - if (StringUtils.hasText(taskletRef)) { - RuntimeBeanReference taskletBeanRef = new RuntimeBeanReference(taskletRef); - propertyValues.addPropertyValue("tasklet", taskletBeanRef); + private void validateTaskletAttributesAndSubelements(Element taskletElement, ParserContext parserContext, + boolean stepUnderspecified, String taskletRef, List chunkElements, List beanElements, + List refElements) { + int total = (StringUtils.hasText(taskletRef) ? 1 : 0) + chunkElements.size() + beanElements.size() + + refElements.size(); + + StringBuilder found = new StringBuilder(); + if (total > 1) { + if (StringUtils.hasText(taskletRef)) { + found.append("'" + TASKLET_REF_ATTR + "' attribute, "); + } + if (chunkElements.size() == 1) { + found.append("<" + CHUNK_ELE + "/> element, "); + } + else if (chunkElements.size() > 1) { + found.append(chunkElements.size() + " <" + CHUNK_ELE + "/> elements, "); + } + if (beanElements.size() == 1) { + found.append("<" + BEAN_ELE + "/> element, "); + } + else if (beanElements.size() > 1) { + found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, "); + } + if (refElements.size() == 1) { + found.append("<" + REF_ELE + "/> element, "); + } + else if (refElements.size() > 1) { + found.append(refElements.size() + " <" + REF_ELE + "/> elements, "); + } + found.delete(found.length() - 2, found.length()); + } + else { + found.append("None"); + } + + String error = null; + if (stepUnderspecified) { + if (total > 1) { + error = "may not have more than"; + } + } + else if (total != 1) { + error = "must have exactly"; + } + + if (error != null) { + parserContext.getReaderContext().error( + "The <" + taskletElement.getTagName() + "/> element " + error + " one of: '" + TASKLET_REF_ATTR + + "' attribute, <" + CHUNK_ELE + "/> element, <" + BEAN_ELE + "/> attribute, or <" + + REF_ELE + "/> element. Found: " + found + ".", taskletElement); } } @@ -250,7 +311,7 @@ public abstract class AbstractStepParser { } String throttleLimit = taskletElement.getAttribute("throttle-limit"); if (StringUtils.hasText(throttleLimit)) { - propertyValues.addPropertyValue("throttleLimit", throttleLimit); + propertyValues.addPropertyValue("throttleLimit", throttleLimit); } } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd index 3a50fd696..021d86130 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd @@ -372,6 +372,8 @@ + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java index 4ab0aaccc..fee5df4f7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java @@ -26,49 +26,70 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - +import org.springframework.test.util.ReflectionTestUtils; /** * @author Thomas Risberg - * */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class StepWithSimpleTaskJobParserTests { - + @Autowired private Job job; @Autowired private JobRepository jobRepository; - - @Autowired - @Qualifier("tasklet") - private AbstractTestComponent tasklet; - + @Autowired @Qualifier("listener") private TestListener listener; - + @Before public void setUp() { MapJobRepositoryFactoryBean.clear(); } @Test - public void testStepWithTask() throws Exception { + public void testJob() throws Exception { assertNotNull(job); + assertTrue(job instanceof FlowJob); JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters()); + + TestTasklet t1 = assertTasklet(job, "step1", "t1"); + TestTasklet t2 = assertTasklet(job, "step2", "t2"); + TestTasklet t3 = assertTasklet(job, "step3", "t3"); + TestTasklet t4 = assertTasklet(job, "step4", "t4"); + job.execute(jobExecution); + + assertTrue(t1.isExecuted()); + assertTrue(t2.isExecuted()); + assertTrue(t3.isExecuted()); + assertTrue(t4.isExecuted()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); - assertEquals(2, jobExecution.getStepExecutions().size()); - assertTrue(tasklet.isExecuted()); + assertEquals(4, jobExecution.getStepExecutions().size()); assertTrue(listener.isExecuted()); } + + private TestTasklet assertTasklet(Job job, String stepName, String taskletName) { + Step step = ((FlowJob) job).getStep(stepName); + assertTrue(step instanceof TaskletStep); + Object tasklet = ReflectionTestUtils.getField(step, "tasklet"); + assertTrue(tasklet instanceof TestTasklet); + TestTasklet testTasklet = (TestTasklet) tasklet; + assertEquals(taskletName, testTasklet.getName()); + assertTrue(!testTasklet.isExecuted()); + return testTasklet; + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java index ff9cb420b..72c23c65a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java @@ -7,10 +7,19 @@ import org.springframework.batch.repeat.RepeatStatus; public class TestTasklet extends AbstractTestComponent implements Tasklet { + private String name; + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { executed = true; return RepeatStatus.FINISHED; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests-context.xml index d3a8ad0c6..dd695edf6 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests-context.xml @@ -8,15 +8,29 @@ - + - + + + + + + + + + + + + + + + @@ -24,6 +38,14 @@ - + + + + + + + + + \ No newline at end of file