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 c68511bf4..e821c09b8 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 @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -49,6 +51,11 @@ import org.w3c.dom.NamedNodeMap; */ public class StepParser { + private static final String NEXT = "next"; + private static final String END = "end"; + private static final String FAIL = "fail"; + private static final String PAUSE = "pause"; + // For generating unique state names for end transitions private static int endCounter = 0; @@ -103,62 +110,26 @@ public class StepParser { Collection list = new ArrayList(); - String shortNextAttribute = element.getAttribute("next"); + String shortNextAttribute = element.getAttribute(NEXT); boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute); if (hasNextAttribute) { list.add(getStateTransitionReference(parserContext, stateDef, null, shortNextAttribute)); } - @SuppressWarnings("unchecked") - List nextElements = (List) DomUtils.getChildElementsByTagName(element, "next"); - @SuppressWarnings("unchecked") - List stopElements = (List) DomUtils.getChildElementsByTagName(element, "stop"); - nextElements.addAll(stopElements); - @SuppressWarnings("unchecked") - List endElements = (List) DomUtils.getChildElementsByTagName(element, "end"); - nextElements.addAll(endElements); - - for (Element nextElement : nextElements) { - String onAttribute = nextElement.getAttribute("on"); - String nextAttribute = nextElement.getAttribute("to"); - if (hasNextAttribute && onAttribute.equals("*")) { - parserContext.getReaderContext().error("Duplicate transition pattern found for '*' " - + "(only specify one of next= attribute at step level and next element with on='*')", - element); - } - - RuntimeBeanReference additionalState = null; - - String name = nextElement.getNodeName(); - if ("stop".equals(name) || "end".equals(name)) { - - String statusName = nextElement.getAttribute("status"); - String status = StringUtils.hasText(statusName) ? statusName : "STOPPED"; - String nextOnEnd = StringUtils.hasText(statusName) ? null : nextAttribute; - - BeanDefinitionBuilder endBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); - endBuilder.addConstructorArgValue(status); - String endName = "stop".equals(name) ? "end" + (endCounter++) : null; - - endBuilder.addConstructorArgValue(endName); - additionalState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd); - nextAttribute = endName; - - } - list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute)); - if(additionalState != null) - { - // - // Must be added after the state to ensure that the state is the first in the list - // - list.add(additionalState); + boolean transitionExists = false; + for(String transitionName : new String[]{NEXT, PAUSE, END, FAIL}) + { + @SuppressWarnings("unchecked") + List transitionElements = (List) DomUtils.getChildElementsByTagName(element, transitionName); + for (Element transitionElement : transitionElements) { + parseTransitionElement(parserContext, stateDef, element, list, hasNextAttribute, transitionElement); + transitionExists = true; } } - if(hasNextAttribute && nextElements.isEmpty()) + if(hasNextAttribute && !transitionExists) { - list.add(getStateTransitionReference(parserContext, stateDef, "FAILED", null)); + list.add(getStateTransitionReference(parserContext, stateDef, ExitStatus.FAILED.getExitCode(), null)); } if (list.isEmpty() && !hasNextAttribute) { @@ -168,6 +139,73 @@ public class StepParser { return list; } + /** + * @param parserContext + * @param stateDef + * @param element + * @param list + * @param hasNextAttribute + * @param transitionElement + */ + private static void parseTransitionElement(ParserContext parserContext, BeanDefinition stateDef, Element element, + Collection list, boolean hasNextAttribute, Element transitionElement) { + String onAttribute = transitionElement.getAttribute("on"); + String nextAttribute = transitionElement.getAttribute("to"); + if (hasNextAttribute && onAttribute.equals("*")) { + parserContext.getReaderContext().error("Duplicate transition pattern found for '*' " + + "(only specify one of next= attribute at step level and next element with on='*')", + element); + } + + RuntimeBeanReference endState = null; + + String name = transitionElement.getNodeName(); + if (PAUSE.equals(name) || END.equals(name) || FAIL.equals(name)) { + + BatchStatus batchStatus = getBatchStatusFromEndTransitionName(name); + BeanDefinitionBuilder endBuilder = + BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); + endBuilder.addConstructorArgValue(batchStatus); + + String statusName = transitionElement.getAttribute("status"); + String exitStatus = StringUtils.hasText(statusName) ? statusName : batchStatus.toString(); + endBuilder.addConstructorArgValue(new ExitStatus(exitStatus)); + + String endName = PAUSE.equals(name) ? "end" + (endCounter++) : null; + endBuilder.addConstructorArgValue(endName); + + String nextOnEnd = StringUtils.hasText(statusName) ? null : nextAttribute; + endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd); + nextAttribute = endName; + + } + list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute)); + if(endState != null) + { + // + // Must be added after the state to ensure that the state is the first in the list + // + list.add(endState); + } + } + + /** + * @param name An end transition name + * @return the BatchStatus corresponding to the transition name + */ + private static BatchStatus getBatchStatusFromEndTransitionName(String name) { + if(PAUSE.equals(name)){ + return BatchStatus.STOPPED; + } + else if(END.equals(name)){ + return BatchStatus.COMPLETED; + } + else if(FAIL.equals(name)){ + return BatchStatus.FAILED; + } + throw new IllegalStateException("No BatchStatus defined for transition: [" + name + "]"); + } + /** * @param parserContext the parser context * @param stateDefinition a reference to the state implementation diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java index 46acf6986..448e5d80b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java @@ -17,6 +17,7 @@ package org.springframework.batch.core.job.flow.support.state; import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.job.flow.FlowExecution; import org.springframework.batch.core.job.flow.FlowExecutor; @@ -32,13 +33,22 @@ import org.springframework.batch.core.job.flow.support.State; public class EndState extends AbstractState { private final BatchStatus status; + private final ExitStatus exitStatus; /** * @param name */ public EndState(BatchStatus status, String name) { + this(status, new ExitStatus(status.toString()), name); + } + + /** + * @param name + */ + public EndState(BatchStatus status, ExitStatus exitStatus, String name) { super(name); this.status = status; + this.exitStatus = exitStatus; } /** @@ -56,6 +66,7 @@ public class EndState extends AbstractState { synchronized (jobExecution) { if (!jobExecution.getStepExecutions().isEmpty()) { jobExecution.upgradeStatus(status); + jobExecution.setExitStatus(exitStatus); } return FlowExecution.COMPLETED; } 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 8e9f7c4bb..5fea6d6bb 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 @@ -581,10 +581,11 @@ - + - Declares job should be stop at this point and provides pointer where execution should continue. + Declares job should be stop at this point and provides pointer where execution should continue when + the job is restarted. @@ -605,7 +606,8 @@ - Declares job should be stop at this point and provides optional pointer where execution should continue. + Declares job should end at this point, without the possibility of restart. + BatchStatus will be COMLETED. ExitStatus is configurable. @@ -616,17 +618,31 @@ Hint: always include a default transition with on="*". - + - The BatchStatus value to end on, defaults to COMPLETED. + The ExitStatus value to end on, defaults to COMPLETED. + + + + + + + + Declares job should fail at this point. BatchStatus will be FAILED. ExitStatus is configurable. + + + + + + A pattern to match against the exit status code. Use * and ? as wildcard characters. + When a step finishes the most specific match will be chosen to select the next step. + Hint: always include a default transition with on="*". + + + + + The ExitStatus value to end on, defaults to FAILED. - - - - - - - diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java index 52416318a..cf28533bc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java @@ -51,11 +51,16 @@ public class EndTransitionJobParserTests { } @Test - public void testNextAttributeFailedDefault() throws Exception { + public void testEndTransition() throws Exception { assertNotNull(job); JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters()); job.execute(jobExecution); + + // TODO: BATCH-1011 assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); + // assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + // assertEquals("EARLY TERMINATION (COMPLETE)", jobExecution.getExitStatus().getExitCode()); + assertEquals(1, jobExecution.getStepExecutions().size()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java new file mode 100644 index 000000000..20194a7f5 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java @@ -0,0 +1,66 @@ +/* + * 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 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.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dan Garrette + * @since 2.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class FailTransitionJobParserTests { + + @Autowired + private Job job; + + @Autowired + private JobRepository jobRepository; + + @Before + public void setUp() { + MapJobRepositoryFactoryBean.clear(); + } + + @Test + public void testFailTransition() throws Exception { + assertNotNull(job); + JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters()); + job.execute(jobExecution); + assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); + + // TODO: BATCH-1011 + // assertEquals("EARLY TERMINATION (FAIL)", jobExecution.getExitStatus().getExitCode()); + + assertEquals(1, jobExecution.getStepExecutions().size()); + } + +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml index ff1b0aedc..807a23b48 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml @@ -10,7 +10,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests-context.xml new file mode 100644 index 000000000..ecb66c45c --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests-context.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml index 2b531f566..208cd833a 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml @@ -10,7 +10,7 @@ - +