diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
index 80c47bcd5..d36f09e79 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java
@@ -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
* jobRepository 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();
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java
index 9a12a86ce..05d66773b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java
@@ -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 <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 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);
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java
deleted file mode 100644
index a988b67ab..000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java
+++ /dev/null
@@ -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> transitionMap = new HashMap>();
-
- private Map stepMap = new HashMap();
-
- private String startStepName;
-
- private Collection stepTransitions = new HashSet();
-
- /**
- * 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 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 set = transitionMap.get(name);
- if (set == null) {
- set = new TreeSet();
- 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 nextStepNames = new HashSet();
-
- 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 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);
-
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepTransition.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepTransition.java
deleted file mode 100644
index 6eef66478..000000000
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepTransition.java
+++ /dev/null
@@ -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 {
-
- 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:
- * '*' means zero or more characters
- * '?' means one and only one character
- * @param pattern pattern to match against. Must not be null.
- * @param str string which must be matched against the pattern. Must not be
- * null.
- * @return true if the string matches against the pattern, or
- * false 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 * > foo* > ??? >
- * 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);
- }
-
-}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
index 3c12cedb7..1556acb37 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java
@@ -36,6 +36,20 @@ import org.springframework.util.Assert;
public class FlowJob extends AbstractJob {
private Flow 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.