From 5e2b218a3f3778c87880489359979cbd1a07bcac Mon Sep 17 00:00:00 2001 From: trisberg Date: Mon, 9 Feb 2009 04:52:34 +0000 Subject: [PATCH] BATCH-1013: adding top-level element; refactoring StepParser to accommodate inline and standalone step definitions --- ...tepParser.java => AbstractStepParser.java} | 252 +----------------- .../xml/CoreNamespaceHandler.java | 1 + .../configuration/xml/DecisionParser.java | 2 +- .../core/configuration/xml/FlowParser.java | 205 +++++++++++++- .../configuration/xml/InlineStepParser.java | 86 ++++++ .../core/configuration/xml/JobParser.java | 2 +- .../core/configuration/xml/SplitParser.java | 2 +- .../xml/StandaloneStepParser.java | 71 +++++ .../configuration/xml/TopLevelStepParser.java | 38 +++ .../configuration/xml/spring-batch-2.0.xsd | 30 +++ 10 files changed, 440 insertions(+), 249 deletions(-) rename spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/{StepParser.java => AbstractStepParser.java} (62%) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java 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/AbstractStepParser.java similarity index 62% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParser.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index 3ceb3f422..7e16bc686 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/AbstractStepParser.java @@ -16,15 +16,11 @@ package org.springframework.batch.core.configuration.xml; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ExitStatus; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -49,250 +45,14 @@ import org.w3c.dom.NamedNodeMap; * @author Thomas Risberg * @since 2.0 */ -public class StepParser { - - private static final String NEXT = "next"; - private static final String END = "end"; - private static final String FAIL = "fail"; - private static final String PAUSE = "pause"; - - // For generating unique state names for end transitions - private static int endCounter = 0; - - /** - * Parse the step and turn it into a list of transitions. - * - * @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 org.springframework.batch.core.job.flow.support.StateTransition} - * instances objects - */ - public Collection parse(Element element, ParserContext parserContext) { - - BeanDefinitionBuilder stateBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.StepState"); - String stepRef = element.getAttribute("name"); - String taskletRef = element.getAttribute("tasklet"); - - if (!StringUtils.hasText(stepRef)) { - parserContext.getReaderContext().error("The name attribute can't be empty for <" + element.getNodeName() + ">", element); - } - - @SuppressWarnings("unchecked") - List processTaskElements = (List) DomUtils.getChildElementsByTagName(element, "tasklet"); - if (StringUtils.hasText(taskletRef)) { - handleTaskletRef(element, taskletRef, parserContext); - stateBuilder.addConstructorArgReference(stepRef); - } - else if (processTaskElements.size() > 0) { - Element taskElement = processTaskElements.get(0); - handleTaskletElement(element, taskElement, parserContext); - stateBuilder.addConstructorArgReference(stepRef); - } - else if (StringUtils.hasText(stepRef)) { - stateBuilder.addConstructorArgReference(stepRef); - } - else { - parserContext.getReaderContext().error("Incomplete configuration detected while creating step with name " + stepRef, element); - } - return getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); - - } - - /** - * @param parserContext the parser context for the bean factory - * @param stateDef The bean definition for the current state - * @param element the <step/gt; element to parse - * @return a collection of - * {@link org.springframework.batch.core.job.flow.support.StateTransition} - * references - */ - protected static Collection getNextElements(ParserContext parserContext, 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, PAUSE, END, FAIL }) { - @SuppressWarnings("unchecked") - List transitionElements = (List) DomUtils.getChildElementsByTagName(element, - transitionName); - for (Element transitionElement : transitionElements) { - verifyUniquePattern(transitionElement, patterns, element, parserContext); - list.addAll(parseTransitionElement(transitionElement, stateDef, parserContext)); - transitionElementExists = true; - } - } - - if (!transitionElementExists) { - list.addAll(createTransition(BatchStatus.FAILED, ExitStatus.FAILED.getExitCode(), null, null, stateDef, - parserContext)); - if (!hasNextAttribute) { - list.addAll(createTransition(BatchStatus.COMPLETED, null, null, null, stateDef, parserContext)); - } - } - else if (hasNextAttribute) { - parserContext.getReaderContext().error("Step may not contain a 'next' attribute and a transition element", - element); - } - - return list; - } - - /** - * @param transitionElement The element to parse - * @param patterns a list of patterns on state transitions for this element - * @param element - * @param parserContext the parser context for the bean factory - */ - private static void verifyUniquePattern(Element transitionElement, List patterns, Element element, - ParserContext parserContext) { - String onAttribute = transitionElement.getAttribute("on"); - if (patterns.contains(onAttribute)) { - parserContext.getReaderContext().error("Duplicate transition pattern found for '" + onAttribute + "'", - element); - } - patterns.add(onAttribute); - } - - /** - * @param transitionElement The element to parse - * @param stateDef The bean definition for the current state - * @param parserContext the parser context for the bean factory - * @param a collection of - * {@link org.springframework.batch.core.job.flow.support.StateTransition} - * references - */ - private static Collection parseTransitionElement(Element transitionElement, - BeanDefinition stateDef, ParserContext parserContext) { - - BatchStatus batchStatus = getBatchStatusFromEndTransitionName(transitionElement.getNodeName()); - String onAttribute = transitionElement.getAttribute("on"); - String nextAttribute = transitionElement.getAttribute("to"); - String statusAttribute = transitionElement.getAttribute("status"); - - return createTransition(batchStatus, onAttribute, nextAttribute, statusAttribute, stateDef, parserContext); - } - - /** - * @param batchStatus The batch status that this transition will set. Use - * BatchStatus.UNKNOWN if not applicable. - * @param on The pattern that this transition should match. Use null for - * "no restriction" (same as "*"). - * @param next The state to which this transition should go. Use null if not - * applicable. - * @param exitCode The exit code that this transition will set. Use null to - * default to batchStatus. - * @param stateDef The bean definition for the current state - * @param parserContext the parser context for the bean factory - * @param a collection of - * {@link org.springframework.batch.core.job.flow.support.StateTransition} - * references - */ - private static Collection createTransition(BatchStatus batchStatus, String on, String next, - String exitCode, BeanDefinition stateDef, ParserContext parserContext) { - - RuntimeBeanReference endState = null; - - if (batchStatus == BatchStatus.STOPPED || batchStatus == BatchStatus.COMPLETED - || batchStatus == BatchStatus.FAILED) { - - BeanDefinitionBuilder endBuilder = BeanDefinitionBuilder - .genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); - endBuilder.addConstructorArgValue(batchStatus); - - boolean exitCodeExists = StringUtils.hasText(exitCode); - endBuilder.addConstructorArgValue(new ExitStatus(exitCodeExists ? exitCode : batchStatus.toString())); - - String endName = "end" + (endCounter++); - endBuilder.addConstructorArgValue(endName); - - String nextOnEnd = exitCodeExists ? null : next; - endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd); - next = endName; - - } - - Collection list = new ArrayList(); - list.add(getStateTransitionReference(parserContext, stateDef, on, next)); - if (endState != null) { - // - // Must be added after the state to ensure that the state is the - // first in the list - // - list.add(endState); - } - return list; - } - - /** - * @param elementName An end transition element name - * @return the BatchStatus corresponding to the transition name - */ - private static BatchStatus getBatchStatusFromEndTransitionName(String elementName) { - if (PAUSE.equals(elementName)) { - return BatchStatus.STOPPED; - } - else if (END.equals(elementName)) { - return BatchStatus.COMPLETED; - } - else if (FAIL.equals(elementName)) { - return BatchStatus.FAILED; - } - else { - return BatchStatus.UNKNOWN; - } - } - - /** - * @param parserContext the parser context - * @param stateDefinition a reference to the state implementation - * @param on the pattern value - * @param next the next step id - * @return a bean definition for a {@link org.springframework.batch.core.job.flow.support.StateTransition} - */ - public static RuntimeBeanReference getStateTransitionReference(ParserContext parserContext, - BeanDefinition stateDefinition, String on, String next) { - - BeanDefinitionBuilder nextBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.StateTransition"); - nextBuilder.addConstructorArgValue(stateDefinition); - - if (StringUtils.hasText(on)) { - nextBuilder.addConstructorArgValue(on); - } - - if (StringUtils.hasText(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); - - return new RuntimeBeanReference(nextDefName); - - } +public abstract class AbstractStepParser { /** * @param stepElement * @param taskletRef * @param parserContext */ - private void handleTaskletRef(Element stepElement, String taskletRef, ParserContext parserContext) { + protected AbstractBeanDefinition handleTaskletRef(Element stepElement, String taskletRef, ParserContext parserContext) { RootBeanDefinition bd = new RootBeanDefinition("org.springframework.batch.core.step.tasklet.TaskletStep", null, null); @@ -314,7 +74,8 @@ public class StepParser { bd.setRole(BeanDefinition.ROLE_SUPPORT); bd.setSource(parserContext.extractSource(stepElement)); - parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepElement.getAttribute("name"))); + + return bd; } @@ -322,7 +83,7 @@ public class StepParser { * @param element * @param parserContext */ - private void handleTaskletElement(Element stepElement, Element element, ParserContext parserContext) { + protected AbstractBeanDefinition handleTaskletElement(Element stepElement, Element element, ParserContext parserContext) { RootBeanDefinition bd; @@ -442,8 +203,9 @@ public class StepParser { bd.setRole(BeanDefinition.ROLE_SUPPORT); bd.setSource(parserContext.extractSource(stepElement)); - parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepElement.getAttribute("name"))); + return bd; + } private boolean checkIntValueForFaultToleranceNeeded(String stringValue) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java index 24d40b9b8..a312652f7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java @@ -31,6 +31,7 @@ public class CoreNamespaceHandler extends NamespaceHandlerSupport { */ public void init() { this.registerBeanDefinitionParser("job", new JobParser()); + this.registerBeanDefinitionParser("step", new TopLevelStepParser()); this.registerBeanDefinitionParser("job-repository", new JobRepositoryParser()); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/DecisionParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/DecisionParser.java index be343db6b..c893780a3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/DecisionParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/DecisionParser.java @@ -55,7 +55,7 @@ public class DecisionParser { BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.DecisionState"); stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute)); stateBuilder.addConstructorArgValue(idAttribute); - return StepParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); + return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java index 6bc340a3e..0021a1c3e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java @@ -16,16 +16,23 @@ package org.springframework.batch.core.configuration.xml; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; 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; @@ -36,6 +43,14 @@ import org.w3c.dom.NodeList; */ public class FlowParser extends AbstractSingleBeanDefinitionParser { + private static final String NEXT = "next"; + private static final String END = "end"; + private static final String FAIL = "fail"; + private static final String PAUSE = "pause"; + + // For generating unique state names for end transitions + private static int endCounter = 0; + private final String flowName; /** @@ -65,7 +80,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser { protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { List stateTransitions = new ArrayList(); - StepParser stepParser = new StepParser(); + InlineStepParser stepParser = new InlineStepParser(); DecisionParser decisionParser = new DecisionParser(); SplitParser splitParser = new SplitParser(); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), @@ -102,4 +117,192 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser { } + /** + * @param parserContext the parser context for the bean factory + * @param stateDef The bean definition for the current state + * @param element the <step/gt; element to parse + * @return a collection of + * {@link org.springframework.batch.core.job.flow.support.StateTransition} + * references + */ + protected static Collection getNextElements(ParserContext parserContext, 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, PAUSE, END, FAIL }) { + @SuppressWarnings("unchecked") + List transitionElements = (List) DomUtils.getChildElementsByTagName(element, + transitionName); + for (Element transitionElement : transitionElements) { + verifyUniquePattern(transitionElement, patterns, element, parserContext); + list.addAll(parseTransitionElement(transitionElement, stateDef, parserContext)); + transitionElementExists = true; + } + } + + if (!transitionElementExists) { + list.addAll(createTransition(BatchStatus.FAILED, ExitStatus.FAILED.getExitCode(), null, null, stateDef, + parserContext)); + if (!hasNextAttribute) { + list.addAll(createTransition(BatchStatus.COMPLETED, null, null, null, stateDef, parserContext)); + } + } + else if (hasNextAttribute) { + parserContext.getReaderContext().error("Step may not contain a 'next' attribute and a transition element", + element); + } + + return list; + } + + /** + * @param transitionElement The element to parse + * @param patterns a list of patterns on state transitions for this element + * @param element + * @param parserContext the parser context for the bean factory + */ + private static void verifyUniquePattern(Element transitionElement, List patterns, Element element, + ParserContext parserContext) { + String onAttribute = transitionElement.getAttribute("on"); + if (patterns.contains(onAttribute)) { + parserContext.getReaderContext().error("Duplicate transition pattern found for '" + onAttribute + "'", + element); + } + patterns.add(onAttribute); + } + + /** + * @param transitionElement The element to parse + * @param stateDef The bean definition for the current state + * @param parserContext the parser context for the bean factory + * @param a collection of + * {@link org.springframework.batch.core.job.flow.support.StateTransition} + * references + */ + private static Collection parseTransitionElement(Element transitionElement, + BeanDefinition stateDef, ParserContext parserContext) { + + BatchStatus batchStatus = getBatchStatusFromEndTransitionName(transitionElement.getNodeName()); + String onAttribute = transitionElement.getAttribute("on"); + String nextAttribute = transitionElement.getAttribute("to"); + String statusAttribute = transitionElement.getAttribute("status"); + + return createTransition(batchStatus, onAttribute, nextAttribute, statusAttribute, stateDef, parserContext); + } + + /** + * @param batchStatus The batch status that this transition will set. Use + * BatchStatus.UNKNOWN if not applicable. + * @param on The pattern that this transition should match. Use null for + * "no restriction" (same as "*"). + * @param next The state to which this transition should go. Use null if not + * applicable. + * @param exitCode The exit code that this transition will set. Use null to + * default to batchStatus. + * @param stateDef The bean definition for the current state + * @param parserContext the parser context for the bean factory + * @param a collection of + * {@link org.springframework.batch.core.job.flow.support.StateTransition} + * references + */ + private static Collection createTransition(BatchStatus batchStatus, String on, String next, + String exitCode, BeanDefinition stateDef, ParserContext parserContext) { + + RuntimeBeanReference endState = null; + + if (batchStatus == BatchStatus.STOPPED || batchStatus == BatchStatus.COMPLETED + || batchStatus == BatchStatus.FAILED) { + + BeanDefinitionBuilder endBuilder = BeanDefinitionBuilder + .genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); + endBuilder.addConstructorArgValue(batchStatus); + + boolean exitCodeExists = StringUtils.hasText(exitCode); + endBuilder.addConstructorArgValue(new ExitStatus(exitCodeExists ? exitCode : batchStatus.toString())); + + String endName = "end" + (endCounter++); + endBuilder.addConstructorArgValue(endName); + + String nextOnEnd = exitCodeExists ? null : next; + endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd); + next = endName; + + } + + Collection list = new ArrayList(); + list.add(getStateTransitionReference(parserContext, stateDef, on, next)); + if (endState != null) { + // + // Must be added after the state to ensure that the state is the + // first in the list + // + list.add(endState); + } + return list; + } + + /** + * @param elementName An end transition element name + * @return the BatchStatus corresponding to the transition name + */ + private static BatchStatus getBatchStatusFromEndTransitionName(String elementName) { + if (PAUSE.equals(elementName)) { + return BatchStatus.STOPPED; + } + else if (END.equals(elementName)) { + return BatchStatus.COMPLETED; + } + else if (FAIL.equals(elementName)) { + return BatchStatus.FAILED; + } + else { + return BatchStatus.UNKNOWN; + } + } + + /** + * @param parserContext the parser context + * @param stateDefinition a reference to the state implementation + * @param on the pattern value + * @param next the next step id + * @return a bean definition for a {@link org.springframework.batch.core.job.flow.support.StateTransition} + */ + public static RuntimeBeanReference getStateTransitionReference(ParserContext parserContext, + BeanDefinition stateDefinition, String on, String next) { + + BeanDefinitionBuilder nextBuilder = + BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.StateTransition"); + nextBuilder.addConstructorArgValue(stateDefinition); + + if (StringUtils.hasText(on)) { + nextBuilder.addConstructorArgValue(on); + } + + if (StringUtils.hasText(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); + + return new RuntimeBeanReference(nextDefName); + + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java new file mode 100644 index 000000000..bdbb5b6c7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java @@ -0,0 +1,86 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import java.util.Collection; +import java.util.List; + +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +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; +import org.w3c.dom.Element; + +/** + * Internal parser for the <step/> elements inside a job. A step element + * references a bean definition for a {@link org.springframework.batch.core.Step} and goes on to (optionally) + * list a set of transitions from that step to others with <next on="pattern" + * to="stepName"/>. Used by the {@link JobParser}. + * + * @see JobParser + * + * @author Dave Syer + * @author Thomas Risberg + * @since 2.0 + */ +public class InlineStepParser extends AbstractStepParser { + + /** + * Parse the step and turn it into a list of transitions. + * + * @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 org.springframework.batch.core.job.flow.support.StateTransition} + * instances objects + */ + public Collection parse(Element element, ParserContext parserContext) { + + BeanDefinitionBuilder stateBuilder = + BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.StepState"); + String stepRef = element.getAttribute("name"); + String taskletRef = element.getAttribute("tasklet"); + + if (!StringUtils.hasText(stepRef)) { + parserContext.getReaderContext().error("The name attribute can't be empty for <" + element.getNodeName() + ">", element); + } + + @SuppressWarnings("unchecked") + List processTaskElements = (List) DomUtils.getChildElementsByTagName(element, "tasklet"); + if (StringUtils.hasText(taskletRef)) { + AbstractBeanDefinition bd = handleTaskletRef(element, taskletRef, parserContext); + parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepRef)); + stateBuilder.addConstructorArgReference(stepRef); + } + else if (processTaskElements.size() > 0) { + Element taskElement = processTaskElements.get(0); + AbstractBeanDefinition bd = handleTaskletElement(element, taskElement, parserContext); + parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepRef)); + stateBuilder.addConstructorArgReference(stepRef); + } + else if (StringUtils.hasText(stepRef)) { + stateBuilder.addConstructorArgReference(stepRef); + } + else { + parserContext.getReaderContext().error("Incomplete configuration detected while creating step with name " + stepRef, element); + } + return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); + + } + +} 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 803661b26..3023eb2e3 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 @@ -44,7 +44,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { * Create a bean definition for a {@link org.springframework.batch.core.job.flow.FlowJob}. The * jobRepository attribute is a reference to a * {@link org.springframework.batch.core.repository.JobRepository} and defaults to "jobRepository". Nested step - * elements are delegated to a {@link StepParser}. + * elements are delegated to an {@link InlineStepParser}. * * @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext, BeanDefinitionBuilder) */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java index b74109120..7ebfcf8a5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java @@ -76,7 +76,7 @@ public class SplitParser { stateBuilder.addConstructorArgValue(idAttribute); // TODO: allow TaskExecutor etc. to be set - return StepParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); + return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java new file mode 100644 index 000000000..2d920af5a --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java @@ -0,0 +1,71 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import java.util.List; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Internal parser for the <step/> elements inside a job. A step element + * references a bean definition for a {@link org.springframework.batch.core.Step} and goes on to (optionally) + * list a set of transitions from that step to others with <next on="pattern" + * to="stepName"/>. Used by the {@link JobParser}. + * + * @see JobParser + * + * @author Dave Syer + * @author Thomas Risberg + * @since 2.0 + */ +public class StandaloneStepParser extends AbstractStepParser { + + /** + * Parse the step and turn it into a list of transitions. + * + * @param element the <step/gt; element to parse + * @param parserContext the parser context for the bean factory + */ + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + +// String stepId = element.getAttribute("id"); + String taskletRef = element.getAttribute("tasklet"); + +// TODO: this should be required in xsd +// if (!StringUtils.hasText(stepId)) { +// parserContext.getReaderContext().error("The id attribute can't be empty for <" + element.getNodeName() + ">", element); +// } + + @SuppressWarnings("unchecked") + List processTaskElements = (List) DomUtils.getChildElementsByTagName(element, "tasklet"); + AbstractBeanDefinition bd = null; + if (StringUtils.hasText(taskletRef)) { + bd = handleTaskletRef(element, taskletRef, parserContext); + } + else if (processTaskElements.size() > 0) { + Element taskElement = processTaskElements.get(0); + bd = handleTaskletElement(element, taskElement, parserContext); + } + + return bd; + + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java new file mode 100644 index 000000000..38180792b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * Parser for the lt;step/gt; top level element in the Batch namespace. Sets up and returns + * a bean definition for a {@link org.springframework.batch.core.Step}. + * + * @author Thomas Risberg + * + */ +public class TopLevelStepParser extends AbstractBeanDefinitionParser { + + @Override + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + StandaloneStepParser stepParser = new StandaloneStepParser(); + return stepParser.parse(element, parserContext); + } + +} diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 5fea6d6bb..8c72ee686 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -59,6 +59,36 @@ + + + + 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 bean + definitions. + + + + + + + + + + The tasklet is a reference to another bean definition that defines implements the Tasklet interface. + + + + + + + + + + + + +