From 73bfca8bb89ccb48ffb461072aa373091db7080c Mon Sep 17 00:00:00 2001 From: dsyer Date: Tue, 27 Jan 2009 16:05:25 +0000 Subject: [PATCH] CLOSED - issue BATCH-1024: FlowJob's start state should be the first state listed in the config --- .../core/configuration/xml/FlowParser.java | 163 +-- .../core/configuration/xml/StepParser.java | 1143 +++++++++-------- .../core/job/flow/support/SimpleFlow.java | 518 ++++---- .../batch/core/job/flow/FlowJobTests.java | 24 +- .../job/flow/support/SimpleFlowTests.java | 484 ++++--- 5 files changed, 1149 insertions(+), 1183 deletions(-) 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 9310db40f..fee90dcd9 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 @@ -1,80 +1,83 @@ -/* - * 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 java.util.ArrayList; -import java.util.List; - -import org.springframework.beans.factory.config.RuntimeBeanReference; -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.ParserContext; -import org.springframework.util.xml.DomUtils; - -import org.w3c.dom.Element; - -/** - * @author Dave Syer - * - */ -public class FlowParser { - - /** - * @param element the top level element containing a flow definition - * @param parserContext the {@link ParserContext} - * @param flowName the name of the flow - * @return a bean definition for a {@link org.springframework.batch.core.job.flow.Flow} - */ - public AbstractBeanDefinition parse(Element element, ParserContext parserContext, String flowName) { - List stateTransitions = new ArrayList(); - - @SuppressWarnings("unchecked") - List stepElements = (List) DomUtils.getChildElementsByTagName(element, "step"); - StepParser stepParser = new StepParser(); - for (Element stepElement : stepElements) { - stateTransitions.addAll(stepParser.parse(stepElement, parserContext)); - } - - @SuppressWarnings("unchecked") - List decisionElements = (List) DomUtils.getChildElementsByTagName(element, "decision"); - DecisionParser decisionParser = new DecisionParser(); - for (Element stepElement : decisionElements) { - stateTransitions.addAll(decisionParser.parse(stepElement, parserContext)); - } - - @SuppressWarnings("unchecked") - List splitElements = (List) DomUtils.getChildElementsByTagName(element, "split"); - SplitParser splitParser = new SplitParser(); - for (Element stepElement : splitElements) { - stateTransitions.addAll(splitParser.parse(stepElement, parserContext)); - } - - BeanDefinitionBuilder flowBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.SimpleFlow"); - flowBuilder.addConstructorArgValue(flowName ); - ManagedList managedList = new ManagedList(); - @SuppressWarnings( { "unchecked", "unused" }) - boolean dummy = managedList.addAll(stateTransitions); - flowBuilder.addPropertyValue("stateTransitions", managedList); - AbstractBeanDefinition flowDef = flowBuilder.getBeanDefinition(); - parserContext.getReaderContext().registerWithGeneratedName(flowDef); - - return flowDef; - - } - -} +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.config.RuntimeBeanReference; +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.ParserContext; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * @author Dave Syer + * + */ +public class FlowParser { + + /** + * @param element the top level element containing a flow definition + * @param parserContext the {@link ParserContext} + * @param flowName the name of the flow + * @return a bean definition for a {@link org.springframework.batch.core.job.flow.Flow} + */ + public AbstractBeanDefinition parse(Element element, ParserContext parserContext, String flowName) { + List stateTransitions = new ArrayList(); + + StepParser stepParser = new StepParser(); + DecisionParser decisionParser = new DecisionParser(); + SplitParser splitParser = new SplitParser(); + + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + String nodeName = node.getLocalName(); + if(nodeName.equals("step")) + { + stateTransitions.addAll(stepParser.parse((Element)node, parserContext)); + } + else if(nodeName.equals("decision")) + { + stateTransitions.addAll(decisionParser.parse((Element)node, parserContext)); + } + else if(nodeName.equals("split")) + { + stateTransitions.addAll(splitParser.parse((Element)node, parserContext)); + } + } + } + + BeanDefinitionBuilder flowBuilder = BeanDefinitionBuilder + .genericBeanDefinition("org.springframework.batch.core.job.flow.support.SimpleFlow"); + flowBuilder.addConstructorArgValue(flowName); + ManagedList managedList = new ManagedList(); + @SuppressWarnings( { "unchecked", "unused" }) + boolean dummy = managedList.addAll(stateTransitions); + flowBuilder.addPropertyValue("stateTransitions", managedList); + AbstractBeanDefinition flowDef = flowBuilder.getBeanDefinition(); + parserContext.getReaderContext().registerWithGeneratedName(flowDef); + + return flowDef; + + } + +} 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 2ecda7a8f..544154820 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 @@ -1,567 +1,576 @@ -/* - * 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.ArrayList; -import java.util.Collection; -import java.util.List; - -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.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.support.RootBeanDefinition; -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.NamedNodeMap; - -/** - * 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 StepParser { - - // 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)) { - BeanDefinition task = handleTaskletRef(element, taskletRef, parserContext); - parserContext.getRegistry().registerBeanDefinition(stepRef, task); - stateBuilder.addConstructorArgReference(stepRef); - } - else if (processTaskElements.size() > 0) { - BeanDefinition task = handleTaskletElement(element, processTaskElements.get(0), parserContext); - parserContext.getRegistry().registerBeanDefinition(stepRef, task); - 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 - * @param stateDef - * @param element - * @return a collection of {@link org.springframework.batch.core.job.flow.support.StateTransition} references - */ - public 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)); - } - - @SuppressWarnings("unchecked") - List nextElements = (List) DomUtils.getChildElementsByTagName(element, "next"); - @SuppressWarnings("unchecked") - List stopElements = (List) DomUtils.getChildElementsByTagName(element, "stop"); - nextElements.addAll(stopElements); - @SuppressWarnings("unchecked") - List endElements = (List) DomUtils.getChildElementsByTagName(element, "end"); - nextElements.addAll(endElements); - - for (Element nextElement : nextElements) { - String onAttribute = nextElement.getAttribute("on"); - String nextAttribute = nextElement.getAttribute("to"); - if (hasNextAttribute && onAttribute.equals("*")) { - parserContext.getReaderContext().error("Duplicate transition pattern found for '*' " - + "(only specify one of next= attribute at step level and next element with on='*')", - element); - } - - String name = nextElement.getNodeName(); - if ("stop".equals(name) || "end".equals(name)) { - - String statusName = nextElement.getAttribute("status"); - String status = StringUtils.hasText(statusName) ? statusName : "STOPPED"; - String nextOnEnd = StringUtils.hasText(statusName) ? null : nextAttribute; - - BeanDefinitionBuilder endBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); - endBuilder.addConstructorArgValue(status); - String endName = "stop".equals(name) ? "end" + (endCounter++) : null; - - endBuilder.addConstructorArgValue(endName); - list.add(getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd)); - nextAttribute = endName; - - } - list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute)); - } - - if(hasNextAttribute && nextElements.isEmpty()) - { - list.add(getStateTransitionReference(parserContext, stateDef, "FAILED", null)); - } - - if (list.isEmpty() && !hasNextAttribute) { - list.add(getStateTransitionReference(parserContext, stateDef, null, null)); - } - - return list; - } - - /** - * @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); - - } - - /** - * @param stepElement - * @param taskletRef - * @param parserContext - * @return the TaskletStep bean - */ - protected BeanDefinition handleTaskletRef(Element stepElement, String taskletRef, ParserContext parserContext) { - - RootBeanDefinition bd = new RootBeanDefinition("org.springframework.batch.core.step.tasklet.TaskletStep", null, null); - - if (StringUtils.hasText(taskletRef)) { - RuntimeBeanReference taskletBeanRef = new RuntimeBeanReference(taskletRef); - bd.getPropertyValues().addPropertyValue("tasklet", taskletBeanRef); - } - - String jobRepositoryRef = stepElement.getAttribute("job-repository"); - RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(jobRepositoryRef); - bd.getPropertyValues().addPropertyValue("jobRepository", jobRepositoryBeanRef); - - String transactionManagerRef = stepElement.getAttribute("transaction-manager"); - RuntimeBeanReference transactionManagerBeanRef = new RuntimeBeanReference(transactionManagerRef); - bd.getPropertyValues().addPropertyValue("transactionManager", transactionManagerBeanRef); - - handleListenersElement(stepElement, bd, parserContext, "stepExecutionListeners"); - - bd.setRole(BeanDefinition.ROLE_SUPPORT); - - return bd; - - } - - /** - * @param element - * @param parserContext - * @return the TaskletStep bean - */ - protected BeanDefinition handleTaskletElement(Element stepElement, Element element, ParserContext parserContext) { - - RootBeanDefinition bd; - - boolean isFaultTolerant = false; - - String skipLimit = element.getAttribute("skip-limit"); - if (!isFaultTolerant) { - isFaultTolerant = checkIntValueForFaultToleranceNeeded(skipLimit); - } - String retryLimit = element.getAttribute("retry-limit"); - if (!isFaultTolerant) { - isFaultTolerant = checkIntValueForFaultToleranceNeeded(retryLimit); - } - String cacheCapacity = element.getAttribute("cache-capacity"); - if (!isFaultTolerant) { - isFaultTolerant = checkIntValueForFaultToleranceNeeded(cacheCapacity); - } - String isReaderTransactionalQueue = element.getAttribute("is-reader-transactional-queue"); - if (!isFaultTolerant && StringUtils.hasText(isReaderTransactionalQueue)) { - if ("true".equals(isReaderTransactionalQueue)) { - isFaultTolerant = true; - } - } - checkExceptionElementForFaultToleranceNeeded(element, "skippable-exception-classes"); - checkExceptionElementForFaultToleranceNeeded(element, "retryable-exception-classes"); - checkExceptionElementForFaultToleranceNeeded(element, "fatal-exception-classes"); - - if (isFaultTolerant) { - bd = new RootBeanDefinition("org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean", null, null); - } - else { - bd = new RootBeanDefinition("org.springframework.batch.core.step.item.SimpleStepFactoryBean", null, null); - } - - // now, set the properties on the new bean - String startLimit = element.getAttribute("start-limit"); - if (StringUtils.hasText(startLimit)) { - bd.getPropertyValues().addPropertyValue("startLimit", startLimit); - } - String allowStartIfComplete = element.getAttribute("allow-start-if-complete"); - if (StringUtils.hasText(allowStartIfComplete)) { - bd.getPropertyValues().addPropertyValue("allowStartIfComplete", allowStartIfComplete); - } - - String readerBeanId = element.getAttribute("reader"); - if (StringUtils.hasText(readerBeanId)) { - RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); - bd.getPropertyValues().addPropertyValue("itemReader", readerRef); - } - - String processorBeanId = element.getAttribute("processor"); - if (StringUtils.hasText(processorBeanId)) { - RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); - bd.getPropertyValues().addPropertyValue("itemProcessor", processorRef); - } - - String writerBeanId = element.getAttribute("writer"); - if (StringUtils.hasText(writerBeanId)) { - RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); - bd.getPropertyValues().addPropertyValue("itemWriter", writerRef); - } - - String taskExecutorBeanId = element.getAttribute("task-executor"); - if (StringUtils.hasText(taskExecutorBeanId)) { - RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); - bd.getPropertyValues().addPropertyValue("taskExecutor", taskExecutorRef); - } - - String jobRepository = stepElement.getAttribute("job-repository"); - RuntimeBeanReference jobRepositoryRef = new RuntimeBeanReference(jobRepository); - bd.getPropertyValues().addPropertyValue("jobRepository", jobRepositoryRef); - - String transactionManager = stepElement.getAttribute("transaction-manager"); - RuntimeBeanReference tx = new RuntimeBeanReference(transactionManager); - bd.getPropertyValues().addPropertyValue("transactionManager", tx); - - String commitInterval = element.getAttribute("commit-interval"); - if (StringUtils.hasText(commitInterval)) { - bd.getPropertyValues().addPropertyValue("commitInterval", commitInterval); - } - - if (StringUtils.hasText(skipLimit)) { - bd.getPropertyValues().addPropertyValue("skipLimit", skipLimit); - } - - if (StringUtils.hasText(retryLimit)) { - bd.getPropertyValues().addPropertyValue("retryLimit", retryLimit); - } - - if (StringUtils.hasText(cacheCapacity)) { - bd.getPropertyValues().addPropertyValue("cacheCapacity", cacheCapacity); - } - - String transactionAttribute = element.getAttribute("transaction-attribute"); - if (StringUtils.hasText(transactionAttribute)) { - bd.getPropertyValues().addPropertyValue("transactionAttribute", transactionAttribute); - } - - if (StringUtils.hasText(isReaderTransactionalQueue)) { - if (isFaultTolerant) { - bd.getPropertyValues().addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); - } - } - - handleExceptionElement(element, parserContext, bd, "skippable-exception-classes", "skippableExceptionClasses", isFaultTolerant); - - handleExceptionElement(element, parserContext, bd, "retryable-exception-classes", "retryableExceptionClasses", isFaultTolerant); - - handleExceptionElement(element, parserContext, bd, "fatal-exception-classes", "fatalExceptionClasses", isFaultTolerant); - - handleListenersElement(stepElement, bd, parserContext, "listeners"); - - handleRetryListenersElement(element, bd, parserContext); - - handleStreamsElement(element, bd, parserContext); - - bd.setRole(BeanDefinition.ROLE_SUPPORT); - - return bd; - - } - - private boolean checkIntValueForFaultToleranceNeeded(String stringValue) { - if (StringUtils.hasText(stringValue)) { - int value = Integer.valueOf(stringValue); - if (value > 0) { - return true; - } - } - return false; - } - - private boolean checkExceptionElementForFaultToleranceNeeded(Element element, String subElementName) { - String exceptions = - DomUtils.getChildElementValueByTagName(element, subElementName); - if (StringUtils.hasLength(exceptions)) { - return true; - } - return false; - } - - private void handleExceptionElement(Element element, ParserContext parserContext, BeanDefinition bd, - String subElementName, String propertyName, boolean isFaultTolerant) { - String exceptions = - DomUtils.getChildElementValueByTagName(element, subElementName); - if (StringUtils.hasLength(exceptions)) { - if (isFaultTolerant) { - String[] exceptionArray = StringUtils.tokenizeToStringArray( - StringUtils.delete(exceptions, ","), "\n"); - if (exceptionArray.length > 0) { - bd.getPropertyValues().addPropertyValue(propertyName, exceptionArray); - } - } - else { - parserContext.getReaderContext().error(subElementName + " can only be specified for fault-tolerant " + - "configurations providing skip-limit, retry-limit or cache-capacity", element); - } - } - } - - @SuppressWarnings("unchecked") - private void handleListenersElement(Element element, BeanDefinition bd, ParserContext parserContext, String property) { - Element listenersElement = - DomUtils.getChildElementByTagName(element, "listeners"); - if (listenersElement != null) { - List listenerBeans = new ArrayList(); - handleStepListenerElements(parserContext, listenersElement, - listenerBeans); - ManagedList arguments = new ManagedList(); - arguments.addAll(listenerBeans); - bd.getPropertyValues().addPropertyValue(property, arguments); - } - } - - @SuppressWarnings("unchecked") - private void handleRetryListenersElement(Element element, BeanDefinition bd, ParserContext parserContext) { - Element retryListenersElement = - DomUtils.getChildElementByTagName(element, "retry-listeners"); - if (retryListenersElement != null) { - List retryListenerBeans = new ArrayList(); - handleListenerElements(parserContext, retryListenersElement, - retryListenerBeans); - ManagedList arguments = new ManagedList(); - arguments.addAll(retryListenerBeans); - bd.getPropertyValues().addPropertyValue("retryListeners", arguments); - } - } - - @SuppressWarnings("unchecked") - private void handleListenerElements(ParserContext parserContext, - Element element, List beans) { - List listenerElements = - DomUtils.getChildElementsByTagName(element, "listener"); - if (listenerElements != null) { - for (Element listenerElement : listenerElements) { - String id = listenerElement.getAttribute("id"); - String listenerRef = listenerElement.getAttribute("ref"); - String className = listenerElement.getAttribute("class"); - checkListenerElementAttributes(parserContext, element, - listenerElement, id, listenerRef, className); - if (StringUtils.hasText(listenerRef)) { - BeanReference bean = new RuntimeBeanReference(listenerRef); - beans.add(bean); - } - else if (StringUtils.hasText(className)) { - RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); - if (!StringUtils.hasText(id)) { - id = parserContext.getReaderContext().generateBeanName(beanDef); - } - parserContext.getRegistry().registerBeanDefinition(id, beanDef); - BeanReference bean = new RuntimeBeanReference(id); - beans.add(bean); - } - else { - parserContext.getReaderContext().error("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", element); - } - } - } - } - - @SuppressWarnings("unchecked") - private void handleStepListenerElements(ParserContext parserContext, - Element element, List beans) { - List listenerElements = - DomUtils.getChildElementsByTagName(element, "listener"); - if (listenerElements != null) { - for (Element listenerElement : listenerElements) { - BeanDefinitionBuilder listenerBuilder = - BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.listener.StepListenerFactoryBean"); - String id = listenerElement.getAttribute("id"); - String listenerRef = listenerElement.getAttribute("ref"); - String className = listenerElement.getAttribute("class"); - checkListenerElementAttributes(parserContext, element, - listenerElement, id, listenerRef, className); - if (StringUtils.hasText(listenerRef)) { - listenerBuilder.addPropertyReference("delegate", listenerRef); - } - else if (StringUtils.hasText(className)) { - RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); - String delegateId = parserContext.getReaderContext().generateBeanName(beanDef); - parserContext.getRegistry().registerBeanDefinition(delegateId, beanDef); - listenerBuilder.addPropertyReference("delegate", delegateId); - } - else { - parserContext.getReaderContext().error("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", element); - } - - ManagedMap metaDataMap = new ManagedMap(); - String[] methodNameAttributes = new String[] { - "before-step-method", - "after-step-method", - "before-chunk-method", - "after-chunk-method", - "before-read-method", - "after-read-method", - "on-read-error-method", - "before-process-method", - "after-process-method", - "on-process-error-method", - "before-write-method", - "after-write-method", - "on-write-error-method", - "on-skip-in-read-method", - "on-skip-in-process-method", - "on-skip-in-write-method" - }; - for (String metaDataPropertyName : methodNameAttributes) { - String listenerMethod = listenerElement.getAttribute(metaDataPropertyName); - if(StringUtils.hasText(listenerMethod)){ - metaDataMap.put(metaDataPropertyName, listenerMethod); - } - } - listenerBuilder.addPropertyValue("metaDataMap", metaDataMap); - - AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition(); - if (!StringUtils.hasText(id)) { - id = parserContext.getReaderContext().generateBeanName(beanDef); - } - parserContext.getRegistry().registerBeanDefinition(id, beanDef); - BeanReference bean = new RuntimeBeanReference(id); - beans.add(bean); - } - } - } - - private void checkListenerElementAttributes(ParserContext parserContext, - Element element, Element listenerElement, String id, - String listenerRef, String className) { - if ((StringUtils.hasText(id) || StringUtils.hasText(className)) - && StringUtils.hasText(listenerRef)) { - NamedNodeMap attributeNodes = listenerElement.getAttributes(); - StringBuilder attributes = new StringBuilder(); - for (int i = 0; i < attributeNodes.getLength(); i++) { - if (i > 0) { - attributes.append(" "); - } - attributes.append(attributeNodes.item(i)); - } - parserContext.getReaderContext().error("Both 'ref' and " + - (StringUtils.hasText(id) ? "'id'" : "'class'") + - " specified; use 'class' with an optional 'id' or just 'ref' for <" + - listenerElement.getTagName() + "> element specified with attributes: " + attributes, element); - } - } - - @SuppressWarnings("unchecked") - private void handleStreamsElement(Element element, BeanDefinition bd, ParserContext parserContext) { - Element streamsElement = - DomUtils.getChildElementByTagName(element, "streams"); - if (streamsElement != null) { - List streamBeans = new ArrayList(); - List streamElements = - DomUtils.getChildElementsByTagName(streamsElement, "stream"); - if (streamElements != null) { - for (Element streamElement : streamElements) { - String streamRef = streamElement.getAttribute("ref"); - if (StringUtils.hasText(streamRef)) { - BeanReference bean = new RuntimeBeanReference(streamRef); - streamBeans.add(bean); - } - else { - parserContext.getReaderContext().error("ref not specified for <" + streamElement.getTagName() + "> element", element); - } - } - } - ManagedList arguments = new ManagedList(); - arguments.addAll(streamBeans); - bd.getPropertyValues().addPropertyValue("streams", arguments); - } - } - -} +/* + * 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.ArrayList; +import java.util.Collection; +import java.util.List; + +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.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.RootBeanDefinition; +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.NamedNodeMap; + +/** + * 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 StepParser { + + // 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)) { + BeanDefinition task = handleTaskletRef(element, taskletRef, parserContext); + parserContext.getRegistry().registerBeanDefinition(stepRef, task); + stateBuilder.addConstructorArgReference(stepRef); + } + else if (processTaskElements.size() > 0) { + BeanDefinition task = handleTaskletElement(element, processTaskElements.get(0), parserContext); + parserContext.getRegistry().registerBeanDefinition(stepRef, task); + 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 + * @param stateDef + * @param element + * @return a collection of {@link org.springframework.batch.core.job.flow.support.StateTransition} references + */ + public 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)); + } + + @SuppressWarnings("unchecked") + List nextElements = (List) DomUtils.getChildElementsByTagName(element, "next"); + @SuppressWarnings("unchecked") + List stopElements = (List) DomUtils.getChildElementsByTagName(element, "stop"); + nextElements.addAll(stopElements); + @SuppressWarnings("unchecked") + List endElements = (List) DomUtils.getChildElementsByTagName(element, "end"); + nextElements.addAll(endElements); + + for (Element nextElement : nextElements) { + String onAttribute = nextElement.getAttribute("on"); + String nextAttribute = nextElement.getAttribute("to"); + if (hasNextAttribute && onAttribute.equals("*")) { + parserContext.getReaderContext().error("Duplicate transition pattern found for '*' " + + "(only specify one of next= attribute at step level and next element with on='*')", + element); + } + + RuntimeBeanReference additionalState = null; + + String name = nextElement.getNodeName(); + if ("stop".equals(name) || "end".equals(name)) { + + String statusName = nextElement.getAttribute("status"); + String status = StringUtils.hasText(statusName) ? statusName : "STOPPED"; + String nextOnEnd = StringUtils.hasText(statusName) ? null : nextAttribute; + + BeanDefinitionBuilder endBuilder = + BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState"); + endBuilder.addConstructorArgValue(status); + String endName = "stop".equals(name) ? "end" + (endCounter++) : null; + + endBuilder.addConstructorArgValue(endName); + additionalState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd); + nextAttribute = endName; + + } + list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute)); + if(additionalState != null) + { + // + // Must be added after the state to ensure that the state is the first in the list + // + list.add(additionalState); + } + } + + if(hasNextAttribute && nextElements.isEmpty()) + { + list.add(getStateTransitionReference(parserContext, stateDef, "FAILED", null)); + } + + if (list.isEmpty() && !hasNextAttribute) { + list.add(getStateTransitionReference(parserContext, stateDef, null, null)); + } + + return list; + } + + /** + * @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); + + } + + /** + * @param stepElement + * @param taskletRef + * @param parserContext + * @return the TaskletStep bean + */ + protected BeanDefinition handleTaskletRef(Element stepElement, String taskletRef, ParserContext parserContext) { + + RootBeanDefinition bd = new RootBeanDefinition("org.springframework.batch.core.step.tasklet.TaskletStep", null, null); + + if (StringUtils.hasText(taskletRef)) { + RuntimeBeanReference taskletBeanRef = new RuntimeBeanReference(taskletRef); + bd.getPropertyValues().addPropertyValue("tasklet", taskletBeanRef); + } + + String jobRepositoryRef = stepElement.getAttribute("job-repository"); + RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(jobRepositoryRef); + bd.getPropertyValues().addPropertyValue("jobRepository", jobRepositoryBeanRef); + + String transactionManagerRef = stepElement.getAttribute("transaction-manager"); + RuntimeBeanReference transactionManagerBeanRef = new RuntimeBeanReference(transactionManagerRef); + bd.getPropertyValues().addPropertyValue("transactionManager", transactionManagerBeanRef); + + handleListenersElement(stepElement, bd, parserContext, "stepExecutionListeners"); + + bd.setRole(BeanDefinition.ROLE_SUPPORT); + + return bd; + + } + + /** + * @param element + * @param parserContext + * @return the TaskletStep bean + */ + protected BeanDefinition handleTaskletElement(Element stepElement, Element element, ParserContext parserContext) { + + RootBeanDefinition bd; + + boolean isFaultTolerant = false; + + String skipLimit = element.getAttribute("skip-limit"); + if (!isFaultTolerant) { + isFaultTolerant = checkIntValueForFaultToleranceNeeded(skipLimit); + } + String retryLimit = element.getAttribute("retry-limit"); + if (!isFaultTolerant) { + isFaultTolerant = checkIntValueForFaultToleranceNeeded(retryLimit); + } + String cacheCapacity = element.getAttribute("cache-capacity"); + if (!isFaultTolerant) { + isFaultTolerant = checkIntValueForFaultToleranceNeeded(cacheCapacity); + } + String isReaderTransactionalQueue = element.getAttribute("is-reader-transactional-queue"); + if (!isFaultTolerant && StringUtils.hasText(isReaderTransactionalQueue)) { + if ("true".equals(isReaderTransactionalQueue)) { + isFaultTolerant = true; + } + } + checkExceptionElementForFaultToleranceNeeded(element, "skippable-exception-classes"); + checkExceptionElementForFaultToleranceNeeded(element, "retryable-exception-classes"); + checkExceptionElementForFaultToleranceNeeded(element, "fatal-exception-classes"); + + if (isFaultTolerant) { + bd = new RootBeanDefinition("org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean", null, null); + } + else { + bd = new RootBeanDefinition("org.springframework.batch.core.step.item.SimpleStepFactoryBean", null, null); + } + + // now, set the properties on the new bean + String startLimit = element.getAttribute("start-limit"); + if (StringUtils.hasText(startLimit)) { + bd.getPropertyValues().addPropertyValue("startLimit", startLimit); + } + String allowStartIfComplete = element.getAttribute("allow-start-if-complete"); + if (StringUtils.hasText(allowStartIfComplete)) { + bd.getPropertyValues().addPropertyValue("allowStartIfComplete", allowStartIfComplete); + } + + String readerBeanId = element.getAttribute("reader"); + if (StringUtils.hasText(readerBeanId)) { + RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); + bd.getPropertyValues().addPropertyValue("itemReader", readerRef); + } + + String processorBeanId = element.getAttribute("processor"); + if (StringUtils.hasText(processorBeanId)) { + RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); + bd.getPropertyValues().addPropertyValue("itemProcessor", processorRef); + } + + String writerBeanId = element.getAttribute("writer"); + if (StringUtils.hasText(writerBeanId)) { + RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); + bd.getPropertyValues().addPropertyValue("itemWriter", writerRef); + } + + String taskExecutorBeanId = element.getAttribute("task-executor"); + if (StringUtils.hasText(taskExecutorBeanId)) { + RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); + bd.getPropertyValues().addPropertyValue("taskExecutor", taskExecutorRef); + } + + String jobRepository = stepElement.getAttribute("job-repository"); + RuntimeBeanReference jobRepositoryRef = new RuntimeBeanReference(jobRepository); + bd.getPropertyValues().addPropertyValue("jobRepository", jobRepositoryRef); + + String transactionManager = stepElement.getAttribute("transaction-manager"); + RuntimeBeanReference tx = new RuntimeBeanReference(transactionManager); + bd.getPropertyValues().addPropertyValue("transactionManager", tx); + + String commitInterval = element.getAttribute("commit-interval"); + if (StringUtils.hasText(commitInterval)) { + bd.getPropertyValues().addPropertyValue("commitInterval", commitInterval); + } + + if (StringUtils.hasText(skipLimit)) { + bd.getPropertyValues().addPropertyValue("skipLimit", skipLimit); + } + + if (StringUtils.hasText(retryLimit)) { + bd.getPropertyValues().addPropertyValue("retryLimit", retryLimit); + } + + if (StringUtils.hasText(cacheCapacity)) { + bd.getPropertyValues().addPropertyValue("cacheCapacity", cacheCapacity); + } + + String transactionAttribute = element.getAttribute("transaction-attribute"); + if (StringUtils.hasText(transactionAttribute)) { + bd.getPropertyValues().addPropertyValue("transactionAttribute", transactionAttribute); + } + + if (StringUtils.hasText(isReaderTransactionalQueue)) { + if (isFaultTolerant) { + bd.getPropertyValues().addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); + } + } + + handleExceptionElement(element, parserContext, bd, "skippable-exception-classes", "skippableExceptionClasses", isFaultTolerant); + + handleExceptionElement(element, parserContext, bd, "retryable-exception-classes", "retryableExceptionClasses", isFaultTolerant); + + handleExceptionElement(element, parserContext, bd, "fatal-exception-classes", "fatalExceptionClasses", isFaultTolerant); + + handleListenersElement(stepElement, bd, parserContext, "listeners"); + + handleRetryListenersElement(element, bd, parserContext); + + handleStreamsElement(element, bd, parserContext); + + bd.setRole(BeanDefinition.ROLE_SUPPORT); + + return bd; + + } + + private boolean checkIntValueForFaultToleranceNeeded(String stringValue) { + if (StringUtils.hasText(stringValue)) { + int value = Integer.valueOf(stringValue); + if (value > 0) { + return true; + } + } + return false; + } + + private boolean checkExceptionElementForFaultToleranceNeeded(Element element, String subElementName) { + String exceptions = + DomUtils.getChildElementValueByTagName(element, subElementName); + if (StringUtils.hasLength(exceptions)) { + return true; + } + return false; + } + + private void handleExceptionElement(Element element, ParserContext parserContext, BeanDefinition bd, + String subElementName, String propertyName, boolean isFaultTolerant) { + String exceptions = + DomUtils.getChildElementValueByTagName(element, subElementName); + if (StringUtils.hasLength(exceptions)) { + if (isFaultTolerant) { + String[] exceptionArray = StringUtils.tokenizeToStringArray( + StringUtils.delete(exceptions, ","), "\n"); + if (exceptionArray.length > 0) { + bd.getPropertyValues().addPropertyValue(propertyName, exceptionArray); + } + } + else { + parserContext.getReaderContext().error(subElementName + " can only be specified for fault-tolerant " + + "configurations providing skip-limit, retry-limit or cache-capacity", element); + } + } + } + + @SuppressWarnings("unchecked") + private void handleListenersElement(Element element, BeanDefinition bd, ParserContext parserContext, String property) { + Element listenersElement = + DomUtils.getChildElementByTagName(element, "listeners"); + if (listenersElement != null) { + List listenerBeans = new ArrayList(); + handleStepListenerElements(parserContext, listenersElement, + listenerBeans); + ManagedList arguments = new ManagedList(); + arguments.addAll(listenerBeans); + bd.getPropertyValues().addPropertyValue(property, arguments); + } + } + + @SuppressWarnings("unchecked") + private void handleRetryListenersElement(Element element, BeanDefinition bd, ParserContext parserContext) { + Element retryListenersElement = + DomUtils.getChildElementByTagName(element, "retry-listeners"); + if (retryListenersElement != null) { + List retryListenerBeans = new ArrayList(); + handleListenerElements(parserContext, retryListenersElement, + retryListenerBeans); + ManagedList arguments = new ManagedList(); + arguments.addAll(retryListenerBeans); + bd.getPropertyValues().addPropertyValue("retryListeners", arguments); + } + } + + @SuppressWarnings("unchecked") + private void handleListenerElements(ParserContext parserContext, + Element element, List beans) { + List listenerElements = + DomUtils.getChildElementsByTagName(element, "listener"); + if (listenerElements != null) { + for (Element listenerElement : listenerElements) { + String id = listenerElement.getAttribute("id"); + String listenerRef = listenerElement.getAttribute("ref"); + String className = listenerElement.getAttribute("class"); + checkListenerElementAttributes(parserContext, element, + listenerElement, id, listenerRef, className); + if (StringUtils.hasText(listenerRef)) { + BeanReference bean = new RuntimeBeanReference(listenerRef); + beans.add(bean); + } + else if (StringUtils.hasText(className)) { + RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); + if (!StringUtils.hasText(id)) { + id = parserContext.getReaderContext().generateBeanName(beanDef); + } + parserContext.getRegistry().registerBeanDefinition(id, beanDef); + BeanReference bean = new RuntimeBeanReference(id); + beans.add(bean); + } + else { + parserContext.getReaderContext().error("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", element); + } + } + } + } + + @SuppressWarnings("unchecked") + private void handleStepListenerElements(ParserContext parserContext, + Element element, List beans) { + List listenerElements = + DomUtils.getChildElementsByTagName(element, "listener"); + if (listenerElements != null) { + for (Element listenerElement : listenerElements) { + BeanDefinitionBuilder listenerBuilder = + BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.listener.StepListenerFactoryBean"); + String id = listenerElement.getAttribute("id"); + String listenerRef = listenerElement.getAttribute("ref"); + String className = listenerElement.getAttribute("class"); + checkListenerElementAttributes(parserContext, element, + listenerElement, id, listenerRef, className); + if (StringUtils.hasText(listenerRef)) { + listenerBuilder.addPropertyReference("delegate", listenerRef); + } + else if (StringUtils.hasText(className)) { + RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); + String delegateId = parserContext.getReaderContext().generateBeanName(beanDef); + parserContext.getRegistry().registerBeanDefinition(delegateId, beanDef); + listenerBuilder.addPropertyReference("delegate", delegateId); + } + else { + parserContext.getReaderContext().error("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", element); + } + + ManagedMap metaDataMap = new ManagedMap(); + String[] methodNameAttributes = new String[] { + "before-step-method", + "after-step-method", + "before-chunk-method", + "after-chunk-method", + "before-read-method", + "after-read-method", + "on-read-error-method", + "before-process-method", + "after-process-method", + "on-process-error-method", + "before-write-method", + "after-write-method", + "on-write-error-method", + "on-skip-in-read-method", + "on-skip-in-process-method", + "on-skip-in-write-method" + }; + for (String metaDataPropertyName : methodNameAttributes) { + String listenerMethod = listenerElement.getAttribute(metaDataPropertyName); + if(StringUtils.hasText(listenerMethod)){ + metaDataMap.put(metaDataPropertyName, listenerMethod); + } + } + listenerBuilder.addPropertyValue("metaDataMap", metaDataMap); + + AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition(); + if (!StringUtils.hasText(id)) { + id = parserContext.getReaderContext().generateBeanName(beanDef); + } + parserContext.getRegistry().registerBeanDefinition(id, beanDef); + BeanReference bean = new RuntimeBeanReference(id); + beans.add(bean); + } + } + } + + private void checkListenerElementAttributes(ParserContext parserContext, + Element element, Element listenerElement, String id, + String listenerRef, String className) { + if ((StringUtils.hasText(id) || StringUtils.hasText(className)) + && StringUtils.hasText(listenerRef)) { + NamedNodeMap attributeNodes = listenerElement.getAttributes(); + StringBuilder attributes = new StringBuilder(); + for (int i = 0; i < attributeNodes.getLength(); i++) { + if (i > 0) { + attributes.append(" "); + } + attributes.append(attributeNodes.item(i)); + } + parserContext.getReaderContext().error("Both 'ref' and " + + (StringUtils.hasText(id) ? "'id'" : "'class'") + + " specified; use 'class' with an optional 'id' or just 'ref' for <" + + listenerElement.getTagName() + "> element specified with attributes: " + attributes, element); + } + } + + @SuppressWarnings("unchecked") + private void handleStreamsElement(Element element, BeanDefinition bd, ParserContext parserContext) { + Element streamsElement = + DomUtils.getChildElementByTagName(element, "streams"); + if (streamsElement != null) { + List streamBeans = new ArrayList(); + List streamElements = + DomUtils.getChildElementsByTagName(streamsElement, "stream"); + if (streamElements != null) { + for (Element streamElement : streamElements) { + String streamRef = streamElement.getAttribute("ref"); + if (StringUtils.hasText(streamRef)) { + BeanReference bean = new RuntimeBeanReference(streamRef); + streamBeans.add(bean); + } + else { + parserContext.getReaderContext().error("ref not specified for <" + streamElement.getTagName() + "> element", element); + } + } + } + ManagedList arguments = new ManagedList(); + arguments.addAll(streamBeans); + bd.getPropertyValues().addPropertyValue("streams", arguments); + } + } + +} 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 4ef3daa01..6f062db4f 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 @@ -1,281 +1,237 @@ -/* - * 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.flow.support; - -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.JobExecutionException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.job.flow.Flow; -import org.springframework.batch.core.job.flow.FlowExecution; -import org.springframework.batch.core.job.flow.FlowExecutionException; -import org.springframework.batch.core.job.flow.FlowExecutor; -import org.springframework.beans.factory.InitializingBean; - - -/** - * A {@link Flow} that branches conditionally depending on the exit status of - * the last {@link State}. The input parameters are the state transitions (in no - * particular order). The start state name can be specified explicitly (and must - * exist in the set of transitions), or computed from the existing transitions, - * if unambiguous. - * - * @author Dave Syer - * - */ -public class SimpleFlow implements Flow, InitializingBean { - - private State startState; - - private Map> transitionMap = new HashMap>(); - - private Map stateMap = new HashMap(); - - private String startStateName; - - private Collection stateTransitions = new HashSet(); - - private final String name; - - /** - * Create a flow with the given name. - * - * @param name the name of the flow - */ - public SimpleFlow(String name) { - this.name = name; - } - - /** - * Get the name for this flow. - * - * @see Flow#getName() - */ - public String getName() { - return name; - } - - /** - * Public setter for the start state name. - * @param startStateName the name of the start state - */ - public void setStartStateName(String startStateName) { - this.startStateName = startStateName; - } - - /** - * Public setter for the stateTransitions. - * @param stateTransitions the stateTransitions to set - */ - public void setStateTransitions(Collection stateTransitions) { - - this.stateTransitions = stateTransitions; - } - - /** - * Locate start state and pre-populate data structures needed for execution. - * - * @see InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception { - initializeTransitions(); - } - - /** - * @see Flow#start(FlowExecutor) - */ - public FlowExecution start(FlowExecutor executor) throws FlowExecutionException { - if (startState == null) { - initializeTransitions(); - } - State state = startState; - String stateName = state.getName(); - return resume(stateName, executor); - } - - /** - * @see Flow#resume(String, FlowExecutor) - */ - public FlowExecution resume(String stateName, FlowExecutor executor) throws FlowExecutionException { - - String status = FlowExecution.UNKNOWN; - State state = stateMap.get(stateName); - - // Terminate if there are no more states - while (state != null) { - - stateName = state.getName(); - - try { - status = state.handle(executor); - } - catch (Exception e) { - executor.close(new FlowExecution(stateName, status)); - throw new FlowExecutionException(String.format("Ended flow=%s at state=%s with exception", name, - stateName), e); - } - - state = nextState(stateName, status); - - } - - FlowExecution result = new FlowExecution(stateName, status); - executor.close(result); - return result; - - } - - /** - * @return the next {@link Step} (or null if this is the end) - * @throws JobExecutionException - */ - private State nextState(String stateName, String status) throws FlowExecutionException { - - // Special status value indicating that a state wishes to pause - // execution - if (status.equals(FlowExecution.PAUSED)) { - return null; - } - - Set set = transitionMap.get(stateName); - - if (set == null) { - throw new FlowExecutionException(String.format("No transitions found in flow=%s for state=%s", getName(), - stateName)); - } - - String next = null; - for (StateTransition stateTransition : set) { - if (stateTransition.matches(status)) { - if (stateTransition.isEnd()) { - // End of job - return null; - } - next = stateTransition.getNext(); - break; - } - } - - if (next == null) { - throw new FlowExecutionException(String.format( - "Next state not found in flow=%s for state=%s with exit status=%s", getName(), stateName, status)); - } - - if (!stateMap.containsKey(next)) { - throw new FlowExecutionException(String.format("Next state not specified in flow=%s for next=%s", - getName(), next)); - } - - return stateMap.get(next); - - } - - /** - * Analyse the transitions provided and generate all the information needed - * to execute the flow. - */ - private void initializeTransitions() { - startState = null; - transitionMap.clear(); - stateMap.clear(); - boolean hasEndStep = false; - - for (StateTransition stateTransition : stateTransitions) { - State state = stateTransition.getState(); - stateMap.put(state.getName(), state); - } - - for (StateTransition stateTransition : stateTransitions) { - - State state = stateTransition.getState(); - - if (!stateTransition.isEnd()) { - - String next = stateTransition.getNext(); - - if (!stateMap.containsKey(next)) { - throw new IllegalArgumentException("Missing state for [" + stateTransition + "]"); - } - - } - else { - hasEndStep = true; - } - - String name = state.getName(); - - SortedSet set = transitionMap.get(name); - if (set == null) { - set = new TreeSet(); - transitionMap.put(name, set); - } - set.add(stateTransition); - - } - - if (!hasEndStep) { - throw new IllegalArgumentException( - "No end state was found. You must specify at least one transition with no next state."); - } - - if (startStateName != null) { - - startState = stateMap.get(startStateName); - if (startState == null) { - throw new IllegalArgumentException( - "Start state does not exist (if you specify a startStateName make sure " - + "a state with that name is in one of the transitions): [" + startStateName + "]"); - } - - } - else { - - // Try and locate a transition with no incoming links - - Set nextStateNames = new HashSet(); - - for (StateTransition stateTransition : stateTransitions) { - nextStateNames.add(stateTransition.getNext()); - } - - for (StateTransition stateTransition : stateTransitions) { - State state = stateTransition.getState(); - if (!nextStateNames.contains(state.getName())) { - if (startState != null && !startState.getName().equals(state.getName())) { - throw new IllegalArgumentException(String.format( - "Multiple possible start states found: [%s, %s]. " - + "Please specify one explicitly with the startStateName property.", startState - .getName(), state.getName())); - } - startState = state; - } - } - - if (startState == null) { - throw new IllegalArgumentException( - "No start state could be located (no transition without incoming links)"); - } - - } - } - -} +/* + * 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.flow.support; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.flow.Flow; +import org.springframework.batch.core.job.flow.FlowExecution; +import org.springframework.batch.core.job.flow.FlowExecutionException; +import org.springframework.batch.core.job.flow.FlowExecutor; +import org.springframework.beans.factory.InitializingBean; + +/** + * A {@link Flow} that branches conditionally depending on the exit status of + * the last {@link State}. The input parameters are the state transitions (in no + * particular order). The start state name can be specified explicitly (and must + * exist in the set of transitions), or computed from the existing transitions, + * if unambiguous. + * + * @author Dave Syer + * + */ +public class SimpleFlow implements Flow, InitializingBean { + + private State startState; + + private Map> transitionMap = new HashMap>(); + + private Map stateMap = new HashMap(); + + private List stateTransitions = new ArrayList(); + + private final String name; + + /** + * Create a flow with the given name. + * + * @param name the name of the flow + */ + public SimpleFlow(String name) { + this.name = name; + } + + /** + * Get the name for this flow. + * + * @see Flow#getName() + */ + public String getName() { + return name; + } + + /** + * Public setter for the stateTransitions. + * + * @param stateTransitions the stateTransitions to set + */ + public void setStateTransitions(List stateTransitions) { + + this.stateTransitions = stateTransitions; + } + + /** + * Locate start state and pre-populate data structures needed for execution. + * + * @see InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + initializeTransitions(); + } + + /** + * @see Flow#start(FlowExecutor) + */ + public FlowExecution start(FlowExecutor executor) throws FlowExecutionException { + if (startState == null) { + initializeTransitions(); + } + State state = startState; + String stateName = state.getName(); + return resume(stateName, executor); + } + + /** + * @see Flow#resume(String, FlowExecutor) + */ + public FlowExecution resume(String stateName, FlowExecutor executor) throws FlowExecutionException { + + String status = FlowExecution.UNKNOWN; + State state = stateMap.get(stateName); + + // Terminate if there are no more states + while (state != null) { + + stateName = state.getName(); + + try { + status = state.handle(executor); + } + catch (Exception e) { + executor.close(new FlowExecution(stateName, status)); + throw new FlowExecutionException(String.format("Ended flow=%s at state=%s with exception", name, + stateName), e); + } + + state = nextState(stateName, status); + + } + + FlowExecution result = new FlowExecution(stateName, status); + executor.close(result); + return result; + + } + + /** + * @return the next {@link Step} (or null if this is the end) + * @throws JobExecutionException + */ + private State nextState(String stateName, String status) throws FlowExecutionException { + + // Special status value indicating that a state wishes to pause + // execution + if (status.equals(FlowExecution.PAUSED)) { + return null; + } + + Set set = transitionMap.get(stateName); + + if (set == null) { + throw new FlowExecutionException(String.format("No transitions found in flow=%s for state=%s", getName(), + stateName)); + } + + String next = null; + for (StateTransition stateTransition : set) { + if (stateTransition.matches(status)) { + if (stateTransition.isEnd()) { + // End of job + return null; + } + next = stateTransition.getNext(); + break; + } + } + + if (next == null) { + throw new FlowExecutionException(String.format( + "Next state not found in flow=%s for state=%s with exit status=%s", getName(), stateName, status)); + } + + if (!stateMap.containsKey(next)) { + throw new FlowExecutionException(String.format("Next state not specified in flow=%s for next=%s", + getName(), next)); + } + + return stateMap.get(next); + + } + + /** + * Analyse the transitions provided and generate all the information needed + * to execute the flow. + */ + private void initializeTransitions() { + startState = null; + transitionMap.clear(); + stateMap.clear(); + boolean hasEndStep = false; + + if (stateTransitions.isEmpty()) { + throw new IllegalArgumentException("No start state was found. You must specify at least one step in a job."); + } + + for (StateTransition stateTransition : stateTransitions) { + State state = stateTransition.getState(); + stateMap.put(state.getName(), state); + } + + for (StateTransition stateTransition : stateTransitions) { + + State state = stateTransition.getState(); + + if (!stateTransition.isEnd()) { + + String next = stateTransition.getNext(); + + if (!stateMap.containsKey(next)) { + throw new IllegalArgumentException("Missing state for [" + stateTransition + "]"); + } + + } + else { + hasEndStep = true; + } + + String name = state.getName(); + + SortedSet set = transitionMap.get(name); + if (set == null) { + set = new TreeSet(); + transitionMap.put(name, set); + } + set.add(stateTransition); + + } + + if (!hasEndStep) { + throw new IllegalArgumentException( + "No end state was found. You must specify at least one transition with no next state."); + } + + startState = stateTransitions.get(0).getState(); + + } +} 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 53fd11c44..50d0f50ea 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 @@ -20,8 +20,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; +import java.util.List; import org.junit.Before; import org.junit.Test; @@ -72,7 +72,7 @@ public class FlowJobTests { @Test public void testTwoSteps() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "step2")); transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2")))); flow.setStateTransitions(transitions); @@ -86,7 +86,7 @@ public class FlowJobTests { @Test public void testFailedStep() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StepSupport("step1") { @Override public void execute(StepExecution stepExecution) throws JobInterruptedException, @@ -108,7 +108,7 @@ public class FlowJobTests { @Test public void testFailedStepRestarted() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StepSupport("step1") { @Override public void execute(StepExecution stepExecution) throws JobInterruptedException, @@ -150,7 +150,7 @@ public class FlowJobTests { @Test public void testStoppingStep() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StepSupport("step1") { @Override public void execute(StepExecution stepExecution) throws JobInterruptedException, @@ -175,7 +175,7 @@ public class FlowJobTests { @Test public void testEndStateStopped() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end")); transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.STOPPED, "end"), "step2")); transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2")))); @@ -194,7 +194,7 @@ public class FlowJobTests { public void testEndStateFailed() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end")); transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.FAILED, "end"), "step2")); transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2")))); @@ -209,7 +209,7 @@ public class FlowJobTests { @Test public void testEndStateStoppedWithRestart() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end")); transitions.add(StateTransition.createStateTransition(new EndState(BatchStatus.STOPPED, "end"), "step2")); transitions.add(StateTransition.createEndStateTransition(new StepState(new StubStep("step2")))); @@ -232,7 +232,7 @@ public class FlowJobTests { @Test public void testBranching() throws Exception { SimpleFlow flow = new SimpleFlow("job"); - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "step2")); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "COMPLETED", "step3")); @@ -251,8 +251,8 @@ public class FlowJobTests { public void testBasicFlow() throws Throwable { SimpleFlow flow = new SimpleFlow("job"); Step step = new StubStep("step"); - flow.setStateTransitions(Collections.singleton(StateTransition.createEndStateTransition(new StepState(step), - "*"))); + flow.setStateTransitions(Collections.singletonList(StateTransition.createEndStateTransition( + new StepState(step), "*"))); job.setFlow(flow); job.execute(jobExecution); if (!jobExecution.getAllFailureExceptions().isEmpty()) { @@ -272,7 +272,7 @@ public class FlowJobTests { } }; - Collection transitions = new ArrayList(); + List transitions = new ArrayList(); transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "*", "decision")); transitions.add(StateTransition.createStateTransition(new DecisionState(decider, "decision"), "*", "step2")); transitions.add(StateTransition 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 d28017a83..35cfd2595 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,243 +1,241 @@ -/* - * 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.flow.support; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.junit.Test; -import org.springframework.batch.core.job.flow.FlowExecution; -import org.springframework.batch.core.job.flow.FlowExecutionException; -import org.springframework.batch.core.job.flow.FlowExecutor; -import org.springframework.batch.core.job.flow.support.SimpleFlow; -import org.springframework.batch.core.job.flow.support.StateTransition; - -/** - * @author Dave Syer - * - */ -public class SimpleFlowTests { - - private SimpleFlow flow = new SimpleFlow("job"); - - private FlowExecutor executor = new JobFlowExecutorSupport(); - - @Test(expected = IllegalArgumentException.class) - public void testEmptySteps() throws Exception { - flow.setStateTransitions(Collections. emptySet()); - flow.afterPropertiesSet(); - } - - @Test(expected = IllegalArgumentException.class) - public void testNoNextStepSpecified() throws Exception { - flow.setStateTransitions(Collections.singleton(StateTransition.createStateTransition(new StateSupport( - "step"), "foo"))); - flow.afterPropertiesSet(); - } - - @Test(expected = IllegalArgumentException.class) - public void testNoStartStep() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StateSupport("step"), - FlowExecution.FAILED, "step"), StateTransition - .createEndStateTransition(new StateSupport("step")))); - flow.afterPropertiesSet(); - } - - @Test(expected = IllegalArgumentException.class) - public void testNoEndStep() throws Exception { - flow.setStateTransitions(Collections.singleton(StateTransition.createStateTransition(new StateSupport( - "step"), FlowExecution.FAILED, "step"))); - flow.setStartStateName("step"); - flow.afterPropertiesSet(); - } - - @Test(expected = IllegalArgumentException.class) - public void testMultipleStartSteps() throws Exception { - flow.setStateTransitions(collect(StateTransition.createEndStateTransition(new StubState("step1")), - StateTransition.createEndStateTransition(new StubState("step2")))); - flow.afterPropertiesSet(); - } - - @Test - public void testNoMatchForNextStep() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "FOO", "step2"), - StateTransition.createEndStateTransition(new StubState("step2")))); - flow.afterPropertiesSet(); - try { - flow.start(executor); - fail("Expected JobExecutionException"); - } - catch (FlowExecutionException e) { - // expected - String message = e.getMessage(); - assertTrue("Wrong message: " + message, message.toLowerCase().contains("next state not found")); - } - } - - @Test - public void testOneStep() throws Exception { - flow.setStateTransitions(Collections - .singleton(StateTransition.createEndStateTransition(new StubState("step1")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step1", execution.getName()); - } - - @Test - public void testOneStepWithListenerCallsClose() throws Exception { - flow.setStateTransitions(Collections - .singleton(StateTransition.createEndStateTransition(new StubState("step1")))); - flow.afterPropertiesSet(); - final List list = new ArrayList(); - executor = new JobFlowExecutorSupport() { - @Override - public void close(FlowExecution result) { - list.add(result); - } - }; - FlowExecution execution = flow.start(executor); - assertEquals(1, list.size()); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step1", execution.getName()); - } - - @Test - public void testExplicitStartStep() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step"), - FlowExecution.FAILED, "step"), StateTransition.createEndStateTransition(new StubState("step")))); - flow.setStartStateName("step"); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step", execution.getName()); - } - - @Test - public void testTwoSteps() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), - StateTransition.createEndStateTransition(new StubState("step2")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step2", execution.getName()); - } - - @Test - public void testResume() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), - StateTransition.createEndStateTransition(new StubState("step2")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.resume("step2", executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step2", execution.getName()); - } - - @Test - public void testFailedStep() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1") { - @Override - public String handle(FlowExecutor executor) { - return FlowExecution.FAILED; - } - }, "step2"), StateTransition.createEndStateTransition(new StubState("step2")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step2", execution.getName()); - } - - @Test - public void testBranching() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), - StateTransition.createStateTransition(new StubState("step1"), FlowExecution.COMPLETED, "step3"), - StateTransition.createEndStateTransition(new StubState("step2")), StateTransition - .createEndStateTransition(new StubState("step3")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step3", execution.getName()); - } - - @Test - public void testPause() throws Exception { - flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), - StateTransition.createStateTransition(new StubState("step2") { - private boolean paused = false; - - @Override - public String handle(FlowExecutor executor) throws Exception { - if (!paused) { - paused = true; - return FlowExecution.PAUSED; - } - paused = false; - return FlowExecution.COMPLETED; - } - - }, "step3"), StateTransition.createEndStateTransition(new StubState("step3")))); - flow.afterPropertiesSet(); - FlowExecution execution = flow.start(executor); - assertEquals(FlowExecution.PAUSED, execution.getStatus()); - assertEquals("step2", execution.getName()); - execution = flow.resume(execution.getName(), executor); - assertEquals(FlowExecution.COMPLETED, execution.getStatus()); - assertEquals("step3", execution.getName()); - } - - private Collection collect(StateTransition s1, StateTransition s2) { - Collection list = new ArrayList(); - list.add(s1); - list.add(s2); - return list; - } - - private Collection collect(StateTransition s1, StateTransition s2, - StateTransition s3) { - Collection list = collect(s1, s2); - list.add(s3); - return list; - } - - private Collection collect(StateTransition s1, StateTransition s2, - StateTransition s3, StateTransition s4) { - Collection list = collect(s1, s2, s3); - list.add(s4); - return list; - } - - /** - * @author Dave Syer - * - */ - private static class StubState extends StateSupport { - - /** - * @param string - */ - public StubState(String string) { - super(string); - } - - } - -} +/* + * 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.flow.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.springframework.batch.core.job.flow.FlowExecution; +import org.springframework.batch.core.job.flow.FlowExecutionException; +import org.springframework.batch.core.job.flow.FlowExecutor; + +/** + * @author Dave Syer + * + */ +public class SimpleFlowTests { + + private SimpleFlow flow = new SimpleFlow("job"); + + private FlowExecutor executor = new JobFlowExecutorSupport(); + + @Test(expected = IllegalArgumentException.class) + public void testEmptySteps() throws Exception { + flow.setStateTransitions(Collections. emptyList()); + flow.afterPropertiesSet(); + } + + @Test(expected = IllegalArgumentException.class) + public void testNoNextStepSpecified() throws Exception { + flow.setStateTransitions(Collections.singletonList(StateTransition.createStateTransition(new StateSupport( + "step"), "foo"))); + flow.afterPropertiesSet(); + } + + @Test + public void testStepLoop() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StateSupport("step"), + FlowExecution.FAILED, "step"), StateTransition.createEndStateTransition(new StateSupport("step")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step", execution.getName()); + } + + @Test(expected = IllegalArgumentException.class) + public void testNoEndStep() throws Exception { + flow.setStateTransitions(Collections.singletonList(StateTransition.createStateTransition(new StateSupport( + "step"), FlowExecution.FAILED, "step"))); + flow.afterPropertiesSet(); + } + + @Test + public void testUnconnectedSteps() throws Exception { + flow.setStateTransitions(collect(StateTransition.createEndStateTransition(new StubState("step1")), + StateTransition.createEndStateTransition(new StubState("step2")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step1", execution.getName()); + } + + @Test + public void testNoMatchForNextStep() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "FOO", "step2"), + StateTransition.createEndStateTransition(new StubState("step2")))); + flow.afterPropertiesSet(); + try { + flow.start(executor); + fail("Expected JobExecutionException"); + } + catch (FlowExecutionException e) { + // expected + String message = e.getMessage(); + assertTrue("Wrong message: " + message, message.toLowerCase().contains("next state not found")); + } + } + + @Test + public void testOneStep() throws Exception { + flow.setStateTransitions(Collections.singletonList(StateTransition.createEndStateTransition(new StubState( + "step1")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step1", execution.getName()); + } + + @Test + public void testOneStepWithListenerCallsClose() throws Exception { + flow.setStateTransitions(Collections.singletonList(StateTransition.createEndStateTransition(new StubState( + "step1")))); + flow.afterPropertiesSet(); + final List list = new ArrayList(); + executor = new JobFlowExecutorSupport() { + @Override + public void close(FlowExecution result) { + list.add(result); + } + }; + FlowExecution execution = flow.start(executor); + assertEquals(1, list.size()); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step1", execution.getName()); + } + + @Test + public void testExplicitStartStep() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step"), + FlowExecution.FAILED, "step"), StateTransition.createEndStateTransition(new StubState("step")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step", execution.getName()); + } + + @Test + public void testTwoSteps() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), + StateTransition.createEndStateTransition(new StubState("step2")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step2", execution.getName()); + } + + @Test + public void testResume() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), + StateTransition.createEndStateTransition(new StubState("step2")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.resume("step2", executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step2", execution.getName()); + } + + @Test + public void testFailedStep() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1") { + @Override + public String handle(FlowExecutor executor) { + return FlowExecution.FAILED; + } + }, "step2"), StateTransition.createEndStateTransition(new StubState("step2")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step2", execution.getName()); + } + + @Test + public void testBranching() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), + StateTransition.createStateTransition(new StubState("step1"), FlowExecution.COMPLETED, "step3"), + StateTransition.createEndStateTransition(new StubState("step2")), StateTransition + .createEndStateTransition(new StubState("step3")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step3", execution.getName()); + } + + @Test + public void testPause() throws Exception { + flow.setStateTransitions(collect(StateTransition.createStateTransition(new StubState("step1"), "step2"), + StateTransition.createStateTransition(new StubState("step2") { + private boolean paused = false; + + @Override + public String handle(FlowExecutor executor) throws Exception { + if (!paused) { + paused = true; + return FlowExecution.PAUSED; + } + paused = false; + return FlowExecution.COMPLETED; + } + + }, "step3"), StateTransition.createEndStateTransition(new StubState("step3")))); + flow.afterPropertiesSet(); + FlowExecution execution = flow.start(executor); + assertEquals(FlowExecution.PAUSED, execution.getStatus()); + assertEquals("step2", execution.getName()); + execution = flow.resume(execution.getName(), executor); + assertEquals(FlowExecution.COMPLETED, execution.getStatus()); + assertEquals("step3", execution.getName()); + } + + private List collect(StateTransition s1, StateTransition s2) { + List list = new ArrayList(); + list.add(s1); + list.add(s2); + return list; + } + + private List collect(StateTransition s1, StateTransition s2, StateTransition s3) { + List list = collect(s1, s2); + list.add(s3); + return list; + } + + private List collect(StateTransition s1, StateTransition s2, StateTransition s3, StateTransition s4) { + List list = collect(s1, s2, s3); + list.add(s4); + return list; + } + + /** + * @author Dave Syer + * + */ + private static class StubState extends StateSupport { + + /** + * @param string + */ + public StubState(String string) { + super(string); + } + + } + +}