From fba258c8e594123d8994870e6742fdca526999e3 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Fri, 30 Aug 2013 15:40:04 -0500 Subject: [PATCH] BATCH-2089: Added ability to remap ExitStatus --- .../configuration/xml/AbstractFlowParser.java | 24 +- .../configuration/xml/CoreNamespaceUtils.java | 31 +- .../configuration/xml/InlineFlowParser.java | 12 +- .../xml/SimpleFlowFactoryBean.java | 10 + .../configuration/xml/TopLevelFlowParser.java | 8 +- .../DefaultStateTransitionComparator.java | 62 + .../core/job/flow/support/SimpleFlow.java | 21 +- .../job/flow/support/StateTransition.java | 42 +- .../jsr/configuration/xml/FlowParser.java | 75 +- .../core/jsr/job/flow/JsrFlowExecutor.java | 33 +- .../configuration/xml/spring-batch-3.0.xsd | 1334 +++++++++++++++++ .../batch/core/job/flow/FlowJobTests.java | 4 + ...DefaultStateTransitionComparatorTests.java | 82 + .../job/flow/support/SimpleFlowTests.java | 10 +- .../flow/support/StateTransitionTests.java | 54 +- .../core/jsr/job/flow/JsrFlowJobTests.java | 51 +- 16 files changed, 1719 insertions(+), 134 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java create mode 100644 spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java index 0d6d3827c..c69989bd4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2008 the original author or authors. + * Copyright 2006-2013 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. @@ -39,7 +39,8 @@ import org.w3c.dom.NodeList; /** * @author Dave Syer - * + * @author Michael Minella + * */ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionParser { @@ -86,7 +87,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar * Convenience method for subclasses to set the job factory reference if it * is available (null is fine, but the quality of error reports is better if * it is available). - * + * * @param jobFactoryRef */ protected void setJobFactoryRef(String jobFactoryRef) { @@ -95,7 +96,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar /* * (non-Javadoc) - * + * * @see AbstractSingleBeanDefinitionParser#getBeanClass(Element) */ @Override @@ -168,16 +169,15 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar } } - ManagedList managedList = new ManagedList(); - @SuppressWarnings( { "unchecked", "unused" }) - boolean dummy = managedList.addAll(stateTransitions); + ManagedList managedList = new ManagedList(); + managedList.addAll(stateTransitions); builder.addPropertyValue("stateTransitions", managedList); } /** * Find all of the elements that are pointed to by this element. - * + * * @param element * @return a collection of reachable element names */ @@ -206,7 +206,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar /** * Find all of the elements reachable from the startElement. - * + * * @param startElement * @param reachableElementMap * @param accumulator a collection of reachable element names @@ -295,7 +295,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar * @param element * @param parserContext the parser context for the bean factory */ - private static void verifyUniquePattern(Element transitionElement, List patterns, Element element, + protected static void verifyUniquePattern(Element transitionElement, List patterns, Element element, ParserContext parserContext) { String onAttribute = transitionElement.getAttribute(ON_ATTR); if (patterns.contains(onAttribute)) { @@ -344,7 +344,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar * {@link org.springframework.batch.core.job.flow.support.StateTransition} * references */ - private static Collection createTransition(FlowExecutionStatus status, String on, String next, + protected static Collection createTransition(FlowExecutionStatus status, String on, String next, String exitCode, BeanDefinition stateDef, ParserContext parserContext, boolean abandon) { BeanDefinition endState = null; @@ -389,7 +389,7 @@ public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionPar * @param elementName An end transition element name * @return the BatchStatus corresponding to the transition name */ - private static FlowExecutionStatus getBatchStatusFromEndTransitionName(String elementName) { + protected static FlowExecutionStatus getBatchStatusFromEndTransitionName(String elementName) { elementName = stripNamespace(elementName); if (STOP_ELE.equals(elementName)) { return FlowExecutionStatus.STOPPED; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java index 4963a5da1..ac6243f1e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2009 the original author or authors. + * Copyright 2006-2013 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. @@ -15,8 +15,11 @@ */ package org.springframework.batch.core.configuration.xml; +import java.util.Comparator; import java.util.Map; +import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; +import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.TypedStringValue; @@ -32,6 +35,7 @@ import org.w3c.dom.Element; * Utility methods used in parsing of the batch core namespace * * @author Thomas Risberg + * @author Michael Minella */ public class CoreNamespaceUtils { @@ -51,6 +55,7 @@ public class CoreNamespaceUtils { checkForStepScope(parserContext, source); addRangePropertyEditor(parserContext); addCoreNamespacePostProcessor(parserContext); + addStateTransitionComparator(parserContext); } private static void checkForStepScope(ParserContext parserContext, Object source) { @@ -73,19 +78,36 @@ public class CoreNamespaceUtils { } } + /** + * Register a {@link Comparator} to be used to sort {@link StateTransition}s + * + * @param parserContext + */ + private static void addStateTransitionComparator(ParserContext parserContext) { + BeanDefinitionRegistry registry = parserContext.getRegistry(); + if (!stateTransitionComparatorAlreadyDefined(registry)) { + AbstractBeanDefinition defaultStateTransitionComparator = BeanDefinitionBuilder.genericBeanDefinition( + DefaultStateTransitionComparator.class).getBeanDefinition(); + registry.registerBeanDefinition(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR, defaultStateTransitionComparator); + } + } + + private static boolean stateTransitionComparatorAlreadyDefined(BeanDefinitionRegistry registry) { + return registry.containsBeanDefinition(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR); + } + /** * Register a RangePropertyEditor if one does not already exist. * * @param parserContext */ - @SuppressWarnings("unchecked") private static void addRangePropertyEditor(ParserContext parserContext) { BeanDefinitionRegistry registry = parserContext.getRegistry(); if (!rangeArrayEditorAlreadyDefined(registry)) { AbstractBeanDefinition customEditorConfigurer = BeanDefinitionBuilder.genericBeanDefinition( CUSTOM_EDITOR_CONFIGURER_CLASS_NAME).getBeanDefinition(); customEditorConfigurer.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - ManagedMap editors = new ManagedMap(); + ManagedMap editors = new ManagedMap(); editors.put(RANGE_ARRAY_CLASS_NAME, RANGE_ARRAY_EDITOR_CLASS_NAME); customEditorConfigurer.getPropertyValues().addPropertyValue("customEditors", editors); registry.registerBeanDefinition(CUSTOM_EDITOR_CONFIGURER_CLASS_NAME, customEditorConfigurer); @@ -176,7 +198,8 @@ public class CoreNamespaceUtils { private static boolean matchesVersionInternal(Element element) { String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); - return schemaLocation.matches("(?m).*spring-batch-2.2.xsd.*") + return schemaLocation.matches("(?m).*spring-batch-3.0.xsd.*") + || schemaLocation.matches("(?m).*spring-batch-2.2.xsd.*") || schemaLocation.matches("(?m).*spring-batch.xsd.*") || !schemaLocation.matches("(?m).*spring-batch.*"); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineFlowParser.java index 49a88311c..36faa624c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineFlowParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2008 the original author or authors. + * Copyright 2006-2013 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. @@ -15,23 +15,26 @@ */ package org.springframework.batch.core.configuration.xml; +import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; 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.xml.ParserContext; import org.w3c.dom.Element; /** * @author Dave Syer - * + * @author Michael Minella + * */ public class InlineFlowParser extends AbstractFlowParser { - + private final String flowName; /** * Construct a {@link InlineFlowParser} with the specified name and using the * provided job repository ref. - * + * * @param flowName the name of the flow * @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean} * from the enclosing tag @@ -51,6 +54,7 @@ public class InlineFlowParser extends AbstractFlowParser { builder.getRawBeanDefinition().setAttribute("flowName", flowName); builder.addPropertyValue("name", flowName); + builder.addPropertyValue("stateTransitionComparator", new RuntimeBeanReference(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR)); super.doParse(element, parserContext, builder); builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); parserContext.popAndRegisterContainingComponent(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java index b963d1d05..9fdd20fb8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.xml; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import org.springframework.batch.core.job.flow.Flow; @@ -40,6 +41,7 @@ import org.springframework.util.Assert; * that form, in which case it is not modified). * * @author Dave Syer + * @author Michael Minella * */ @SuppressWarnings("rawtypes") @@ -51,6 +53,12 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { private String prefix; + private Comparator stateTransitionComparator; + + public void setStateTransitionComparator(Comparator stateTransitionComparator) { + this.stateTransitionComparator = stateTransitionComparator; + } + /** * The name of the flow that is created by this factory. * @@ -87,6 +95,8 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { SimpleFlow flow = new SimpleFlow(name); + flow.setStateTransitionComparator(stateTransitionComparator); + List updatedTransitions = new ArrayList(); for (StateTransition stateTransition : stateTransitions) { State state = getProxyState(stateTransition.getState()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java index a6970d1e9..9956c132d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2008 the original author or authors. + * Copyright 2006-2013 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. @@ -15,6 +15,8 @@ */ package org.springframework.batch.core.configuration.xml; +import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; +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.StringUtils; @@ -22,7 +24,8 @@ import org.w3c.dom.Element; /** * @author Dave Syer - * + * @author Michael Minella + * */ public class TopLevelFlowParser extends AbstractFlowParser { @@ -40,6 +43,7 @@ public class TopLevelFlowParser extends AbstractFlowParser { String flowName = element.getAttribute(ID_ATTR); builder.getRawBeanDefinition().setAttribute("flowName", flowName); builder.addPropertyValue("name", flowName); + builder.addPropertyValue("stateTransitionComparator", new RuntimeBeanReference(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR)); String abstractAttr = element.getAttribute(ABSTRACT_ATTR); if (StringUtils.hasText(abstractAttr)) { builder.setAbstract(abstractAttr.equals("true")); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java new file mode 100644 index 000000000..10c636b3a --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013 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.flow.support; + +import java.util.Comparator; + +import org.springframework.util.StringUtils; + +/** + * 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 Comparator + * @author Michael Minella + * @since 3.0 + */ +public class DefaultStateTransitionComparator implements Comparator { + public static final String STATE_TRANSITION_COMPARATOR = "batch_state_transition_comparator"; + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(StateTransition arg0, StateTransition arg1) { + String value = arg1.getPattern(); + if (arg0.getPattern().equals(value)) { + return 0; + } + int patternCount = StringUtils.countOccurrencesOf(arg0.getPattern(), "*"); + int valueCount = StringUtils.countOccurrencesOf(value, "*"); + if (patternCount > valueCount) { + return 1; + } + if (patternCount < valueCount) { + return -1; + } + patternCount = StringUtils.countOccurrencesOf(arg0.getPattern(), "?"); + valueCount = StringUtils.countOccurrencesOf(value, "?"); + if (patternCount > valueCount) { + return 1; + } + if (patternCount < valueCount) { + return -1; + } + return arg0.getPattern().compareTo(value); + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java index e59a8cea1..82f76b3f0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java @@ -17,12 +17,13 @@ package org.springframework.batch.core.job.flow.support; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.logging.Log; @@ -55,7 +56,7 @@ public class SimpleFlow implements Flow, InitializingBean { private State startState; - private Map> transitionMap = new HashMap>(); + private Map> transitionMap = new HashMap>(); private Map stateMap = new HashMap(); @@ -63,6 +64,12 @@ public class SimpleFlow implements Flow, InitializingBean { private final String name; + private Comparator stateTransitionComparator; + + public void setStateTransitionComparator(Comparator stateTransitionComparator) { + this.stateTransitionComparator = stateTransitionComparator; + } + /** * Create a flow with the given name. * @@ -270,9 +277,15 @@ public class SimpleFlow implements Flow, InitializingBean { String name = state.getName(); - SortedSet set = transitionMap.get(name); + Set set = transitionMap.get(name); if (set == null) { - set = new TreeSet(); + // If no comparator is provided, we will maintain the order of insertion + if(stateTransitionComparator == null) { + set = new LinkedHashSet(); + } else { + set = new TreeSet(stateTransitionComparator); + } + transitionMap.put(name, set); } set.add(stateTransition); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java index 4afafcead..612f3cd71 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java @@ -28,9 +28,10 @@ import org.springframework.util.StringUtils; * execution of the originating State. * * @author Dave Syer + * @author Michael Minella * @since 2.0 */ -public final class StateTransition implements Comparable { +public final class StateTransition { private final State state; @@ -38,6 +39,13 @@ public final class StateTransition implements Comparable { private final String next; + /** + * @return the pattern the {@link ExitStatus#getExitCode()} will be compared against. + */ + public String getPattern() { + return this.pattern; + } + /** * Create a new end state {@link StateTransition} specification. This * transition explicitly goes unconditionally to an end state (i.e. no more @@ -158,38 +166,6 @@ public final class StateTransition implements Comparable { return next == null; } - /** - * 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) - */ - @Override - public int compareTo(StateTransition 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) * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java index b59f0b1a1..ac01e6ad2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/FlowParser.java @@ -16,6 +16,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -24,10 +25,13 @@ import java.util.Set; import org.springframework.batch.core.configuration.xml.AbstractFlowParser; import org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean; +import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.beans.factory.config.BeanDefinition; 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; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -47,6 +51,10 @@ public class FlowParser extends AbstractFlowParser { private StepParser stepParser = new StepParser(); private String flowName; + /** + * @param flowName The name of the flow + * @param jobFactoryRef The bean name for the job factory + */ public FlowParser(String flowName, String jobFactoryRef) { super.setJobFactoryRef(jobFactoryRef); this.flowName = flowName; @@ -58,7 +66,6 @@ public class FlowParser extends AbstractFlowParser { } @Override - @SuppressWarnings({"rawtypes", "unchecked"}) protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { builder.getRawBeanDefinition().setAttribute("flowName", flowName); builder.addPropertyValue("name", flowName); @@ -91,8 +98,70 @@ public class FlowParser extends AbstractFlowParser { } } - ManagedList managedList = new ManagedList(); + ManagedList managedList = new ManagedList(); managedList.addAll(stateTransitions); builder.addPropertyValue("stateTransitions", managedList); } -} + + + public static Collection getNextElements(ParserContext parserContext, BeanDefinition stateDef, + Element element) { + return getNextElements(parserContext, null, stateDef, element); + } + + public static Collection getNextElements(ParserContext parserContext, String stepId, + BeanDefinition stateDef, Element element) { + + Collection list = new ArrayList(); + + String shortNextAttribute = element.getAttribute("next"); + boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute); + if (hasNextAttribute) { + list.add(getStateTransitionReference(parserContext, stateDef, null, shortNextAttribute)); + } + + boolean transitionElementExists = false; + List patterns = new ArrayList(); + for (String transitionName : new String[] { "next", "stop", "end", "fail" }) { + List transitionElements = DomUtils.getChildElementsByTagName(element, transitionName); + for (Element transitionElement : transitionElements) { + verifyUniquePattern(transitionElement, patterns, element, parserContext); + list.addAll(parseTransitionElement(transitionElement, stepId, stateDef, parserContext)); + transitionElementExists = true; + } + } + + if (!transitionElementExists) { + list.addAll(createTransition(FlowExecutionStatus.FAILED, FlowExecutionStatus.FAILED.getName(), null, null, + stateDef, parserContext, false)); + list.addAll(createTransition(FlowExecutionStatus.UNKNOWN, FlowExecutionStatus.UNKNOWN.getName(), null, null, + stateDef, parserContext, false)); + if (!hasNextAttribute) { + list.addAll(createTransition(FlowExecutionStatus.COMPLETED, null, null, null, stateDef, parserContext, + false)); + } + } + else if (hasNextAttribute) { + parserContext.getReaderContext().error( + "The <" + element.getNodeName() + "/> may not contain a 'next" + + "' attribute and a transition element", element); + } + + return list; + } + + protected static Collection parseTransitionElement(Element transitionElement, String stateId, + BeanDefinition stateDef, ParserContext parserContext) { + + FlowExecutionStatus status = getBatchStatusFromEndTransitionName(transitionElement.getNodeName()); + String onAttribute = transitionElement.getAttribute("on"); + String restartAttribute = transitionElement.getAttribute("restart"); + String nextAttribute = transitionElement.getAttribute("to"); + if (!StringUtils.hasText(nextAttribute)) { + nextAttribute = restartAttribute; + } + boolean abandon = stateId != null && StringUtils.hasText(restartAttribute) && !restartAttribute.equals(stateId); + String exitCodeAttribute = transitionElement.getAttribute("exit-status"); + + return createTransition(status, onAttribute, nextAttribute, exitCodeAttribute, stateDef, parserContext, abandon); + }} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowExecutor.java index 2b3c57b07..ebbd9b122 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowExecutor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/job/flow/JsrFlowExecutor.java @@ -37,6 +37,16 @@ public class JsrFlowExecutor extends JobFlowExecutor { super(jobRepository, stepHandler, execution); } + /* (non-Javadoc) + * @see org.springframework.batch.core.job.flow.JobFlowExecutor#addExitStatus(java.lang.String) + */ + @Override + public void addExitStatus(String code) { + if((exitStatus != null && isNonDefaultExitStauts(exitStatus.getExitCode())) && !isNonDefaultExitStauts(code)) { + exitStatus = exitStatus.and(new ExitStatus(code)); + } + } + /* (non-Javadoc) * @see org.springframework.batch.core.job.flow.JobFlowExecutor#updateJobExecutionStatus(org.springframework.batch.core.job.flow.FlowExecutionStatus) */ @@ -47,16 +57,23 @@ public class JsrFlowExecutor extends JobFlowExecutor { execution.setStatus(findBatchStatus(status)); ExitStatus curStatus = execution.getExitStatus(); - if(curStatus == null || - curStatus.getExitCode() == null || - curStatus.getExitCode().equals(ExitStatus.COMPLETED.getExitCode()) || - curStatus.getExitCode().equals(ExitStatus.EXECUTING.getExitCode()) || - curStatus.getExitCode().equals(ExitStatus.FAILED.getExitCode()) || - curStatus.getExitCode().equals(ExitStatus.NOOP.getExitCode()) || - curStatus.getExitCode().equals(ExitStatus.STOPPED.getExitCode()) || - curStatus.getExitCode().equals(ExitStatus.UNKNOWN.getExitCode())) { + if(isNonDefaultExitStauts(curStatus.getExitCode())) { exitStatus = exitStatus.and(new ExitStatus(status.getName())); execution.setExitStatus(exitStatus); } } + + /** + * @param curStatus the exit code to be evaluated + * @return true if the value matches a known exit code + */ + protected boolean isNonDefaultExitStauts(String curStatus) { + return curStatus == null || + curStatus.equals(ExitStatus.COMPLETED.getExitCode()) || + curStatus.equals(ExitStatus.EXECUTING.getExitCode()) || + curStatus.equals(ExitStatus.FAILED.getExitCode()) || + curStatus.equals(ExitStatus.NOOP.getExitCode()) || + curStatus.equals(ExitStatus.STOPPED.getExitCode()) || + curStatus.equals(ExitStatus.UNKNOWN.getExitCode()); + } } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd new file mode 100644 index 000000000..cf6cc6cfa --- /dev/null +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd @@ -0,0 +1,1334 @@ + + + + + + + + + + + + + + Defines a job composed of a set of steps and + transitions between steps. The job will be exposed in + the enclosing + bean factory as a component of type Job + that can be launched using a + JobLauncher. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a stage in job processing backed by a + Step. The id attribute must be specified since this + step definition + will be referred to from other elements + to form a Job flow. + + + + + + + + + + + + + + + + + Defines a flow composed of a set of steps and + transitions between steps. + + + + + + + + + + + + + + + + + + A reference to a JobExecutionListener (or a POJO + if using before-job-method / after-job-method or + source level + annotations). + + + + + + + + + + + + + + + A bean definition for a step listener (or POJO if + using *-method attributes or source level + annotations) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a stage in job processing backed by a + Step. The id attribute must be specified. The + step + requires either + a chunk definition, + a tasklet reference, or a reference to a + (possibly abstract) parent step. + + + + + + + + + + + + + + + + Declares job should split here into two or more + subflows. + + + + + + + + A subflow within a job, having the same + format as a job, but without a separate identity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Declares job should include an externalized flow + here. + + + + + + + + + + + + + + + + + + + + + + Declares job should query a decider to determine + where execution should go next. + + + + + + + + + The decider is a reference to a + JobExecutionDecider that can produce a status to base + the next + transition on. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The tasklet is a reference to another bean + definition that implements + the Tasklet interface. + + + + + + + + + + If the tasklet is specified as a bean definition, then a method can be specified and a POJO + will + be adapted to the Tasklet interface. The method suggested should have the same arguments + as Tasklet.execute (or a subset), and have a compatible return type (boolean, void or RepeatStatus). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An exception class name. + + + + + + + + + + + + + + + + + Classify an exception as "included" in the set. Exceptions of this type or a subclass are + included. + + + + + + + + + + + + + + + + Classify an exception as "excluded" from the + set. Exceptions of this type or a subclass are + excluded + + + + + + + + + + + + + + + A reference to a listener, a POJO with a + listener-annotated method, or a POJO with + a method + referenced by a + *-method attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a transition from this step to the + next + one depending on the value of the exit + status. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. When a + step finishes + the most + specific match will be chosen to select the next step. + Hint: + always include a default + transition with on="*". + + + + + + + The name of the step to go to next. Must + resolve to one of the other steps in this job. + + + + + + + + + Declares job should be stop at this point and + provides pointer where execution should continue + when + the job is + restarted. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The name of the step to start on when the + stopped job is restarted. + Must resolve to one of the + other steps + in this job. + + + + + + The exit code value to end on, defaults to + STOPPED. + + + + + + + + Declares job should end at this point, without + the possibility of restart. + BatchStatus will be + COMPLETED. + ExitStatus is configurable. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The exit code value to end on, defaults to + COMPLETED. + + + + + + + + Declares job should fail at this point. + BatchStatus will be FAILED. ExitStatus is configurable. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The exit code value to end on, defaults to + FAILED. + + + + + + + + + + + + + + + + + + + + + + + + + The name of the parent bean from which the + configuration should inherit. + + + + + + + + + + + + + Is this bean "abstract", that is, not meant to be + instantiated itself + but rather just serving as + parent for concrete + child bean definitions? + The default is "false". Specify "true" to + tell the bean factory to not + try + to instantiate that particular bean + in any case. + + Note: This attribute will not be inherited by child + bean definitions. + Hence, it needs to be specified per abstract bean + definition. + + + + + + + + + + Should this list be merged with the corresponding + list provided + by the parent? If not, it will + overwrite the parent + list. + + + + + + + + + + This attribute indicates the method from the + class that should + be used to dynamically create a + proxy. + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java index e9a09856a..41b58de0f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java @@ -35,6 +35,7 @@ import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.batch.core.job.flow.support.state.DecisionState; @@ -49,6 +50,7 @@ import org.springframework.batch.core.step.StepSupport; /** * @author Dave Syer + * @author Michael Minella * */ public class FlowJobTests { @@ -422,6 +424,7 @@ public class FlowJobTests { transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end2"))); transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end3"))); flow.setStateTransitions(transitions); + flow.setStateTransitionComparator(new DefaultStateTransitionComparator()); job.setFlow(flow); job.afterPropertiesSet(); job.doExecute(jobExecution); @@ -473,6 +476,7 @@ public class FlowJobTests { transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end2"))); transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end3"))); flow.setStateTransitions(transitions); + flow.setStateTransitionComparator(new DefaultStateTransitionComparator()); job.setFlow(flow); job.doExecute(jobExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java new file mode 100644 index 000000000..b4a705c29 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013 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.flow.support; + +import static org.junit.Assert.assertEquals; + +import java.util.Comparator; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.core.job.flow.State; +import org.springframework.batch.core.job.flow.StateSupport; + +public class DefaultStateTransitionComparatorTests { + + private State state = new StateSupport("state1"); + private Comparator comparator; + + @Before + public void setUp() throws Exception { + comparator = new DefaultStateTransitionComparator(); + } + + @Test + public void testSimpleOrderingEqual() { + StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); + assertEquals(0, comparator.compare(transition, transition)); + } + + @Test + public void testSimpleOrderingMoreGeneral() { + StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); + StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); + assertEquals(1, comparator.compare(transition, other)); + assertEquals(-1, comparator.compare(other, transition)); + } + + @Test + public void testSimpleOrderingMostGeneral() { + StateTransition transition = StateTransition.createStateTransition(state, "*", "start"); + StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); + assertEquals(1, comparator.compare(transition, other)); + assertEquals(-1, comparator.compare(other, transition)); + } + + @Test + public void testSubstringAndWildcard() { + StateTransition transition = StateTransition.createStateTransition(state, "CONTIN*", "start"); + StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); + assertEquals(1, comparator.compare(transition, other)); + assertEquals(-1, comparator.compare(other, transition)); + } + + @Test + public void testSimpleOrderingMostToNextGeneral() { + StateTransition transition = StateTransition.createStateTransition(state, "*", "start"); + StateTransition other = StateTransition.createStateTransition(state, "C?", "start"); + assertEquals(1, comparator.compare(transition, other)); + assertEquals(-1, comparator.compare(other, transition)); + } + + @Test + public void testSimpleOrderingAdjacent() { + StateTransition transition = StateTransition.createStateTransition(state, "CON*", "start"); + StateTransition other = StateTransition.createStateTransition(state, "CON?", "start"); + assertEquals(1, comparator.compare(transition, other)); + assertEquals(-1, comparator.compare(other, transition)); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/SimpleFlowTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/SimpleFlowTests.java index 91f812024..17963ce93 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/SimpleFlowTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/SimpleFlowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -36,7 +36,8 @@ import org.springframework.batch.core.job.flow.StateSupport; /** * @author Dave Syer - * + * @author Michael Minella + * */ public class SimpleFlowTests { @@ -177,7 +178,8 @@ public class SimpleFlowTests { flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), StateTransition.createStateTransition(new StubState("step1"), ExitStatus.COMPLETED.getExitCode(), "step3"), StateTransition.createEndStateTransition(new StubState("step2")), StateTransition - .createEndStateTransition(new StubState("step3")))); + .createEndStateTransition(new StubState("step3")))); + flow.setStateTransitionComparator(new DefaultStateTransitionComparator()); flow.afterPropertiesSet(); FlowExecution execution = flow.start(executor); assertEquals(FlowExecutionStatus.COMPLETED, execution.getStatus()); @@ -224,7 +226,7 @@ public class SimpleFlowTests { /** * @author Dave Syer - * + * */ private static class StubState extends StateSupport { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java index 28d1889b3..245acd8a3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2013 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. @@ -15,7 +15,6 @@ */ package org.springframework.batch.core.job.flow.support; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -25,12 +24,13 @@ import org.springframework.batch.core.job.flow.StateSupport; /** * @author Dave Syer - * + * @author Michael Minella + * */ public class StateTransitionTests { State state = new StateSupport("state1"); - + @Test public void testIsEnd() { StateTransition transition = StateTransition.createEndStateTransition(state, ""); @@ -74,52 +74,6 @@ public class StateTransitionTests { assertTrue(transition.matches("CONTINUABLE")); } - @Test - public void testSimpleOrderingEqual() { - StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); - assertEquals(0, transition.compareTo(transition)); - } - - @Test - public void testSimpleOrderingMoreGeneral() { - StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); - StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, transition.compareTo(other)); - assertEquals(-1, other.compareTo(transition)); - } - - @Test - public void testSimpleOrderingMostGeneral() { - StateTransition transition = StateTransition.createStateTransition(state, "*", "start"); - StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, transition.compareTo(other)); - assertEquals(-1, other.compareTo(transition)); - } - - @Test - public void testSubstringAndWildcard() { - StateTransition transition = StateTransition.createStateTransition(state, "CONTIN*", "start"); - StateTransition other = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, transition.compareTo(other)); - assertEquals(-1, other.compareTo(transition)); - } - - @Test - public void testSimpleOrderingMostToNextGeneral() { - StateTransition transition = StateTransition.createStateTransition(state, "*", "start"); - StateTransition other = StateTransition.createStateTransition(state, "C?", "start"); - assertEquals(1, transition.compareTo(other)); - assertEquals(-1, other.compareTo(transition)); - } - - @Test - public void testSimpleOrderingAdjacent() { - StateTransition transition = StateTransition.createStateTransition(state, "CON*", "start"); - StateTransition other = StateTransition.createStateTransition(state, "CON?", "start"); - assertEquals(1, transition.compareTo(other)); - assertEquals(-1, other.compareTo(transition)); - } - @Test public void testToString() { StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJobTests.java index 95c75bc48..a2cd8415b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/job/flow/JsrFlowJobTests.java @@ -25,6 +25,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.batch.api.Decider; + import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.BatchStatus; @@ -59,7 +61,7 @@ import org.springframework.batch.core.step.StepSupport; */ public class JsrFlowJobTests { - private JsrFlowJob job = new JsrFlowJob(); + private JsrFlowJob job; private JobExecution jobExecution; @@ -71,6 +73,7 @@ public class JsrFlowJobTests { @Before public void setUp() throws Exception { + job = new JsrFlowJob(); MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(); factory.afterPropertiesSet(); jobExecutionDao = factory.getJobExecutionDao(); @@ -431,7 +434,7 @@ public class JsrFlowJobTests { job.setFlow(flow); job.afterPropertiesSet(); job.doExecute(jobExecution); - StepExecution stepExecution = getStepExecution(jobExecution, "step3"); + StepExecution stepExecution = getStepExecution(jobExecution, "step2"); assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus()); assertEquals(2, jobExecution.getStepExecutions().size()); } @@ -455,19 +458,21 @@ public class JsrFlowJobTests { public void testDecisionFlow() throws Throwable { SimpleFlow flow = new SimpleFlow("job"); - JobExecutionDecider decider = new JobExecutionDecider() { + Decider decider = new Decider() { + @Override - public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) { - assertNotNull(stepExecution); - return new FlowExecutionStatus("SWITCH"); + public String decide(javax.batch.runtime.StepExecution[] executions) + throws Exception { + assertNotNull(executions); + return "SWITCH"; } }; List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "decision")); - DecisionState decision = new DecisionState(decider, "decision"); - transitions.add(StateTransition.createStateTransition(decision, "step2")); + StepState decision = new StepState(new StubDecisionStep("decision", decider)); transitions.add(StateTransition.createStateTransition(decision, "SWITCH", "step3")); + transitions.add(StateTransition.createStateTransition(decision, "step2")); StepState step2 = new StepState(new StubStep("step2")); transitions.add(StateTransition.createStateTransition(step2, ExitStatus.COMPLETED.getExitCode(), "end0")); transitions.add(StateTransition.createStateTransition(step2, ExitStatus.FAILED.getExitCode(), "end1")); @@ -482,13 +487,14 @@ public class JsrFlowJobTests { job.setFlow(flow); job.doExecute(jobExecution); + StepExecution stepExecution = getStepExecution(jobExecution, "step3"); if (!jobExecution.getAllFailureExceptions().isEmpty()) { throw jobExecution.getAllFailureExceptions().get(0); } assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - assertEquals(2, jobExecution.getStepExecutions().size()); + assertEquals(3, jobExecution.getStepExecutions().size()); } @@ -672,7 +678,6 @@ public class JsrFlowJobTests { assertEquals("[step1, step2]", names.toString()); } - /** /** * @author Dave Syer * @@ -692,6 +697,32 @@ public class JsrFlowJobTests { } + /** + * @author Michael Minella + * + */ + private class StubDecisionStep extends StepSupport { + + private Decider decider; + + private StubDecisionStep(String name, Decider decider) { + super(name); + this.decider = decider; + } + + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException { + stepExecution.setStatus(BatchStatus.COMPLETED); + try { + stepExecution.setExitStatus(new ExitStatus(decider.decide(new javax.batch.runtime.StepExecution [] {new org.springframework.batch.core.jsr.StepExecution(stepExecution)}))); + } catch (Exception e) { + throw new RuntimeException(e); + } + + jobRepository.update(stepExecution); + } + } + /** * @param jobExecution * @param stepName