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