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:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <flow/> 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 <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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user