diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java index c69989bd4..ab6d54503 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java @@ -44,33 +44,33 @@ import org.w3c.dom.NodeList; */ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionParser { - private static final String ID_ATTR = "id"; + protected static final String ID_ATTR = "id"; - private static final String STEP_ELE = "step"; + protected static final String STEP_ELE = "step"; - private static final String FLOW_ELE = "flow"; + protected static final String FLOW_ELE = "flow"; - private static final String DECISION_ELE = "decision"; + protected static final String DECISION_ELE = "decision"; - private static final String SPLIT_ELE = "split"; + protected static final String SPLIT_ELE = "split"; - private static final String NEXT_ATTR = "next"; + protected static final String NEXT_ATTR = "next"; - private static final String NEXT_ELE = "next"; + protected static final String NEXT_ELE = "next"; - private static final String END_ELE = "end"; + protected static final String END_ELE = "end"; - private static final String FAIL_ELE = "fail"; + protected static final String FAIL_ELE = "fail"; - private static final String STOP_ELE = "stop"; + protected static final String STOP_ELE = "stop"; - private static final String ON_ATTR = "on"; + protected static final String ON_ATTR = "on"; - private static final String TO_ATTR = "to"; + protected static final String TO_ATTR = "to"; - private static final String RESTART_ATTR = "restart"; + protected static final String RESTART_ATTR = "restart"; - private static final String EXIT_CODE_ATTR = "exit-code"; + protected static final String EXIT_CODE_ATTR = "exit-code"; private static final InlineStepParser stepParser = new InlineStepParser(); @@ -79,7 +79,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar private static final DecisionParser decisionParser = new DecisionParser(); // For generating unique state names for end transitions - private static int endCounter = 0; + protected static int endCounter = 0; private String jobFactoryRef; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java index 9956c132d..3b3239773 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java @@ -29,8 +29,6 @@ import org.w3c.dom.Element; */ public class TopLevelFlowParser extends AbstractFlowParser { - private static final String ID_ATTR = "id"; - private static final String ABSTRACT_ATTR = "abstract"; /** 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 c8782ff31..e8fd0b94a 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 @@ -108,12 +108,23 @@ public class EndState extends AbstractState { } } - executor.addExitStatus(code); + setExitStatus(executor, code); + return status; } } + /** + * Performs any logic to update the exit status for the current flow. + * + * @param executor {@link FlowExecutor} for the current flow + * @param code The exit status to save + */ + protected void setExitStatus(FlowExecutor executor, String code) { + executor.addExitStatus(code); + } + /* * (non-Javadoc) * 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 5ffdab85d..fc97db05f 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 @@ -46,26 +46,15 @@ import org.w3c.dom.NodeList; * @since 3.0 */ public class FlowParser extends AbstractFlowParser { - private static final String DECISION_ELEMENT = "decision"; - private static final String SPLIT_ELEMENT = "split"; - private static final String STEP_ELEMENT = "step"; - private static final String FLOW_ELEMENT = "flow"; - private static final String ID_ATTRIBUTE = "id"; private static final String NEXT_ATTRIBUTE = "next"; - private static final String STOP_ATTRIBUTE = "stop"; - private static final String FAIL_ATTRIBUTE = "fail"; - private static final String END_ATTRIBUTE = "end"; - private static final String ON_ATTRIBUTE = "on"; - 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); + TRANSITION_TYPES.add(NEXT_ELE); + TRANSITION_TYPES.add(STOP_ELE); + TRANSITION_TYPES.add(END_ELE); + TRANSITION_TYPES.add(FAIL_ELE); } private String flowName; @@ -103,13 +92,13 @@ public class FlowParser extends AbstractFlowParser { if (node instanceof Element) { String nodeName = node.getLocalName(); Element child = (Element) node; - if (nodeName.equals(STEP_ELEMENT)) { + if (nodeName.equals(STEP_ELE)) { stateTransitions.addAll(stepParser.parse(child, parserContext, builder)); - } else if(nodeName.equals(SPLIT_ELEMENT)) { + } else if(nodeName.equals(SPLIT_ELE)) { stateTransitions.addAll(new SplitParser(flowName).parse(child, parserContext)); - } else if(nodeName.equals(DECISION_ELEMENT)) { + } else if(nodeName.equals(DECISION_ELE)) { stateTransitions.addAll(new DecisionParser().parse(child, parserContext, flowName)); - } else if(nodeName.equals(FLOW_ELEMENT)) { + } else if(nodeName.equals(FLOW_ELE)) { stateTransitions.addAll(parseFlow(child, parserContext, builder)); } } @@ -194,9 +183,9 @@ public class FlowParser extends AbstractFlowParser { protected static Collection parseTransitionElement(Element transitionElement, String stateId, BeanDefinition stateDef, ParserContext parserContext) { FlowExecutionStatus status = getBatchStatusFromEndTransitionName(transitionElement.getNodeName()); - String onAttribute = transitionElement.getAttribute(ON_ATTRIBUTE); - String restartAttribute = transitionElement.getAttribute(RESTART_ATTRIBUTE); - String nextAttribute = transitionElement.getAttribute(TO_ATTRIBUTE); + String onAttribute = transitionElement.getAttribute(ON_ATTR); + String restartAttribute = transitionElement.getAttribute(RESTART_ATTR); + String nextAttribute = transitionElement.getAttribute(TO_ATTR); if (!StringUtils.hasText(nextAttribute)) { nextAttribute = restartAttribute; @@ -207,4 +196,60 @@ public class FlowParser extends AbstractFlowParser { return createTransition(status, onAttribute, nextAttribute, exitCodeAttribute, stateDef, parserContext, abandon); } + + /** + * @param status The batch status that this transition will set. Use + * BatchStatus.UNKNOWN if not applicable. + * @param on The pattern that this transition should match. Use null for + * "no restriction" (same as "*"). + * @param next The state to which this transition should go. Use null if not + * applicable. + * @param exitCode The exit code that this transition will set. Use null to + * default to batchStatus. + * @param stateDef The bean definition for the current state + * @param parserContext the parser context for the bean factory + * @param a collection of + * {@link org.springframework.batch.core.job.flow.support.StateTransition} + * references + */ + protected static Collection createTransition(FlowExecutionStatus status, String on, String next, + String exitCode, BeanDefinition stateDef, ParserContext parserContext, boolean abandon) { + + BeanDefinition endState = null; + + if (status.isEnd()) { + + BeanDefinitionBuilder endBuilder = BeanDefinitionBuilder + .genericBeanDefinition("org.springframework.batch.core.jsr.job.flow.support.state.EndState"); + + boolean exitCodeExists = StringUtils.hasText(exitCode); + + endBuilder.addConstructorArgValue(status); + + endBuilder.addConstructorArgValue(exitCodeExists ? exitCode : status.getName()); + + String endName = (status == FlowExecutionStatus.STOPPED ? STOP_ELE + : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE) + + (endCounter++); + endBuilder.addConstructorArgValue(endName); + + endBuilder.addConstructorArgValue(abandon); + + String nextOnEnd = exitCodeExists ? null : next; + endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd); + next = endName; + + } + + Collection list = new ArrayList(); + list.add(getStateTransitionReference(parserContext, stateDef, on, next)); + if (endState != null) { + // + // Must be added after the state to ensure that the state is the + // first in the list + // + list.add(endState); + } + return list; + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/EndState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/EndState.java new file mode 100644 index 000000000..7a7833055 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/support/state/EndState.java @@ -0,0 +1,85 @@ +/* + * 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.job.flow.support.state; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; +import org.springframework.batch.core.job.flow.FlowExecutor; +import org.springframework.batch.core.job.flow.State; + +/** + * {@link State} implementation for ending a job per JSR-352 rules if it is + * in progress and continuing if just starting. + * + * @author Michael Minella + * @since 3.0 + */ +public class EndState extends org.springframework.batch.core.job.flow.support.state.EndState { + + /** + * @param status The {@link FlowExecutionStatus} to end with + * @param name The name of the state + */ + public EndState(FlowExecutionStatus status, String name) { + super(status, status.getName(), name); + } + + /** + * @param status The {@link FlowExecutionStatus} to end with + * @param name The name of the state + */ + public EndState(FlowExecutionStatus status, String code, String name) { + super(status, code, name, false); + } + + /** + * @param status The {@link FlowExecutionStatus} to end with + * @param name The name of the state + * @param abandon flag to indicate that previous step execution can be + * marked as abandoned (if there is one) + * + */ + public EndState(FlowExecutionStatus status, String code, String name, boolean abandon) { + super(status, code, name, abandon); + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.job.flow.support.state.EndState#setExitStatus(org.springframework.batch.core.job.flow.FlowExecutor, java.lang.String) + */ + @Override + protected void setExitStatus(FlowExecutor executor, String code) { + StepExecution stepExecution = executor.getStepExecution(); + + if(!isNonDefaultExitStatus(code)) { + stepExecution.getJobExecution().setExitStatus(new ExitStatus(code)); + } + } + + /** + * @param curStatus the exit code to be evaluated + * @return true if the value matches a known exit code + */ + protected boolean isNonDefaultExitStatus(String curStatus) { + return curStatus == null || + curStatus.equals(ExitStatus.COMPLETED.getExitCode()) || + curStatus.equals(ExitStatus.EXECUTING.getExitCode()) || + curStatus.equals(ExitStatus.FAILED.getExitCode()) || + curStatus.equals(ExitStatus.NOOP.getExitCode()) || + curStatus.equals(ExitStatus.STOPPED.getExitCode()) || + curStatus.equals(ExitStatus.UNKNOWN.getExitCode()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/support/state/EndStateTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/support/state/EndStateTests.java new file mode 100644 index 000000000..f811a920e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/support/state/EndStateTests.java @@ -0,0 +1,51 @@ +/* + * 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.job.flow.support.state; + +import static org.junit.Assert.assertEquals; + +import javax.batch.api.AbstractBatchlet; +import javax.batch.runtime.BatchRuntime; +import javax.batch.runtime.BatchStatus; +import javax.batch.runtime.JobExecution; + +import org.junit.Test; +import org.springframework.batch.core.jsr.JsrTestUtils; + +/** + * Tests for the JSR-352 version of {@link EndState} + * + * @author Michael Minella + */ +public class EndStateTests { + + @Test + public void test() throws Exception { + JobExecution jobExecution = JsrTestUtils.runJob("jobWithEndTransition", null, 10000l); + + assertEquals(BatchStatus.COMPLETED, jobExecution.getBatchStatus()); + assertEquals("SUCCESS", jobExecution.getExitStatus()); + assertEquals(1, BatchRuntime.getJobOperator().getStepExecutions(jobExecution.getExecutionId()).size()); + } + + public static class EndStateBatchlet extends AbstractBatchlet { + + @Override + public String process() throws Exception { + return "GOOD"; + } + } +} diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jobWithEndTransition.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jobWithEndTransition.xml new file mode 100644 index 000000000..17f948608 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jobWithEndTransition.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + +