OPEN - issue BATCH-679: Non-sequential execution

Migrate JobParser to use JobFlow instead of ConditionalFlow
This commit is contained in:
dsyer
2008-10-27 14:21:15 +00:00
parent 48bbafb449
commit 082d9379c3
5 changed files with 52 additions and 547 deletions

View File

@@ -19,8 +19,9 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.job.ConditionalJob;
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;
@@ -41,7 +42,7 @@ import org.w3c.dom.Element;
public class JobParser extends AbstractBeanDefinitionParser {
/**
* Create a bean definition for a {@link ConditionalJob}. The
* Create a bean definition for a {@link FlowJob}. The
* <code>jobRepository</code> attribute is a reference to a
* {@link JobRepository} and defaults to "jobRepository". Nested step
* elements are delegated to a {@link StepParser}.
@@ -51,7 +52,10 @@ public class JobParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConditionalJob.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FlowJob.class);
String jobName = element.getAttribute("id");
builder.addConstructorArgValue(jobName);
String repositoryAttribute = element.getAttribute("repository");
if (!StringUtils.hasText(repositoryAttribute)) {
repositoryAttribute = "jobRepository";
@@ -69,7 +73,10 @@ public class JobParser extends AbstractBeanDefinitionParser {
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(stepTransitions);
builder.addPropertyValue("stepTransitions", managedList);
BeanDefinitionBuilder flowBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleFlow.class);
flowBuilder.addConstructorArgValue(jobName );
flowBuilder.addPropertyValue("stateTransitions", managedList);
builder.addPropertyValue("flow", flowBuilder.getBeanDefinition());
return builder.getBeanDefinition();
}

View File

@@ -20,11 +20,13 @@ import java.util.Collection;
import java.util.List;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.StepTransition;
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.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -48,8 +50,8 @@ public class StepParser {
*
* @param element the &lt;step/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a collection of bean definitions for {@link StepTransition}
* objects
* @return a collection of bean definitions for {@link StateTransition}
* instances objects
*/
public Collection<RuntimeBeanReference> parse(Element element, ParserContext parserContext) {
@@ -60,7 +62,7 @@ public class StepParser {
String shortNextAttribute = element.getAttribute("next");
boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute);
if (hasNextAttribute) {
list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), "*",
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), null,
shortNextAttribute));
}
@@ -69,7 +71,7 @@ public class StepParser {
// If there are no next elements then this must be an end state
if (nextElements.isEmpty() && !hasNextAttribute) {
list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), "*", null));
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), null, null));
}
else {
// Otherwise we need to capture the "to" state
@@ -80,8 +82,8 @@ public class StepParser {
throw new BeanCreationException("Duplicate transition pattern found for '*' "
+ "(only specify one of next= attribute at step level and next element with on='*')");
}
list.add(getStepTransitionReference(parserContext, new RuntimeBeanReference(refAttribute), onAttribute,
nextAttribute));
list.add(getStateTransitionReference(parserContext, new RuntimeBeanReference(refAttribute),
onAttribute, nextAttribute));
}
}
@@ -91,23 +93,33 @@ public class StepParser {
/**
* @param parserContext the parser context
* @param runtimeBeanReference a reference to the step implementation
* @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}
*/
private RuntimeBeanReference getStepTransitionReference(ParserContext parserContext,
RuntimeBeanReference runtimeBeanReference, String on, String next) {
private RuntimeBeanReference getStateTransitionReference(ParserContext parserContext,
RuntimeBeanReference stepReference, String on, String next) {
RootBeanDefinition nextDef = new RootBeanDefinition(StepTransition.class);
nextDef.getConstructorArgumentValues().addIndexedArgumentValue(0, runtimeBeanReference);
nextDef.getConstructorArgumentValues().addIndexedArgumentValue(1, on);
BeanDefinitionBuilder nextBuilder = BeanDefinitionBuilder.genericBeanDefinition(StateTransition.class);
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
stateBuilder.addConstructorArgValue(stepReference);
nextBuilder.addConstructorArgValue(stateBuilder.getBeanDefinition());
if (StringUtils.hasText(on)) {
nextBuilder.addConstructorArgValue(on);
}
if (StringUtils.hasText(next)) {
nextDef.getConstructorArgumentValues().addIndexedArgumentValue(2, next);
nextBuilder.setFactoryMethod("createStateTransition");
nextBuilder.addConstructorArgValue(next);
} else {
nextBuilder.setFactoryMethod("createEndStateTransition");
}
// TODO: do we need to use RuntimeBeanReference?
AbstractBeanDefinition nextDef = nextBuilder.getBeanDefinition();
String nextDefName = parserContext.getReaderContext().generateBeanName(nextDef);
BeanComponentDefinition nextDefComponent = new BeanComponentDefinition(nextDef, nextDefName);
parserContext.registerBeanComponent(nextDefComponent);

View File

@@ -1,244 +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.job;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.repeat.ExitStatus;
/**
* A {@link Job} that branches conditionally depending on the exit status of the
* last step execution. The input parameters are the step transitions (in no
* particular order). The start step name must also be specified (and must exist
* in the set of transitions).
*
* @author Dave Syer
*
*/
public class ConditionalJob extends AbstractJob {
private Step startStep;
private Map<String, SortedSet<StepTransition>> transitionMap = new HashMap<String, SortedSet<StepTransition>>();
private Map<String, Step> stepMap = new HashMap<String, Step>();
private String startStepName;
private Collection<StepTransition> stepTransitions = new HashSet<StepTransition>();
/**
* Create a {@link Job} with the given name.
* @param name
*/
public ConditionalJob(String name) {
super(name);
}
/**
* Create a {@link Job} with no name.
*/
public ConditionalJob() {
super();
}
/**
* Public setter for the start step name.
* @param startStepName the name of the start step
*/
public void setStartStepName(String startStepName) {
this.startStepName = startStepName;
}
/**
* Public setter for the stepTransitions.
* @param stepTransitions the stepTransitions to set
*/
public void setStepTransitions(Collection<StepTransition> stepTransitions) {
this.stepTransitions = stepTransitions;
}
/**
* Locate start step and pre-populate data structures needed for execution.
*
* @see AbstractJob#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
startStep = null;
transitionMap.clear();
stepMap.clear();
boolean hasEndStep = false;
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
stepMap.put(step.getName(), step);
}
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
if (!stepTransition.isEnd()) {
String next = stepTransition.getNext();
if (!stepMap.containsKey(next)) {
throw new IllegalArgumentException("Missing step for [" + stepTransition + "]");
}
}
else {
hasEndStep = true;
}
String name = step.getName();
SortedSet<StepTransition> set = transitionMap.get(name);
if (set == null) {
set = new TreeSet<StepTransition>();
transitionMap.put(name, set);
}
set.add(stepTransition);
}
if (!hasEndStep) {
throw new IllegalArgumentException(
"No end step was found. You must specify at least one transition with no next step.");
}
if (startStepName != null) {
startStep = stepMap.get(startStepName);
if (startStep == null) {
throw new IllegalArgumentException(
"Start step does not exist (if you specify a startStepName make sure "
+ "a step with that name is in one of the transitions): [" + startStepName + "]");
}
}
else {
// Try and locate a transition with no incoming links
Set<String> nextStepNames = new HashSet<String>();
for (StepTransition stepTransition : stepTransitions) {
nextStepNames.add(stepTransition.getNext());
}
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
if (!nextStepNames.contains(step.getName())) {
if (startStep != null && !startStep.getName().equals(step.getName())) {
throw new IllegalArgumentException(String.format(
"Multiple possible start steps found: [%s, %s]. "
+ "Please specify one explicitly with the startStepName property.", startStep
.getName(), step.getName()));
}
startStep = step;
}
}
if (startStep == null) {
throw new IllegalArgumentException(
"No start step could be located (no transition without incoming links)");
}
}
}
/**
* @see AbstractJob#doExecute(JobExecution)
* @throws JobExecutionException if the next step cannot be located at any
* point
*/
@Override
protected StepExecution doExecute(JobExecution jobExecution) throws JobExecutionException {
StepExecution stepExecution = null;
Step step = nextStep(null);
while (step != null) {
stepExecution = handleStep(step, jobExecution);
step = nextStep(stepExecution);
}
return stepExecution;
}
/**
* @param stepExecution the last {@link StepExecution} (or null if this is
* the start)
* @return the next {@link Step} (or null if this is the end)
* @throws JobExecutionException
*/
private Step nextStep(StepExecution stepExecution) throws JobExecutionException {
if (stepExecution == null) {
return startStep;
}
String stepName = stepExecution.getStepName();
Set<StepTransition> set = transitionMap.get(stepName);
if (set == null) {
throw new JobExecutionException(String.format("No transitions found in job=%s for step=%s", getName(),
stepName));
}
ExitStatus status = stepExecution.getExitStatus();
String next = null;
for (StepTransition stepTransition : set) {
if (stepTransition.matches(status)) {
if (stepTransition.isEnd()) {
// End of job
return null;
}
next = stepTransition.getNext();
break;
}
}
if (next == null) {
throw new JobExecutionException(String.format(
"Next step not found in job=%s for step=%s with exit status=%s", getName(), stepName, status));
}
if (!stepMap.containsKey(next)) {
throw new JobExecutionException(String.format("Next step not specified in job=%s for next=%s", getName(),
next));
}
return stepMap.get(next);
}
}

View File

@@ -1,284 +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.job;
import org.springframework.batch.core.Step;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.StringUtils;
/**
* Value object representing a potential transition from one {@link Step} to
* another. The originating step name and the next {@link Step} to execute are
* linked by a pattern for the {@link ExitStatus#getExitCode() exit code} of an
* execution of the originating step.
*
* @author Dave Syer
*
*/
public class StepTransition implements Comparable<StepTransition> {
private final String pattern;
private final String next;
private final Step step;
/**
* Create a new end state {@link StepTransition} specification. This
* transition explicitly goes to an end state (i.e. no more executions).
*
* @see StepTransition#StepTransition(Step, String, String)
*/
public StepTransition(Step step, String pattern) {
this(step, pattern, null);
}
/**
* Create a new {@link StepTransition} specification from one step to
* another (by name).
*
* @param step the step to be executed
* @param pattern the pattern to match in the {@link ExitStatus} of the step
* @param next the name of the next step to execute
*/
public StepTransition(Step step, String pattern, String next) {
super();
this.step = step;
if (!StringUtils.hasText(pattern)) {
this.pattern = "*";
}
else {
this.pattern = pattern;
}
this.next = next;
}
/**
* Public getter for the next step name.
* @return the next
*/
public String getNext() {
return next;
}
/**
* Public getter for the step.
* @return the step
*/
public Step getStep() {
return step;
}
/**
* Check if the provided {@link ExitStatus} matches the pattern, signalling
* that the next step should be executed.
*
* @param status the {@link ExitStatus} to compare
* @return true if the pattern matches this status
*/
public boolean matches(ExitStatus status) {
return matchStrings(pattern, status.getExitCode());
}
/**
* Check for a special next step signalling the end of a job.
*
* @return true if this transition goes nowhere (there is no next)
*/
public boolean isEnd() {
return next == null;
}
/**
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
* matches against a pattern. The pattern may contain two special
* characters:<br>
* '*' means zero or more characters<br>
* '?' means one and only one character
* @param pattern pattern to match against. Must not be <code>null</code>.
* @param str string which must be matched against the pattern. Must not be
* <code>null</code>.
* @return <code>true</code> if the string matches against the pattern, or
* <code>false</code> otherwise.
*/
private boolean matchStrings(String pattern, String str) {
char[] patArr = pattern.toCharArray();
char[] strArr = str.toCharArray();
int patIdxStart = 0;
int patIdxEnd = patArr.length - 1;
int strIdxStart = 0;
int strIdxEnd = strArr.length - 1;
char ch;
boolean containsStar = pattern.contains("*");
if (!containsStar) {
// No '*'s, so we make a shortcut
if (patIdxEnd != strIdxEnd) {
return false; // Pattern and string do not have the same size
}
for (int i = 0; i <= patIdxEnd; i++) {
ch = patArr[i];
if (ch != '?') {
if (ch != strArr[i]) {
return false;// Character mismatch
}
}
}
return true; // String matches against pattern
}
if (patIdxEnd == 0) {
return true; // Pattern contains only '*', which matches anything
}
// Process characters before first star
while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxStart]) {
return false;// Character mismatch
}
}
patIdxStart++;
strIdxStart++;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// Process characters after last star
while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxEnd]) {
return false;// Character mismatch
}
}
patIdxEnd--;
strIdxEnd--;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// process pattern between stars. padIdxStart and patIdxEnd point
// always to a '*'.
while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
int patIdxTmp = -1;
for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
if (patArr[i] == '*') {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == patIdxStart + 1) {
// Two stars next to each other, skip the first one.
patIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - patIdxStart - 1);
int strLength = (strIdxEnd - strIdxStart + 1);
int foundIdx = -1;
strLoop: for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
ch = patArr[patIdxStart + j + 1];
if (ch != '?') {
if (ch != strArr[strIdxStart + i + j]) {
continue strLoop;
}
}
}
foundIdx = strIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
patIdxStart = patIdxTmp;
strIdxStart = foundIdx + patLength;
}
// All characters in the string are used. Check if only '*'s are left
// in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
/**
* Sorts by decreasing specificity of pattern, based on just counting
* wildcards (with * taking precedence over ?). If wildcard counts are equal
* then falls back to alphabetic comparison. Hence * &gt; foo* &gt; ??? &gt;
* fo? > foo.
* @see Comparable#compareTo(Object)
*/
public int compareTo(StepTransition other) {
String value = other.pattern;
if (pattern.equals(value)) {
return 0;
}
int patternCount = StringUtils.countOccurrencesOf(pattern, "*");
int valueCount = StringUtils.countOccurrencesOf(value, "*");
if (patternCount > valueCount) {
return 1;
}
if (patternCount < valueCount) {
return -1;
}
patternCount = StringUtils.countOccurrencesOf(pattern, "?");
valueCount = StringUtils.countOccurrencesOf(value, "?");
if (patternCount > valueCount) {
return 1;
}
if (patternCount < valueCount) {
return -1;
}
return pattern.compareTo(value);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("StepTransition: step=%s, pattern=%s, next=%s", step.getName(), pattern, next);
}
}

View File

@@ -36,6 +36,20 @@ import org.springframework.util.Assert;
public class FlowJob extends AbstractJob {
private Flow<JobFlowExecutor> flow;
/**
* Create a {@link FlowJob} with null name and no flow (invalid state).
*/
public FlowJob() {
super();
}
/**
* Create a {@link FlowJob} with provided name and no flow (invalid state).
*/
public FlowJob(String name) {
super(name);
}
/**
* Public setter for the flow.