OPEN - issue BATCH-890: Stop transition in XML namespace

Add stop and end elements through EndState.
This commit is contained in:
dsyer
2008-10-29 07:19:19 +00:00
parent 6185714368
commit a4e78858e2
13 changed files with 244 additions and 179 deletions

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.batch.core.configuration.xml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.batch.core.job.flow.DecisionState;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
@@ -25,7 +23,6 @@ import org.springframework.batch.flow.StateTransition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
@@ -52,23 +49,12 @@ public class DecisionParser {
public Collection<RuntimeBeanReference> parse(Element element, ParserContext parserContext) {
String refAttribute = element.getAttribute("decider");
String idAttribute = element.getAttribute("id");
Collection<RuntimeBeanReference> list = new ArrayList<RuntimeBeanReference>();
@SuppressWarnings("unchecked")
List<Element> nextElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "next");
for (Element nextElement : nextElements) {
String onAttribute = nextElement.getAttribute("on");
String nextAttribute = nextElement.getAttribute("to");
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(DecisionState.class);
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(parserContext.getReaderContext().generateBeanName(stateBuilder.getBeanDefinition()));
list.add(StepParser.getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(),
onAttribute, nextAttribute));
}
return list;
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(DecisionState.class);
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(idAttribute);
return StepParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}
}

View File

@@ -57,13 +57,6 @@ public class FlowParser {
stateTransitions.addAll(decisionParser.parse(stepElement, parserContext));
}
@SuppressWarnings("unchecked")
List<Element> pauseElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "pause");
PauseParser pauseParser = new PauseParser();
for (Element stepElement : pauseElements) {
stateTransitions.add(pauseParser.parse(stepElement, parserContext));
}
@SuppressWarnings("unchecked")
List<Element> splitElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "split");
SplitParser splitParser = new SplitParser();

View File

@@ -1,56 +0,0 @@
/*
* 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 org.springframework.batch.core.job.flow.PauseState;
import org.springframework.batch.flow.StateTransition;
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;pause/&gt; elements inside a job. A pause element
* causes the job flow to end and will resume on the next execution at the next
* state. Used by the {@link JobParser}.
*
* @see JobParser
*
* @author Dave Syer
*
*/
public class PauseParser {
/**
* Parse the pause and turn it into a transition.
*
* @param element the &lt;pause/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a bean definitions for a {@link StateTransition}
* instances objects
*/
public RuntimeBeanReference parse(Element element, ParserContext parserContext) {
String nextAttribute = element.getAttribute("next");
String idAttribute = element.getAttribute("id");
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(PauseState.class);
stateBuilder.addConstructorArgValue(idAttribute);
return StepParser.getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(), "*",
nextAttribute);
}
}

View File

@@ -22,13 +22,11 @@ import java.util.List;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.flow.SplitState;
import org.springframework.batch.flow.StateTransition;
import org.springframework.beans.factory.BeanCreationException;
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.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -76,40 +74,8 @@ public class SplitParser {
stateBuilder.addConstructorArgValue(managedList);
stateBuilder.addConstructorArgValue(idAttribute);
// TODO: extract common code from StepParser
// TODO: allow TaskExecutor etc. to be set
Collection<RuntimeBeanReference> list = new ArrayList<RuntimeBeanReference>();
String shortNextAttribute = element.getAttribute("next");
boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute);
if (hasNextAttribute) {
list.add(StepParser.getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(), null,
shortNextAttribute));
}
@SuppressWarnings("unchecked")
List<Element> nextElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "next");
// If there are no next elements then this must be an end state
if (nextElements.isEmpty() && !hasNextAttribute) {
list.add(StepParser.getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(), null, null));
}
else {
// Otherwise we need to capture the "to" state
for (Element nextElement : nextElements) {
String onAttribute = nextElement.getAttribute("on");
String nextAttribute = nextElement.getAttribute("to");
if (hasNextAttribute && onAttribute.equals("*")) {
throw new BeanCreationException("Duplicate transition pattern found for '*' "
+ "(only specify one of next= attribute at step level and next element with on='*')");
}
list.add(StepParser.getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(),
onAttribute, nextAttribute));
}
}
return list;
return StepParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}

View File

@@ -19,7 +19,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.flow.EndState;
import org.springframework.batch.core.job.flow.StepState;
import org.springframework.batch.flow.StateTransition;
import org.springframework.beans.factory.BeanCreationException;
@@ -46,6 +48,9 @@ import org.w3c.dom.Element;
*/
public class StepParser {
// For generating unique state names for end transitions
private static int endCounter = 0;
/**
* Parse the step and turn it into a list of transitions.
*
@@ -56,55 +61,73 @@ public class StepParser {
*/
public Collection<RuntimeBeanReference> parse(Element element, ParserContext parserContext) {
String refAttribute = element.getAttribute("name");
RuntimeBeanReference stateDef = new RuntimeBeanReference(element.getAttribute("name"));
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
stateBuilder.addConstructorArgValue(stateDef);
return getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}
/**
* @param parserContext
* @param stateDef
* @param element
* @return a collection of {@link StateTransition} references
*/
public static Collection<RuntimeBeanReference> getNextElements(ParserContext parserContext,
BeanDefinition stateDef, Element element) {
Collection<RuntimeBeanReference> list = new ArrayList<RuntimeBeanReference>();
String shortNextAttribute = element.getAttribute("next");
boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute);
if (hasNextAttribute) {
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), null,
shortNextAttribute));
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);
// If there are no next elements then this must be an end state
if (nextElements.isEmpty() && !hasNextAttribute) {
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), null, null));
}
else {
// Otherwise we need to capture the "to" state
for (Element nextElement : nextElements) {
String onAttribute = nextElement.getAttribute("on");
String nextAttribute = nextElement.getAttribute("to");
if (hasNextAttribute && onAttribute.equals("*")) {
throw new BeanCreationException("Duplicate transition pattern found for '*' "
+ "(only specify one of next= attribute at step level and next element with on='*')");
}
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute),
onAttribute, nextAttribute));
for (Element nextElement : nextElements) {
String onAttribute = nextElement.getAttribute("on");
String nextAttribute = nextElement.getAttribute("to");
if (hasNextAttribute && onAttribute.equals("*")) {
throw new BeanCreationException("Duplicate transition pattern found for '*' "
+ "(only specify one of next= attribute at step level and next element with on='*')");
}
String name = nextElement.getNodeName();
if ("stop".equals(name) || "end".equals(name)) {
String statusName = nextElement.getAttribute("status");
BatchStatus status = StringUtils.hasText(statusName) ? BatchStatus.valueOf(statusName)
: BatchStatus.STOPPED;
String nextOnEnd = StringUtils.hasText(statusName) ? null : nextAttribute;
BeanDefinitionBuilder endBuilder = BeanDefinitionBuilder.genericBeanDefinition(EndState.class);
endBuilder.addConstructorArgValue(status);
String endName = "end" + endCounter;
endCounter++;
endBuilder.addConstructorArgValue(endName);
list.add(getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd));
nextAttribute = endName;
}
list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute));
}
if (list.isEmpty() && !hasNextAttribute) {
list.add(getStateTransitionReference(parserContext, stateDef, null, null));
}
return list;
}
/**
* @param parserContext
* @param runtimeBeanReference
* @param onAttribute
* @param nextAttribute
* @return
*/
private RuntimeBeanReference getStateTransitionReference(ParserContext parserContext,
RuntimeBeanReference runtimeBeanReference, String onAttribute, String nextAttribute) {
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
stateBuilder.addConstructorArgValue(runtimeBeanReference);
return getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(), onAttribute,
nextAttribute);
}
/**
@@ -115,8 +138,7 @@ public class StepParser {
* @return a bean definition for a {@link StateTransition}
*/
public static RuntimeBeanReference getStateTransitionReference(ParserContext parserContext,
BeanDefinition stateDefinition, String on,
String next) {
BeanDefinition stateDefinition, String on, String next) {
BeanDefinitionBuilder nextBuilder = BeanDefinitionBuilder.genericBeanDefinition(StateTransition.class);
nextBuilder.addConstructorArgValue(stateDefinition);

View File

@@ -0,0 +1,46 @@
package org.springframework.batch.core.job.flow;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.flow.AbstractState;
import org.springframework.batch.flow.FlowExecution;
import org.springframework.batch.flow.State;
/**
* {@link State} implementation for ending a job if it is in progress and
* continuing if just starting.
*
* @author Dave Syer
*
*/
public class EndState extends AbstractState<JobFlowExecutor> {
private final BatchStatus status;
/**
* @param name
*/
EndState(BatchStatus status, String name) {
super(name);
this.status = status;
}
/**
* Set the status as long the {@link JobExecution} is in progress. If this
* is the first place we came after a restart we do nothing (otherwise the
* same outcome that ended the job on the last run will occur).
*
* @see State#handle(Object)
*/
@Override
public String handle(JobFlowExecutor context) throws Exception {
JobExecution jobExecution = context.getJobExecution();
// If there are no step executions, then we are at the beginning of a
// restart
if (!jobExecution.getStepExecutions().isEmpty()) {
jobExecution.setStatus(status);
}
return FlowExecution.COMPLETED;
}
}

View File

@@ -84,21 +84,52 @@ specific match will be chosen to select the next step. Hint: always include a de
</xsd:complexType>
</xsd:element>
<xsd:element name="pause">
<xsd:element name="stop">
<xsd:annotation>
<xsd:documentation>
Declares job should be paused 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.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="next" type="xsd:string" use="required" >
<xsd:attribute name="on" type="xsd:string" use="required" >
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the next step to execute after job is resumed.]]>
</xsd:documentation>
<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=&quot;*&quot;.</xsd:documentation>
</xsd:annotation></xsd:attribute>
<xsd:attribute name="to" type="xsd:string" use="required" >
<xsd:annotation>
<xsd:documentation>The name of the step to go to next.
Must resolve to one of the other steps in this job.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:complexType>
</xsd:element>
<xsd:element name="end">
<xsd:annotation>
<xsd:documentation>
Declares job should be stop at this point and provides optional pointer where execution should continue.
</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=&quot;*&quot;.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="status" use="optional" default="COMPLETED">
<xsd:annotation>
<xsd:documentation>The BatchStatus value to end on, defaults to COMPLETED.</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="COMPLETED"></xsd:enumeration>
<xsd:enumeration value="FAILED"></xsd:enumeration>
<xsd:enumeration value="STOPPED"></xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -111,6 +142,7 @@ specific match will be chosen to select the next step. Hint: always include a de
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="transitionType">
<xsd:attribute name="id" type="xsd:ID" use="required" />
<xsd:attribute name="decider" type="xsd:string" use="required" >
<xsd:annotation>
<xsd:documentation>
@@ -151,7 +183,6 @@ The decider is a reference to a JobExecutionDecider that can produce a status to
<xsd:element ref="step" />
<xsd:element ref="split" />
<xsd:element ref="decision" />
<xsd:element ref="pause" />
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
@@ -173,11 +204,11 @@ The decider is a reference to a JobExecutionDecider that can produce a status to
<xsd:complexType name="transitionType">
<xsd:sequence>
<xsd:element ref="next" minOccurs="0" maxOccurs="unbounded" >
<xsd:annotation>
<xsd:documentation>A sequence of next elements specifies the possible transitions from this step to the next one.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:choice minOccurs="0" maxOccurs="unbounded" >
<xsd:element ref="next"/>
<xsd:element ref="stop"/>
<xsd:element ref="end"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>

View File

@@ -25,6 +25,7 @@ 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.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,7 +39,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PauseJobParserTests {
public class StopJobParserTests {
@Autowired
@Qualifier("job")
@@ -53,15 +54,22 @@ public class PauseJobParserTests {
}
@Test
public void testPauseState() throws Exception {
public void testStopState() throws Exception {
assertNotNull(job);
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.PAUSED, jobExecution.getStatus());
assertEquals(BatchStatus.STOPPED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
assertEquals(1, jobExecution.getStepExecutions().size());
}
public static class TestDecider implements JobExecutionDecider {
public String decide(JobExecution jobExecution) {
return "FOO";
}
}
}

View File

@@ -123,12 +123,70 @@ public class FlowJobTests {
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testEndStateStopped() throws Exception {
SimpleFlow<JobFlowExecutor> flow = new SimpleFlow<JobFlowExecutor>("job");
Collection<StateTransition<JobFlowExecutor>> transitions = new ArrayList<StateTransition<JobFlowExecutor>>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end"));
transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.STOPPED, "end"), "step2"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2"))));
flow.setStateTransitions(transitions);
job.setFlow(flow);
job.afterPropertiesSet();
try {
job.doExecute(jobExecution);
fail("Expected JobInterruptedException");
}
catch (JobInterruptedException e) {
// expected
}
assertEquals(1, jobExecution.getStepExecutions().size());
}
public void testEndStateFailed() throws Exception {
SimpleFlow<JobFlowExecutor> flow = new SimpleFlow<JobFlowExecutor>("job");
Collection<StateTransition<JobFlowExecutor>> transitions = new ArrayList<StateTransition<JobFlowExecutor>>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end"));
transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.FAILED, "end"), "step2"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2"))));
flow.setStateTransitions(transitions);
job.setFlow(flow);
job.afterPropertiesSet();
job.doExecute(jobExecution);
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testEndStateStoppedWithRestart() throws Exception {
SimpleFlow<JobFlowExecutor> flow = new SimpleFlow<JobFlowExecutor>("job");
Collection<StateTransition<JobFlowExecutor>> transitions = new ArrayList<StateTransition<JobFlowExecutor>>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end"));
transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.STOPPED, "end"), "step2"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2"))));
flow.setStateTransitions(transitions);
job.setFlow(flow);
job.afterPropertiesSet();
// To test a restart we have to use the AbstractJob.execute()...
job.execute(jobExecution);
assertEquals(BatchStatus.STOPPED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
jobExecution = jobRepository.createJobExecution("job", new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testBranching() throws Exception {
SimpleFlow<JobFlowExecutor> flow = new SimpleFlow<JobFlowExecutor>("job");
Collection<StateTransition<JobFlowExecutor>> transitions = new ArrayList<StateTransition<JobFlowExecutor>>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "step2"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "COMPLETED", "step3"));
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "COMPLETED",
"step3"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2"))));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step3"))));
flow.setStateTransitions(transitions);

View File

@@ -9,7 +9,7 @@
<beans:bean id="decider" class="org.springframework.batch.core.configuration.xml.DecisionJobParserTests$TestDecider"/>
<job id="job">
<decision decider="decider">
<decision id="decision" decider="decider">
<next on="FOO" to="step1"/>
<next on="*" to="step2"/>
</decision>

View File

@@ -6,9 +6,16 @@
<beans:import resource="common-context.xml" />
<beans:bean id="decider" class="org.springframework.batch.core.configuration.xml.StopJobParserTests$TestDecider"/>
<job id="job">
<step name="step1" next="pause"/>
<pause id="pause" next="step2"/>
<step name="step1">
<stop on="COMPLETED" to="decision"/>
</step>
<decision id="decision" decider="decider">
<next on="FOO" to="step2"/>
<end on="*" status="FAILED"/>
</decision>
<step name="step2" />
</job>

View File

@@ -35,6 +35,14 @@ public abstract class AbstractState<T> implements State<T> {
return name;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getSimpleName()+": name=["+name+"]";
}
public abstract String handle(T context) throws Exception;
}

View File

@@ -77,11 +77,7 @@ public class SplitState<T> extends AbstractState<T> {
tasks.add(task);
try {
taskExecutor.execute(new Runnable() {
public void run() {
task.run();
}
});
taskExecutor.execute(task);
}
catch (TaskRejectedException e) {
throw new FlowExecutionException("TaskExecutor rejected task for flow=" + flow.getName());