diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java index e2a4e7321..24452f8cd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java @@ -60,10 +60,18 @@ public class FlowParser extends AbstractFlowParser { private static final String TO_ATTRIBUTE = "to"; private static final String RESTART_ATTRIBUTE = "restart"; private static final String EXIT_STATUS_ATTRIBUTE = "exit-status"; + private static final List TRANSITION_TYPES = new ArrayList(); + static { + TRANSITION_TYPES.add(NEXT_ATTRIBUTE); + TRANSITION_TYPES.add(STOP_ATTRIBUTE); + TRANSITION_TYPES.add(END_ATTRIBUTE); + TRANSITION_TYPES.add(FAIL_ATTRIBUTE); + } + + private String flowName; private String jobFactoryRef; private StepParser stepParser = new StepParser(); - private String flowName; /** * @param flowName The name of the flow @@ -157,12 +165,10 @@ public class FlowParser extends AbstractFlowParser { } boolean transitionElementExists = false; - List patterns = new ArrayList(); - for (String transitionName : new String[] { NEXT_ATTRIBUTE, STOP_ATTRIBUTE, END_ATTRIBUTE, FAIL_ATTRIBUTE }) { - List transitionElements = DomUtils.getChildElementsByTagName(element, transitionName); - for (Element transitionElement : transitionElements) { - verifyUniquePattern(transitionElement, patterns, element, parserContext); - list.addAll(parseTransitionElement(transitionElement, stepId, stateDef, parserContext)); + List childElements = DomUtils.getChildElements(element); + for(Element childElement : childElements) { + if(isChildElementTransitionElement(childElement)) { + list.addAll(parseTransitionElement(childElement, stepId, stateDef, parserContext)); transitionElementExists = true; } } @@ -186,6 +192,17 @@ public class FlowParser extends AbstractFlowParser { return list; } + private static boolean isChildElementTransitionElement(Element childElement) { + boolean isTransitionElement = false; + + if(TRANSITION_TYPES.contains(childElement.getLocalName())) { + int index = TRANSITION_TYPES.indexOf(childElement.getLocalName()); + isTransitionElement = TRANSITION_TYPES.get(index) != null; + } + + return isTransitionElement; + } + protected static Collection parseTransitionElement(Element transitionElement, String stateId, BeanDefinition stateDef, ParserContext parserContext) { FlowExecutionStatus status = getBatchStatusFromEndTransitionName(transitionElement.getNodeName()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests.java new file mode 100644 index 000000000..a6230f886 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013 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.jsr.configuration.xml; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.core.*; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.batch.api.Batchlet; +import javax.batch.runtime.context.StepContext; +import javax.inject.Inject; + +import static org.junit.Assert.assertEquals; + +/** + *

+ * Unit tests around {@link FlowParser}. + *

+ * + * @author Chris Schaefer + * @since 3.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class FlowParserTests { + @Autowired + private Job job; + + @Autowired + private JobLauncher jobLauncher; + + @Test + public void testDuplicateTransitionPatternsAllowed() throws Exception { + JobExecution stoppedExecution = jobLauncher.run(job, new JobParameters()); + assertEquals(ExitStatus.STOPPED.getExitCode(), stoppedExecution.getExitStatus().getExitCode()); + + JobExecution endedExecution = jobLauncher.run(job, new JobParameters()); + assertEquals(ExitStatus.COMPLETED.getExitCode(), endedExecution.getExitStatus().getExitCode()); + + JobExecution failedExecution = jobLauncher.run(job, new JobParameters()); + assertEquals(ExitStatus.FAILED.getExitCode(), failedExecution.getExitStatus().getExitCode()); + } + + public static class TestBatchlet implements Batchlet { + private static int CNT; + + @Inject + private StepContext stepContext; + + @Override + public String process() throws Exception { + String exitCode = "DISTINCT"; + + if("step3".equals(stepContext.getStepName())) { + exitCode = CNT % 2 == 0 ? "DISTINCT" : "RESTART"; + CNT++; + } + + return exitCode; + } + + @Override + public void stop() throws Exception { } + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests-context.xml new file mode 100644 index 000000000..08a05af29 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/FlowParserTests-context.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +