From 082d9379c32f3c79ae2d1d41bad75ba146d18349 Mon Sep 17 00:00:00 2001 From: dsyer Date: Mon, 27 Oct 2008 14:21:15 +0000 Subject: [PATCH] OPEN - issue BATCH-679: Non-sequential execution Migrate JobParser to use JobFlow instead of ConditionalFlow --- .../core/configuration/xml/JobParser.java | 15 +- .../core/configuration/xml/StepParser.java | 42 ++- .../batch/core/job/ConditionalJob.java | 244 --------------- .../batch/core/job/StepTransition.java | 284 ------------------ .../batch/core/job/flow/FlowJob.java | 14 + 5 files changed, 52 insertions(+), 547 deletions(-) delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/job/ConditionalJob.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/job/StepTransition.java 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.