OPEN - issue BATCH-1380: Make <flow/> a top-level element, so it can be shared or strategised in a job

Add <flow/> at top level and as element in a flow.
This commit is contained in:
dsyer
2009-11-12 15:02:45 +00:00
parent f2fecf79af
commit 3bf6bd59da
18 changed files with 573 additions and 55 deletions

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.2.6.200908051215-RELEASE]]></pluginVersion>
<pluginVersion><![CDATA[2.2.7.200910202224-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
@@ -62,6 +62,38 @@
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/scope/util/PlaceholderTargetSourceCustomEditorTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/scope/util/PlaceholderTargetSourceErrorTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementParentAttributeParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/DuplicateTransitionJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/launch/support/error.xml</config>
<config>src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/FlowJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/JobParserNextOutOfScopeTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/JobParserUnreachableStepInFlowTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/JobParserUnreachableStepTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserReferenceTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/launch/support/launcher-with-environment.xml</config>
<config>src/test/resources/org/springframework/batch/core/launch/support/launcher-with-locator.xml</config>
<config>src/test/resources/org/springframework/batch/core/resource/ListPreparedStatementSetterTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeMultipleFinalJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/support/parent-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/SplitInterruptedJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserBadRetryListenerTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserBadStepListenerTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalCompletionPolicyTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserNoCommitIntervalOrCompletionPolicyTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/scope/StepScopePerformanceTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/scope/StepScopeProxyTargetClassIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml</config>
</configs>
<configSets>
<configSet>

View File

@@ -42,12 +42,14 @@ import org.w3c.dom.NodeList;
* @author Dave Syer
*
*/
public class FlowParser extends AbstractSingleBeanDefinitionParser {
public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionParser {
private static final String ID_ATTR = "id";
private static final String STEP_ELE = "step";
private static final String FLOW_ELE = "flow";
private static final String DECISION_ELE = "decision";
private static final String SPLIT_ELE = "split";
@@ -72,27 +74,36 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
private static final InlineStepParser stepParser = new InlineStepParser();
private static final FlowElementParser flowParser = new FlowElementParser();
private static final DecisionParser decisionParser = new DecisionParser();
// For generating unique state names for end transitions
private static int endCounter = 0;
private final String flowName;
private String flowName;
private final String jobFactoryRef;
private String jobFactoryRef;
/**
* Construct a {@link FlowParser} with the specified name and using the
* provided job repository ref.
* Convenience method for subclasses to set up the flow name for error
* reporting.
*
* @param flowName the name of the flow
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
* @param flowName
*/
public FlowParser(String flowName, String jobFactoryRef) {
protected void setFlowName(String flowName) {
this.flowName = flowName;
this.jobFactoryRef = jobFactoryRef;
}
/**
* Convenience method for subclasses to set the job factory reference if it
* is available (null is fine, but the quality of error reports is better if
* it is available).
*
* @param jobFactoryRef
*/
protected void setJobFactoryRef(String jobFactoryRef) {
this.jobFactoryRef = jobFactoryRef;
}
/*
@@ -111,6 +122,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
List<BeanDefinition> stateTransitions = new ArrayList<BeanDefinition>();
SplitParser splitParser = new SplitParser(jobFactoryRef);
@@ -134,6 +146,10 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
else if (nodeName.equals(DECISION_ELE)) {
stateTransitions.addAll(decisionParser.parse(child, parserContext));
}
else if (nodeName.equals(FLOW_ELE)) {
stateTransitions.addAll(flowParser.parse(child, parserContext));
stepExists = true;
}
else if (nodeName.equals(SPLIT_ELE)) {
stateTransitions.addAll(splitParser
.parse(child, new ParserContext(parserContext.getReaderContext(), parserContext
@@ -141,7 +157,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
stepExists = true;
}
if (Arrays.asList(STEP_ELE, DECISION_ELE, SPLIT_ELE).contains(nodeName)) {
if (Arrays.asList(STEP_ELE, DECISION_ELE, SPLIT_ELE, FLOW_ELE).contains(nodeName)) {
reachableElementMap.put(child.getAttribute(ID_ATTR), findReachableElements(child));
if (startElement == null) {
startElement = child.getAttribute(ID_ATTR);
@@ -170,10 +186,6 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
boolean dummy = managedList.addAll(stateTransitions);
builder.addPropertyValue("stateTransitions", managedList);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.popAndRegisterContainingComponent();
}
/**

View File

@@ -31,6 +31,7 @@ public class CoreNamespaceHandler extends NamespaceHandlerSupport {
*/
public void init() {
this.registerBeanDefinitionParser("job", new JobParser());
this.registerBeanDefinitionParser("flow", new TopLevelFlowParser());
this.registerBeanDefinitionParser("step", new TopLevelStepParser());
this.registerBeanDefinitionParser("job-repository", new JobRepositoryParser());
this.registerBeanDefinitionParser("job-listener", new TopLevelJobListenerParser());

View File

@@ -55,7 +55,7 @@ public class DecisionParser {
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.DecisionState");
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(idAttribute);
return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
return InlineFlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2006-2008 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 java.util.Collection;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Internal parser for the &lt;flow/&gt; elements inside a job..
*
* @see JobParser
*
* @author Dave Syer
*
*/
public class FlowElementParser {
/**
* Parse the flow and turn it into a list of transitions.
*
* @param element the &lt;flow/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a collection of bean definitions for
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
*/
public Collection<BeanDefinition> parse(Element element, ParserContext parserContext) {
String refAttribute = element.getAttribute("ref");
String idAttribute = element.getAttribute("id");
BeanDefinitionBuilder stateBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.FlowState");
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(idAttribute);
return InlineFlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2006-2008 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 org.springframework.batch.core.job.flow.support.SimpleFlow;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author Dave Syer
*
*/
public class InlineFlowParser extends AbstractFlowParser {
/**
* Construct a {@link InlineFlowParser} with the specified name and using the
* provided job repository ref.
*
* @param flowName the name of the flow
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
*/
public InlineFlowParser(String flowName, String jobFactoryRef) {
setFlowName(flowName);
setJobFactoryRef(jobFactoryRef);
}
/*
* (non-Javadoc)
*
* @see AbstractSingleBeanDefinitionParser#getBeanClass(Element)
*/
@Override
protected Class<SimpleFlow> getBeanClass(Element element) {
return SimpleFlow.class;
}
/**
* @param element the top level element containing a flow definition
* @param parserContext the {@link ParserContext}
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.popAndRegisterContainingComponent();
}
}

View File

@@ -60,7 +60,7 @@ public class InlineStepParser extends AbstractStepParser {
parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepId));
stateBuilder.addConstructorArgReference(stepId);
return FlowParser.getNextElements(parserContext, stepId, stateBuilder.getBeanDefinition(), element);
return InlineFlowParser.getNextElements(parserContext, stepId, stateBuilder.getBeanDefinition(), element);
}

View File

@@ -101,7 +101,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
}
}
else {
FlowParser flowParser = new FlowParser(jobName, jobName);
InlineFlowParser flowParser = new InlineFlowParser(jobName, jobName);
BeanDefinition flowDef = flowParser.parse(element, parserContext);
builder.addPropertyValue("flow", flowDef);
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.batch.core.configuration.xml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -45,10 +44,11 @@ public class SplitParser {
private final String jobFactoryRef;
/**
* Construct a {@link FlowParser} using the provided job repository ref.
* Construct a {@link InlineFlowParser} using the provided job repository
* ref.
*
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
* from the enclosing tag
*/
public SplitParser(String jobFactoryRef) {
this.jobFactoryRef = jobFactoryRef;
@@ -60,8 +60,8 @@ public class SplitParser {
* @param element the &lt;split/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a collection of bean definitions for
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
*/
public Collection<BeanDefinition> parse(Element element, ParserContext parserContext) {
@@ -83,21 +83,28 @@ public class SplitParser {
parserContext.getReaderContext().error("A <split/> must contain at least two 'flow' elements.", element);
}
Collection<BeanDefinition> flows = new ArrayList<BeanDefinition>();
@SuppressWarnings("unchecked")
Collection<Object> flows = new ManagedList();
int i = 0;
for (Element nextElement : flowElements) {
FlowParser flowParser = new FlowParser(idAttribute + "#" + i, jobFactoryRef);
flows.add(flowParser.parse(nextElement, parserContext));
InlineFlowParser flowParser = new InlineFlowParser(idAttribute + "#" + i, jobFactoryRef);
String ref = nextElement.getAttribute("ref");
if (StringUtils.hasText(ref)) {
if (nextElement.getElementsByTagName("*").getLength() > 0) {
parserContext.getReaderContext().error("A <flow/> in a <split/> must have ref= or nested <flow/>, but not both.", nextElement);
}
flows.add(new RuntimeBeanReference(ref));
}
else {
flows.add(flowParser.parse(nextElement, parserContext));
}
i++;
}
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(flows);
stateBuilder.addConstructorArgValue(managedList);
stateBuilder.addConstructorArgValue(flows);
stateBuilder.addConstructorArgValue(idAttribute);
return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
return InlineFlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2006-2008 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 org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author Dave Syer
*
*/
public class TopLevelFlowParser extends AbstractFlowParser {
private static final String ID_ATTR = "id";
/**
* @param element the top level element containing a flow definition
* @param parserContext the {@link ParserContext}
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
setFlowName(element.getAttribute(ID_ATTR));
super.doParse(element, parserContext, builder);
}
}

View File

@@ -347,6 +347,13 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In
JobInstance jobInstance = execution.getJobInstance();
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName());
if (stepExecutionPartOfExistingJobExecution(execution, lastStepExecution)) {
// If the last execution of this step was in the same job, it's probably
// intentional so we want to run it again...
logger.info(String.format("Duplicate step [%s] detected in execution of job=[%s]. " +
"If either step fails, both will be executed again on restart.", step.getName(), name));
lastStepExecution = null;
}
StepExecution currentStepExecution = lastStepExecution;
if (shouldStart(lastStepExecution, jobInstance, step)) {
@@ -395,6 +402,17 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In
}
/**
* Detect whether a step execution belongs to this job execution.
* @param jobExecution the current job execution
* @param stepExecution an existing step execution
* @return
*/
private boolean stepExecutionPartOfExistingJobExecution(JobExecution jobExecution, StepExecution stepExecution) {
return stepExecution != null && stepExecution.getJobExecutionId() != null
&& stepExecution.getJobExecutionId().equals(jobExecution.getId());
}
/**
* Convenience method for subclasses so they can change the state of a
* {@link StepExecution} if necessary. Use with care (and not at all
@@ -430,9 +448,9 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In
}
if (stepStatus == BatchStatus.UNKNOWN) {
throw new JobRestartException("Cannot restart step from UNKNOWN status. "
throw new JobRestartException("Cannot restart step from UNKNOWN status. "
+ "The last execution ended with a failure that could not be rolled back, "
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
+ "so it may be dangerous to proceed. Manual intervention is probably necessary.");
}
if ((stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false)

View File

@@ -221,7 +221,8 @@ public class SimpleFlow implements Flow, InitializingBean {
for (StateTransition stateTransition : stateTransitions) {
State state = stateTransition.getState();
stateMap.put(state.getName(), state);
String stateName = state.getName();
stateMap.put(stateName, state);
}
for (StateTransition stateTransition : stateTransitions) {

View File

@@ -0,0 +1,53 @@
/*
* 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.job.flow.support.state;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.FlowExecutor;
/**
* State that delegates to a Flow
*
* @author Dave Syer
* @since 2.0
*/
public class FlowState extends AbstractState {
private final Flow flow;
/**
* @param name
*/
public FlowState(Flow flow, String name) {
super(name);
this.flow = flow;
}
@Override
public FlowExecutionStatus handle(FlowExecutor executor) throws Exception {
return flow.start(executor).getStatus();
}
/* (non-Javadoc)
* @see org.springframework.batch.core.job.flow.State#isEndState()
*/
public boolean isEndState() {
return false;
}
}

View File

@@ -127,6 +127,24 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="flow">
<xsd:annotation>
<xsd:documentation>
Defines a flow composed of a set of steps and
transitions between steps.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="description" minOccurs="0"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="flowGroup" />
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:complexType>
</xsd:element>
<xsd:element name="job-listener">
<xsd:annotation>
<xsd:documentation>
@@ -295,6 +313,18 @@
</xsd:annotation>
<xsd:complexType>
<xsd:group ref="flowGroup" minOccurs="0" maxOccurs="unbounded" />
<xsd:attribute name="ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.batch.core.job.flow.Flow"><![CDATA[
The flow that will execute at this point in the job.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.batch.core.job.flow.Flow" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:group ref="transitions" />
@@ -318,6 +348,30 @@
<xsd:attributeGroup ref="nextAttribute" />
</xsd:complexType>
</xsd:element>
<xsd:element name="flow">
<xsd:annotation>
<xsd:documentation>
Declares job should include an externalized flow here.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:group ref="transitions" minOccurs="0" maxOccurs="unbounded" />
<xsd:attribute name="id" type="xsd:ID" use="required" />
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.batch.core.job.flow.Flow"><![CDATA[
The flow that will execute at this point in the job.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.batch.core.job.flow.Flow" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="nextAttribute" />
</xsd:complexType>
</xsd:element>
<xsd:element name="decision">
<xsd:annotation>
<xsd:documentation>

View File

@@ -0,0 +1,104 @@
/*
* 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.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dave Syer
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class FlowJobParserTests {
@Autowired
@Qualifier("job1")
private Job job1;
@Autowired
@Qualifier("job2")
private Job job2;
@Autowired
@Qualifier("job3")
private Job job3;
@Autowired
@Qualifier("job4")
private Job job4;
@Autowired
private JobRepository jobRepository;
@Before
public void setUp() {
MapJobRepositoryFactoryBean.clear();
}
@Test
public void testFlowJob() throws Exception {
assertNotNull(job1);
JobExecution jobExecution = jobRepository.createJobExecution(job1.getName(), new JobParameters());
job1.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(4, jobExecution.getStepExecutions().size());
}
@Test
public void testFlowJobWithNestedTransitions() throws Exception {
assertNotNull(job2);
JobExecution jobExecution = jobRepository.createJobExecution(job2.getName(), new JobParameters());
job2.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(3, jobExecution.getStepExecutions().size());
}
@Test
public void testFlowJobWithNoSteps() throws Exception {
assertNotNull(job3);
JobExecution jobExecution = jobRepository.createJobExecution(job3.getName(), new JobParameters());
job3.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
}
@Test
public void testFlowInSplit() throws Exception {
assertNotNull(job4);
JobExecution jobExecution = jobRepository.createJobExecution(job4.getName(), new JobParameters());
job4.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(4, jobExecution.getStepExecutions().size());
}
}

View File

@@ -30,6 +30,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.Before;
@@ -325,12 +326,32 @@ public class SimpleJobTests {
public void testStepAlreadyComplete() throws Exception {
stepExecution1.setStatus(BatchStatus.COMPLETED);
jobRepository.add(stepExecution1);
jobExecution.setEndTime(new Date());
jobRepository.update(jobExecution);
jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters);
job.execute(jobExecution);
assertEquals(0, jobExecution.getFailureExceptions().size());
assertEquals(1, jobExecution.getStepExecutions().size());
assertEquals(stepExecution2.getStepName(), jobExecution.getStepExecutions().iterator().next().getStepName());
}
@Test
public void testStepAlreadyCompleteInSameExecution() throws Exception {
List<Step> steps = new ArrayList<Step>();
steps.add(step1);
steps.add(step2);
// Two steps with the same name should both be executed, since
// the user might actually want it to happen twice. On a restart
// it would be executed twice again, even if it failed on the
// second execution. This seems reasonable.
steps.add(step2);
job.setSteps(steps);
job.execute(jobExecution);
assertEquals(0, jobExecution.getFailureExceptions().size());
assertEquals(3, jobExecution.getStepExecutions().size());
assertEquals(stepExecution1.getStepName(), jobExecution.getStepExecutions().iterator().next().getStepName());
}
@Test
public void testNoSteps() throws Exception {
job.setSteps(new ArrayList<Step>());
@@ -362,6 +383,7 @@ public class SimpleJobTests {
Throwable e = jobExecution.getAllFailureExceptions().get(0);
assertSame(exception, e);
jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters);
job.execute(jobExecution);
e = jobExecution.getAllFailureExceptions().get(0);
assertSame(exception, e);

View File

@@ -100,9 +100,10 @@ public class FlowJobTests {
SimpleFlow flow = new SimpleFlow("job");
List<StateTransition> transitions = new ArrayList<StateTransition>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1")));
@@ -121,9 +122,10 @@ public class FlowJobTests {
List<StateTransition> transitions = new ArrayList<StateTransition>();
transitions.add(StateTransition.createStateTransition(new StateSupport("step1", FlowExecutionStatus.FAILED),
"step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1")));
@@ -328,9 +330,10 @@ public class FlowJobTests {
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end"));
transitions.add(StateTransition
.createStateTransition(new EndState(FlowExecutionStatus.STOPPED, "end"), "step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1")));
@@ -369,9 +372,10 @@ public class FlowJobTests {
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end"));
transitions.add(StateTransition
.createStateTransition(new EndState(FlowExecutionStatus.STOPPED, "end"), "step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end1")));
@@ -395,18 +399,21 @@ public class FlowJobTests {
public void testBranching() throws Exception {
SimpleFlow flow = new SimpleFlow("job");
List<StateTransition> transitions = new ArrayList<StateTransition>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "COMPLETED",
StepState step1 = new StepState(new StubStep("step1"));
transitions.add(StateTransition.createStateTransition(step1, "step2"));
transitions.add(StateTransition.createStateTransition(step1, "COMPLETED",
"step3"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end1")));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step3")), ExitStatus.FAILED
StepState step3 = new StepState(new StubStep("step3"));
transitions.add(StateTransition.createStateTransition(step3, ExitStatus.FAILED
.getExitCode(), "end2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step3")),
transitions.add(StateTransition.createStateTransition(step3,
ExitStatus.COMPLETED.getExitCode(), "end3"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end2")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end3")));
@@ -447,18 +454,21 @@ public class FlowJobTests {
List<StateTransition> transitions = new ArrayList<StateTransition>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "decision"));
transitions.add(StateTransition.createStateTransition(new DecisionState(decider, "decision"), "step2"));
DecisionState decision = new DecisionState(decider, "decision");
transitions.add(StateTransition.createStateTransition(decision, "step2"));
transitions.add(StateTransition
.createStateTransition(new DecisionState(decider, "decision"), "SWITCH", "step3"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")),
.createStateTransition(decision, "SWITCH", "step3"));
StepState step2 = new StepState(new StubStep("step2"));
transitions.add(StateTransition.createStateTransition(step2,
ExitStatus.COMPLETED.getExitCode(), "end0"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), ExitStatus.FAILED
transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED
.getExitCode(), "end1"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end1")));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step3")), ExitStatus.FAILED
StepState step3 = new StepState(new StubStep("step3"));
transitions.add(StateTransition.createStateTransition(step3, ExitStatus.FAILED
.getExitCode(), "end2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step3")),
transitions.add(StateTransition.createStateTransition(step3,
ExitStatus.COMPLETED.getExitCode(), "end3"));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end2")));
transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end3")));

View File

@@ -0,0 +1,40 @@
<?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.1.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="job1">
<step id="s1" parent="step1" next="job1.flow"/>
<flow id="job1.flow" ref="flow" next="s4" />
<step id="s4" parent="step4" />
</job>
<job id="job2">
<flow id="job2.flow" ref="flow">
<next on="*" to="job2.s1"/>
<next on="FAILED" to="job2.s2"/>
</flow>
<step id="job2.s1" parent="step1" />
<step id="job2.s2" parent="step2" />
</job>
<job id="job3">
<flow id="job3.flow" ref="flow" />
</job>
<job id="job4">
<split id="split">
<flow ref="flow" />
<flow ref="flow" />
</split>
</job>
<flow id="flow">
<step id="s2" parent="step2" next="s3"/>
<step id="s3" parent="step3" />
</flow>
</beans:beans>