diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java index 8435674e1..9a12a86ce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java @@ -21,6 +21,7 @@ import java.util.List; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.StepTransition; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -33,7 +34,7 @@ import org.w3c.dom.Element; * Internal parser for the <step/> elements inside a job. A step element * references a bean definition for a {@link Step} and goes on to (optionally) * list a set of transitions from that step to others with <next on="pattern" - * to="stepName"/>. Used by the {@link JobParser}. + * to="stepName"/>. Used by the {@link JobParser}. * * @see JobParser * @@ -47,18 +48,27 @@ public class StepParser { * * @param element the <step/gt; element to parse * @param parserContext the parser context for the bean factory - * @return a collection of bean definitions for {@link StepTransition} objects + * @return a collection of bean definitions for {@link StepTransition} + * objects */ public Collection parse(Element element, ParserContext parserContext) { String refAttribute = element.getAttribute("name"); Collection list = new ArrayList(); + + String shortNextAttribute = element.getAttribute("next"); + boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute); + if (hasNextAttribute) { + list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), "*", + shortNextAttribute)); + } + @SuppressWarnings("unchecked") List nextElements = (List) DomUtils.getChildElementsByTagName(element, "next"); // If there are no next elements then this must be an end state - if (nextElements.isEmpty()) { + if (nextElements.isEmpty() && !hasNextAttribute) { list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), "*", null)); } else { @@ -66,6 +76,10 @@ public class StepParser { for (Element nextElement : nextElements) { String onAttribute = nextElement.getAttribute("on"); String nextAttribute = nextElement.getAttribute("to"); + if (hasNextAttribute && onAttribute.equals("*")) { + throw new BeanCreationException("Duplicate transition pattern found for '*' " + + "(only specify one of next= attribute at step level and next element with on='*')"); + } list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), onAttribute, nextAttribute)); } 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 a364652d0..a7eac48b7 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 @@ -39,7 +39,7 @@ Defines a stage in job processing. The name attribute has to match the id of a bean definition for a - Step. + Step. The next attribute is a synonym for <next on="*" .../> @@ -47,6 +47,7 @@ + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java new file mode 100644 index 000000000..d294e6cee --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java @@ -0,0 +1,81 @@ +/* + * 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.core.configuration.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +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.repository.JobRepository; +import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ClassUtils; + +/** + * @author Dave Syer + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class NextAttributeJobParserTests { + + @Autowired + private Job job; + + @Autowired + private JobRepository jobRepository; + + @Before + public void setUp() { + MapJobRepositoryFactoryBean.clear(); + } + + @Test + public void testNextAttributeSunnyDay() throws Exception { + assertNotNull(job); + JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters()); + job.execute(jobExecution); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + assertEquals(2, jobExecution.getStepExecutions().size()); + } + + @Test + public void testNextAttributeWithNestedElement() throws Exception { + try { + new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(), + "NextAttributeFailureJobParserTests-context.xml")); + fail("Expected BeanCreationException"); + } + catch (BeanDefinitionStoreException e) { + // expected + String message = e.getCause().getMessage(); + assertTrue("Wrong message; " + message, message.startsWith("Duplicate transition pattern")); + } + } + +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml new file mode 100644 index 000000000..b0b42d768 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests-context.xml new file mode 100644 index 000000000..691305b79 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file