BATCH-1011:
spring-batch-2.0.xsd: * Renamed <stop/> to <pause/> * Added <fail/> StepParser.java * Added capabilities to parse <pause/> and <fail/> * Changed the semantics of the status= attribute so that it modifies the ExitStatus instead of BatchStatus. * BatchStatus is determined by the element (end, fail, pause) EndState.java * Added storage for ExitStatus in addition to the existing BatchStatus since both will need to be saved from the parser
This commit is contained in:
@@ -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<RuntimeBeanReference> list = new ArrayList<RuntimeBeanReference>();
|
||||
|
||||
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<Element> nextElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "next");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Element> stopElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "stop");
|
||||
nextElements.addAll(stopElements);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Element> endElements = (List<Element>) 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<Element> transitionElements = (List<Element>) 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<RuntimeBeanReference> 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -581,10 +581,11 @@
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="stop">
|
||||
<xsd:element name="pause">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
@@ -605,7 +606,8 @@
|
||||
<xsd:element name="end">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
@@ -616,17 +618,31 @@
|
||||
Hint: always include a default transition with on="*".</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="status" use="optional" default="COMPLETED">
|
||||
<xsd:attribute name="status" use="optional" type="xsd:string" default="COMPLETED">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The BatchStatus value to end on, defaults to COMPLETED.</xsd:documentation>
|
||||
<xsd:documentation>The ExitStatus value to end on, defaults to COMPLETED.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="fail">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Declares job should fail at this point. BatchStatus will be FAILED. ExitStatus is configurable.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="on" type="xsd:string" use="required" >
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>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="*".</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="status" use="optional" type="xsd:string" default="FAILED">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The ExitStatus value to end on, defaults to FAILED.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="COMPLETED"/>
|
||||
<xsd:enumeration value="FAILED"/>
|
||||
<xsd:enumeration value="STOPPED"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<job id="job">
|
||||
<step name="failingStep">
|
||||
<end on="FAILED" status="FAILED"/>
|
||||
<end on="FAILED" status="EARLY TERMINATION (COMPLETE)"/>
|
||||
<next on="*" to="step2"/>
|
||||
</step>
|
||||
<step name="step2" />
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/batch"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<beans:import resource="common-context.xml" />
|
||||
|
||||
<job id="job">
|
||||
<step name="failingStep">
|
||||
<fail on="FAILED" status="EARLY TERMINATION (FAIL)"/>
|
||||
<next on="*" to="step2"/>
|
||||
</step>
|
||||
<step name="step2" />
|
||||
</job>
|
||||
|
||||
</beans:beans>
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<job id="job">
|
||||
<step name="step1">
|
||||
<stop on="COMPLETED" to="decision"/>
|
||||
<pause on="COMPLETED" to="decision"/>
|
||||
</step>
|
||||
<decision id="decision" decider="decider">
|
||||
<next on="FOO" to="step2"/>
|
||||
|
||||
Reference in New Issue
Block a user