OPEN - issue BATCH-679: Non-sequential execution

Add XML parser support for split, decision and pause
This commit is contained in:
dsyer
2008-10-27 18:56:38 +00:00
parent 3d8acc66b3
commit 35d156161b
19 changed files with 672 additions and 65 deletions

View File

@@ -0,0 +1,74 @@
/*
* 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 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;
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;
/**
* Internal parser for the <decision/> elements inside a job. A decision
* element references a bean definition for a {@link JobExecutionDecider} and
* goes on to list a set of transitions to other states with <next
* on="pattern" to="stepName"/>. Used by the {@link JobParser}.
*
* @see JobParser
*
* @author Dave Syer
*
*/
public class DecisionParser {
/**
* Parse the decision and turn it into a list of transitions.
*
* @param element the <decision/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a collection of bean definitions for {@link StateTransition}
* instances objects
*/
public Collection<RuntimeBeanReference> parse(Element element, ParserContext parserContext) {
String refAttribute = element.getAttribute("decider");
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;
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import org.springframework.batch.flow.Flow;
import org.springframework.batch.flow.SimpleFlow;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
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.xml.DomUtils;
import org.w3c.dom.Element;
/**
* @author Dave Syer
*
*/
public class FlowParser {
/**
* @param element the top level element containing a flow definition
* @param parserContext the {@link ParserContext}
* @param flowName the name of the flow
* @return a bean definition for a {@link Flow}
*/
public AbstractBeanDefinition parse(Element element, ParserContext parserContext, String flowName) {
List<RuntimeBeanReference> stateTransitions = new ArrayList<RuntimeBeanReference>();
@SuppressWarnings("unchecked")
List<Element> stepElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "step");
StepParser stepParser = new StepParser();
for (Element stepElement : stepElements) {
stateTransitions.addAll(stepParser.parse(stepElement, parserContext));
}
@SuppressWarnings("unchecked")
List<Element> decisionElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "decision");
DecisionParser decisionParser = new DecisionParser();
for (Element stepElement : decisionElements) {
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();
for (Element stepElement : splitElements) {
stateTransitions.addAll(splitParser.parse(stepElement, parserContext));
}
BeanDefinitionBuilder flowBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleFlow.class);
flowBuilder.addConstructorArgValue(flowName );
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(stateTransitions);
flowBuilder.addPropertyValue("stateTransitions", managedList);
AbstractBeanDefinition flowDef = flowBuilder.getBeanDefinition();
parserContext.getReaderContext().registerWithGeneratedName(flowDef);
return flowDef;
}
}

View File

@@ -15,21 +15,14 @@
*/
package org.springframework.batch.core.configuration.xml;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.flow.SimpleFlow;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
@@ -62,22 +55,11 @@ public class JobParser extends AbstractBeanDefinitionParser {
}
builder.addPropertyReference("jobRepository", repositoryAttribute);
StepParser stepParser = new StepParser();
List<RuntimeBeanReference> stepTransitions = new ArrayList<RuntimeBeanReference>();
@SuppressWarnings("unchecked")
List<Element> stepElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "step");
for (Element stepElement : stepElements) {
stepTransitions.addAll(stepParser.parse(stepElement, parserContext));
}
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(stepTransitions);
BeanDefinitionBuilder flowBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleFlow.class);
flowBuilder.addConstructorArgValue(jobName );
flowBuilder.addPropertyValue("stateTransitions", managedList);
builder.addPropertyValue("flow", flowBuilder.getBeanDefinition());
FlowParser flowParser = new FlowParser();
AbstractBeanDefinition flowDef = flowParser.parse(element, parserContext, jobName);
builder.addPropertyValue("flow", flowDef);
return builder.getBeanDefinition();
}

View File

@@ -0,0 +1,56 @@
/*
* 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

@@ -0,0 +1,116 @@
/*
* 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 java.util.ArrayList;
import java.util.Collection;
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;
/**
* Internal parser for the &lt;split/&gt; elements inside a job. A split element
* references a bean definition for a {@link JobExecutionDecider} and goes on to
* list a set of transitions to other states with &lt;next on="pattern"
* to="stepName"/&gt;. Used by the {@link JobParser}.
*
* @see JobParser
*
* @author Dave Syer
*
*/
public class SplitParser {
/**
* Parse the split and turn it into a list of transitions.
*
* @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 StateTransition}
* instances objects
*/
public Collection<RuntimeBeanReference> parse(Element element, ParserContext parserContext) {
String idAttribute = element.getAttribute("id");
@SuppressWarnings("unchecked")
List<Element> flowElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "flow");
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(SplitState.class);
Collection<BeanDefinition> flows = new ArrayList<BeanDefinition>();
FlowParser flowParser = new FlowParser();
int i = 0;
for (Element nextElement : flowElements) {
flows.add(flowParser.parse(nextElement, parserContext, idAttribute+"#"+i));
i++;
}
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(flows);
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;
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.flow.StepState;
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.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -92,20 +93,33 @@ public class StepParser {
}
/**
* @param parserContext the parser context
* @param stepReference a reference to the step implementation
* @param on the pattern value
* @param next the next step id
* @return a bean definition for a {@link StepTransition}
* @param parserContext
* @param runtimeBeanReference
* @param onAttribute
* @param nextAttribute
* @return
*/
private RuntimeBeanReference getStateTransitionReference(ParserContext parserContext,
RuntimeBeanReference stepReference, String on, String next) {
RuntimeBeanReference runtimeBeanReference, String onAttribute, String nextAttribute) {
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
stateBuilder.addConstructorArgValue(runtimeBeanReference);
return getStateTransitionReference(parserContext, stateBuilder.getBeanDefinition(), onAttribute,
nextAttribute);
}
/**
* @param parserContext the parser context
* @param stateDefinition a reference to the state implementation
* @param on the pattern value
* @param next the next step id
* @return a bean definition for a {@link StateTransition}
*/
public static RuntimeBeanReference getStateTransitionReference(ParserContext parserContext,
BeanDefinition stateDefinition, String on,
String next) {
BeanDefinitionBuilder nextBuilder = BeanDefinitionBuilder.genericBeanDefinition(StateTransition.class);
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
stateBuilder.addConstructorArgValue(stepReference);
nextBuilder.addConstructorArgValue(stateBuilder.getBeanDefinition());
nextBuilder.addConstructorArgValue(stateDefinition);
if (StringUtils.hasText(on)) {
nextBuilder.addConstructorArgValue(on);
@@ -114,7 +128,8 @@ public class StepParser {
if (StringUtils.hasText(next)) {
nextBuilder.setFactoryMethod("createStateTransition");
nextBuilder.addConstructorArgValue(next);
} else {
}
else {
nextBuilder.setFactoryMethod("createEndStateTransition");
}

View File

@@ -13,7 +13,7 @@ public class DecisionState extends AbstractState<JobFlowExecutor> {
/**
* @param name
*/
DecisionState(String name, JobExecutionDecider decider) {
DecisionState(JobExecutionDecider decider, String name) {
super(name);
this.decider = decider;
}

View File

@@ -30,7 +30,7 @@
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:complexType>
</xsd:element>
<xsd:element name="step">
@@ -76,12 +76,22 @@ specific match will be chosen to select the next step. Hint: always include a de
</xsd:complexType>
</xsd:element>
<xsd:element name="pause" type="transitionWithNextType">
<xsd:element name="pause">
<xsd:annotation>
<xsd:documentation>
Declares job should be paused 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:annotation>
<xsd:documentation><![CDATA[
Specifies the next step to execute after job is resumed.]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:complexType>
</xsd:element>
<xsd:element name="decision">
@@ -112,13 +122,18 @@ The decider is a reference to a JobExecutionDecider that can produce a status to
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence minOccurs="2" maxOccurs="unbounded" >
<xsd:element name="flow" type="flowType">
<xsd:annotation><xsd:documentation>
A subflow within a job, having the same format as a job, but without a separate identity.
</xsd:documentation></xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:complexContent>
<xsd:extension base="transitionWithNextType">
<xsd:sequence minOccurs="2" maxOccurs="unbounded" >
<xsd:element name="flow" type="flowType">
<xsd:annotation><xsd:documentation>
A subflow within a job, having the same format as a job, but without a separate identity.
</xsd:documentation></xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
@@ -165,5 +180,5 @@ The decider is a reference to a JobExecutionDecider that can produce a status to
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,72 @@
/*
* 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.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;
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 DecisionJobParserTests {
@Autowired
@Qualifier("job")
private Job job;
@Autowired
private JobRepository jobRepository;
@Before
public void setUp() {
MapJobRepositoryFactoryBean.clear();
}
@Test
public void testDecisionState() throws Exception {
assertNotNull(job);
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
}
public static class TestDecider implements JobExecutionDecider {
public String decide(JobExecution jobExecution) {
return "FOO";
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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 PauseJobParserTests {
@Autowired
@Qualifier("job")
private Job job;
@Autowired
private JobRepository jobRepository;
@Before
public void setUp() {
MapJobRepositoryFactoryBean.clear();
}
@Test
public void testPauseState() throws Exception {
assertNotNull(job);
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.PAUSED, jobExecution.getStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
job.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 SplitJobParserTests {
@Autowired
@Qualifier("job")
private Job job;
@Autowired
private JobRepository jobRepository;
@Before
public void setUp() {
MapJobRepositoryFactoryBean.clear();
}
@Test
public void testSplitJob() throws Exception {
assertNotNull(job);
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(4, jobExecution.getStepExecutions().size());
}
}

View File

@@ -164,9 +164,9 @@ public class FlowJobTests {
};
Collection<StateTransition<JobFlowExecutor>> transitions = new ArrayList<StateTransition<JobFlowExecutor>>();
transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "*", "decision"));
transitions.add(StateTransition.createStateTransition(new DecisionState("decision", decider), "*", "step2"));
transitions.add(StateTransition.createStateTransition(new DecisionState(decider, "decision"), "*", "step2"));
transitions.add(StateTransition
.createStateTransition(new DecisionState("decision", decider), "SWITCH", "step3"));
.createStateTransition(new DecisionState(decider, "decision"), "SWITCH", "step3"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2")), "*"));
transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step3")), "*"));
flow.setStateTransitions(transitions);

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<beans:bean id="decider" class="org.springframework.batch.core.configuration.xml.DecisionJobParserTests$TestDecider"/>
<job id="job">
<decision decider="decider">
<next on="FOO" to="step1"/>
<next on="*" to="step2"/>
</decision>
<step name="step1" />
<step name="step2" />
</job>
</beans:beans>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<job id="job">
<step name="step1" next="pause"/>
<pause id="pause" next="step2"/>
<step name="step2" />
</job>
</beans:beans>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<job id="job">
<split id="split" next="step4">
<flow>
<step name="step1" next="step2"/>
<step name="step2" />
</flow>
<flow>
<step name="step3" />
</flow>
</split>
<step name="step4" />
</job>
</beans:beans>

View File

@@ -21,5 +21,6 @@
</bean>
<bean id="step2" parent="step1"/>
<bean id="step3" parent="step1"/>
<bean id="step4" parent="step1"/>
</beans>

View File

@@ -37,7 +37,7 @@ import com.sun.org.apache.xerces.internal.impl.xpath.XPath.Step;
* @author Dave Syer
*
*/
public class SimpleFlow<T> implements Flow<T> {
public class SimpleFlow<T> implements Flow<T>, InitializingBean {
private State<T> startState;
@@ -87,7 +87,7 @@ public class SimpleFlow<T> implements Flow<T> {
}
/**
* Locate start step and pre-populate data structures needed for execution.
* Locate start state and pre-populate data structures needed for execution.
*
* @see InitializingBean#afterPropertiesSet()
*/
@@ -132,7 +132,7 @@ public class SimpleFlow<T> implements Flow<T> {
* @return the next {@link Step} (or null if this is the end)
* @throws JobExecutionException
*/
private State<T> nextState(String stepName, String status) throws FlowExecutionException {
private State<T> nextState(String stateName, String status) throws FlowExecutionException {
// Special status value indicating that a state wishes to pause
// execution
@@ -140,11 +140,11 @@ public class SimpleFlow<T> implements Flow<T> {
return null;
}
Set<StateTransition<T>> set = transitionMap.get(stepName);
Set<StateTransition<T>> set = transitionMap.get(stateName);
if (set == null) {
throw new FlowExecutionException(String.format("No transitions found in flow=%s for state=%s", getName(),
stepName));
stateName));
}
String next = null;
@@ -161,7 +161,7 @@ public class SimpleFlow<T> implements Flow<T> {
if (next == null) {
throw new FlowExecutionException(String.format(
"Next state not found in flow=%s for step=%s with exit status=%s", getName(), stepName, status));
"Next state not found in flow=%s for state=%s with exit status=%s", getName(), stateName, status));
}
if (!stateMap.containsKey(next)) {
@@ -183,9 +183,9 @@ public class SimpleFlow<T> implements Flow<T> {
stateMap.clear();
boolean hasEndStep = false;
for (StateTransition<T> stepTransition : stateTransitions) {
State<T> step = stepTransition.getState();
stateMap.put(step.getName(), step);
for (StateTransition<T> stateTransition : stateTransitions) {
State<T> state = stateTransition.getState();
stateMap.put(state.getName(), state);
}
for (StateTransition<T> stateTransition : stateTransitions) {
@@ -197,7 +197,7 @@ public class SimpleFlow<T> implements Flow<T> {
String next = stateTransition.getNext();
if (!stateMap.containsKey(next)) {
throw new IllegalArgumentException("Missing step for [" + stateTransition + "]");
throw new IllegalArgumentException("Missing state for [" + stateTransition + "]");
}
}
@@ -218,7 +218,7 @@ public class SimpleFlow<T> implements Flow<T> {
if (!hasEndStep) {
throw new IllegalArgumentException(
"No end step was found. You must specify at least one transition with no next state.");
"No end state was found. You must specify at least one transition with no next state.");
}
if (startStateName != null) {
@@ -237,16 +237,16 @@ public class SimpleFlow<T> implements Flow<T> {
Set<String> nextStateNames = new HashSet<String>();
for (StateTransition<T> stepTransition : stateTransitions) {
nextStateNames.add(stepTransition.getNext());
for (StateTransition<T> stateTransition : stateTransitions) {
nextStateNames.add(stateTransition.getNext());
}
for (StateTransition<T> stepTransition : stateTransitions) {
State<T> state = stepTransition.getState();
for (StateTransition<T> stateTransition : stateTransitions) {
State<T> state = stateTransition.getState();
if (!nextStateNames.contains(state.getName())) {
if (startState != null && !startState.getName().equals(state.getName())) {
throw new IllegalArgumentException(String.format(
"Multiple possible start steps found: [%s, %s]. "
"Multiple possible start states found: [%s, %s]. "
+ "Please specify one explicitly with the startStateName property.", startState
.getName(), state.getName()));
}

View File

@@ -42,7 +42,7 @@ public class SplitState<T> extends AbstractState<T> {
/**
* @param name
*/
public SplitState(String name, Collection<Flow<T>> flows) {
public SplitState(Collection<Flow<T>> flows, String name) {
super(name);
this.flows = flows;
}

View File

@@ -45,7 +45,7 @@ public class SplitStateTests {
flows.add(flow1);
flows.add(flow2);
SplitState<Object> state = new SplitState<Object>("foo", flows);
SplitState<Object> state = new SplitState<Object>(flows, "foo");
EasyMock.expect(flow1.start(null)).andReturn(new FlowExecution("step1", FlowExecution.COMPLETED));
EasyMock.expect(flow2.start(null)).andReturn(new FlowExecution("step1", FlowExecution.COMPLETED));
@@ -69,7 +69,7 @@ public class SplitStateTests {
flows.add(flow1);
flows.add(flow2);
SplitState<Object> state = new SplitState<Object>("foo", flows);
SplitState<Object> state = new SplitState<Object>(flows, "foo");
state.setTaskExecutor(new SimpleAsyncTaskExecutor());
EasyMock.expect(flow1.start(null)).andReturn(new FlowExecution("step1", FlowExecution.COMPLETED));