diff --git a/spring-batch-core/.springBeans b/spring-batch-core/.springBeans
index 4f18db94b..3d031b577 100644
--- a/spring-batch-core/.springBeans
+++ b/spring-batch-core/.springBeans
@@ -28,7 +28,6 @@
src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserTests-context.xml
src/test/resources/org/springframework/batch/core/partition/launch-context.xml
src/test/resources/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests-context.xml
- src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests-context.xml
src/test/resources/org/springframework/batch/core/scope/util/PlaceholderTargetSourceTests-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml
@@ -43,6 +42,11 @@
src/test/resources/org/springframework/batch/core/configuration/xml/StepWithFaultTolerantProcessTaskJobParserTests-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests-context.xml
src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml
+ src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml
+ src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests-context.xml
+ src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeMultipleFinalJobParserTests-context.xml
+ src/test/resources/org/springframework/batch/core/configuration/xml/StepParserBadRetryListenerTests-context.xml
+ src/test/resources/org/springframework/batch/core/configuration/xml/StepParserBadStepListenerTests-context.xml
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 76b8ff86a..ef414d6b4 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,554 +1,558 @@
-/*
- * 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");
-
- @SuppressWarnings("unchecked")
- List processTaskElements = (List) DomUtils.getChildElementsByTagName(element, "tasklet");
- if (StringUtils.hasText(taskletRef)) {
- Object task = handleTaskletStep(element, taskletRef, parserContext);
- stateBuilder.addConstructorArgValue(stepRef);
- stateBuilder.addConstructorArgValue(task);
- }
- else if (processTaskElements.size() > 0) {
- Object task = handleChunkOrientedTaskletStep(element, processTaskElements.get(0), parserContext);
- stateBuilder.addConstructorArgValue(stepRef);
- stateBuilder.addConstructorArgValue(task);
- }
- else if (StringUtils.hasText(stepRef)) {
- RuntimeBeanReference stateDef = new RuntimeBeanReference(stepRef);
- stateBuilder.addConstructorArgValue(stateDef);
- }
- 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 = "end" + endCounter;
- endCounter++;
-
- endBuilder.addConstructorArgValue(endName);
- list.add(getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), onAttribute, nextOnEnd));
- nextAttribute = endName;
-
- }
- list.add(getStateTransitionReference(parserContext, stateDef, onAttribute, nextAttribute));
- }
-
- 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 RootBeanDefinition handleTaskletStep(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 RootBeanDefinition handleChunkOrientedTaskletStep(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 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);
-
- String id = parserContext.getReaderContext().generateBeanName(bd);
- parserContext.getRegistry().registerBeanDefinition(id, bd);
-
- 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, RootBeanDefinition 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, RootBeanDefinition 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, RootBeanDefinition 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, RootBeanDefinition 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");
+
+ @SuppressWarnings("unchecked")
+ List processTaskElements = (List) DomUtils.getChildElementsByTagName(element, "tasklet");
+ if (StringUtils.hasText(taskletRef)) {
+ Object task = handleTaskletStep(element, taskletRef, parserContext);
+ stateBuilder.addConstructorArgValue(stepRef);
+ stateBuilder.addConstructorArgValue(task);
+ }
+ else if (processTaskElements.size() > 0) {
+ Object task = handleChunkOrientedTaskletStep(element, processTaskElements.get(0), parserContext);
+ stateBuilder.addConstructorArgValue(stepRef);
+ stateBuilder.addConstructorArgValue(task);
+ }
+ else if (StringUtils.hasText(stepRef)) {
+ RuntimeBeanReference stateDef = new RuntimeBeanReference(stepRef);
+ stateBuilder.addConstructorArgValue(stateDef);
+ }
+ 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 RootBeanDefinition handleTaskletStep(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 RootBeanDefinition handleChunkOrientedTaskletStep(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 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);
+
+ String id = parserContext.getReaderContext().generateBeanName(bd);
+ parserContext.getRegistry().registerBeanDefinition(id, bd);
+
+ 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, RootBeanDefinition 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, RootBeanDefinition 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, RootBeanDefinition 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, RootBeanDefinition 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/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java
new file mode 100644
index 000000000..52416318a
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java
@@ -0,0 +1,62 @@
+/*
+ * 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.configuration.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Dan Garrette
+ * @since 2.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class EndTransitionJobParserTests {
+
+ @Autowired
+ private Job job;
+
+ @Autowired
+ private JobRepository jobRepository;
+
+ @Before
+ public void setUp() {
+ MapJobRepositoryFactoryBean.clear();
+ }
+
+ @Test
+ public void testNextAttributeFailedDefault() throws Exception {
+ assertNotNull(job);
+ JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
+ job.execute(jobExecution);
+ assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
+ assertEquals(1, jobExecution.getStepExecutions().size());
+ }
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java
new file mode 100644
index 000000000..2fc024030
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java
@@ -0,0 +1,18 @@
+package org.springframework.batch.core.configuration.xml;
+
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+
+/**
+ * @author Dan Garrette
+ * @since 2.0
+ */
+public class FailingTasklet implements Tasklet {
+
+ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
+ throw new RuntimeException();
+ }
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests.java
new file mode 100644
index 000000000..852b0a6b9
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests.java
@@ -0,0 +1,62 @@
+/*
+ * 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.configuration.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Dan Garrette
+ * @since 2.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class NextAttributeDefaultFailureJobParserTests {
+
+ @Autowired
+ private Job job;
+
+ @Autowired
+ private JobRepository jobRepository;
+
+ @Before
+ public void setUp() {
+ MapJobRepositoryFactoryBean.clear();
+ }
+
+ @Test
+ public void testNextAttributeFailedDefault() throws Exception {
+ assertNotNull(job);
+ JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
+ job.execute(jobExecution);
+ assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
+ assertEquals(1, jobExecution.getStepExecutions().size());
+ }
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java
index ae9d34adf..f8b5591ed 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java
@@ -1,81 +1,81 @@
-/*
- * 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.configuration.xml;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.batch.core.BatchStatus;
-import org.springframework.batch.core.Job;
-import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobParameters;
-import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
-import org.springframework.beans.factory.BeanDefinitionStoreException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.util.ClassUtils;
-
-/**
- * @author Dave Syer
- *
- */
-@ContextConfiguration
-@RunWith(SpringJUnit4ClassRunner.class)
-public class NextAttributeJobParserTests {
-
- @Autowired
- private Job job;
-
- @Autowired
- private JobRepository jobRepository;
-
- @Before
- public void setUp() {
- MapJobRepositoryFactoryBean.clear();
- }
-
- @Test
- public void testNextAttributeSunnyDay() throws Exception {
- assertNotNull(job);
- JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
- job.execute(jobExecution);
- assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
- assertEquals(2, jobExecution.getStepExecutions().size());
- }
-
- @Test
- public void testNextAttributeWithNestedElement() throws Exception {
- try {
- new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(),
- "NextAttributeFailureJobParserTests-context.xml"));
- fail("Expected BeanCreationException");
- }
- catch (BeanDefinitionStoreException e) {
- // expected
- String message = e.getMessage();
- assertTrue("Wrong message; " + message, message.contains("Duplicate transition pattern"));
- }
- }
-
-}
+/*
+ * 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.configuration.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.ClassUtils;
+
+/**
+ * @author Dave Syer
+ *
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class NextAttributeJobParserTests {
+
+ @Autowired
+ private Job job;
+
+ @Autowired
+ private JobRepository jobRepository;
+
+ @Before
+ public void setUp() {
+ MapJobRepositoryFactoryBean.clear();
+ }
+
+ @Test
+ public void testNextAttributeSunnyDay() throws Exception {
+ assertNotNull(job);
+ JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParameters());
+ job.execute(jobExecution);
+ assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
+ assertEquals(2, jobExecution.getStepExecutions().size());
+ }
+
+ @Test
+ public void testNextAttributeWithNestedElement() throws Exception {
+ try {
+ new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(),
+ "NextAttributeMultipleFinalJobParserTests-context.xml"));
+ fail("Expected BeanCreationException");
+ }
+ catch (BeanDefinitionStoreException e) {
+ // expected
+ String message = e.getMessage();
+ assertTrue("Wrong message; " + message, message.contains("Duplicate transition pattern"));
+ }
+ }
+
+}
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml
new file mode 100644
index 000000000..ff1b0aedc
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests-context.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests-context.xml
new file mode 100644
index 000000000..2a6dea23f
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeDefaultFailureJobParserTests-context.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeMultipleFinalJobParserTests-context.xml
similarity index 97%
rename from spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml
rename to spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeMultipleFinalJobParserTests-context.xml
index b0b42d768..6535c1107 100644
--- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeFailureJobParserTests-context.xml
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/NextAttributeMultipleFinalJobParserTests-context.xml
@@ -1,17 +1,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml
index 1f92263ac..f9e2d3a92 100644
--- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml
@@ -1,26 +1,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file