diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index c2eb133e2..edc118171 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -77,7 +77,7 @@ public abstract class AbstractStepParser { protected AbstractBeanDefinition parseTaskletElement(Element stepElement, Element element, ParserContext parserContext, String jobRepositoryRef) { - AbstractBeanDefinition bd = taskletElementParser.parseTaskletElement(element, parserContext); + AbstractBeanDefinition bd = taskletElementParser.parse(element, parserContext); setUpBeanDefinition(stepElement, bd, parserContext, jobRepositoryRef); return bd; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java index 06cf321cc..de0b28ee8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceHandler.java @@ -32,6 +32,7 @@ public class CoreNamespaceHandler extends NamespaceHandlerSupport { public void init() { this.registerBeanDefinitionParser("job", new JobParser()); this.registerBeanDefinitionParser("step", new TopLevelStepParser()); + this.registerBeanDefinitionParser("tasklet", new TopLevelTaskletElementParser()); this.registerBeanDefinitionParser("job-repository", new JobRepositoryParser()); this.registerBeanDefinitionParser("step-listener", new TopLevelStepListenerParser()); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java index dea5d38f4..af2a7a34d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletElementParser.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean; @@ -36,7 +36,8 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; /** - * Internal parser for the <tasklet/> element either inside a job or as a standalone tasklet definition. + * Internal parser for the <tasklet/> element either inside a job or as a + * standalone tasklet definition. * * @author Thomas Risberg * @since 2.0 @@ -47,190 +48,198 @@ public class TaskletElementParser { * @param element * @param parserContext */ - protected AbstractBeanDefinition parseTaskletElement(Element element, ParserContext parserContext) { + protected AbstractBeanDefinition parse(Element element, ParserContext parserContext) { - boolean isFaultTolerant = false; + 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)) { + 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"); - - GenericBeanDefinition bd = new GenericBeanDefinition(); - if (isFaultTolerant) { - bd.setBeanClass(FaultTolerantStepFactoryBean.class); - } - else { - bd.setBeanClass(SimpleStepFactoryBean.class); - } - - MutablePropertyValues propertyValues = bd.getPropertyValues(); + } + } + checkExceptionElementForFaultToleranceNeeded(element, "skippable-exception-classes"); + checkExceptionElementForFaultToleranceNeeded(element, "retryable-exception-classes"); + checkExceptionElementForFaultToleranceNeeded(element, "fatal-exception-classes"); - String readerBeanId = element.getAttribute("reader"); - if (StringUtils.hasText(readerBeanId)) { - RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); - propertyValues.addPropertyValue("itemReader", readerRef); - } + GenericBeanDefinition bd = new GenericBeanDefinition(); + if (isFaultTolerant) { + bd.setBeanClass(FaultTolerantStepFactoryBean.class); + } + else { + bd.setBeanClass(SimpleStepFactoryBean.class); + } - String processorBeanId = element.getAttribute("processor"); - if (StringUtils.hasText(processorBeanId)) { - RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); - propertyValues.addPropertyValue("itemProcessor", processorRef); - } + boolean isAbstract = Boolean.valueOf(element.getAttribute("abstract")); + bd.setAbstract(isAbstract); - String writerBeanId = element.getAttribute("writer"); - if (StringUtils.hasText(writerBeanId)) { - RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); - propertyValues.addPropertyValue("itemWriter", writerRef); - } + String parentRef = element.getAttribute("parent"); + if (StringUtils.hasText(parentRef)) { + bd.setParentName(parentRef); + } - String taskExecutorBeanId = element.getAttribute("task-executor"); - if (StringUtils.hasText(taskExecutorBeanId)) { - RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); - propertyValues.addPropertyValue("taskExecutor", taskExecutorRef); - } + MutablePropertyValues propertyValues = bd.getPropertyValues(); - String commitInterval = element.getAttribute("commit-interval"); - if (StringUtils.hasText(commitInterval)) { - propertyValues.addPropertyValue("commitInterval", commitInterval); - } + String readerBeanId = element.getAttribute("reader"); + if (StringUtils.hasText(readerBeanId)) { + RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); + propertyValues.addPropertyValue("itemReader", readerRef); + } - String completionPolicyRef = element.getAttribute("chunk-completion-policy"); + String processorBeanId = element.getAttribute("processor"); + if (StringUtils.hasText(processorBeanId)) { + RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); + propertyValues.addPropertyValue("itemProcessor", processorRef); + } + + String writerBeanId = element.getAttribute("writer"); + if (StringUtils.hasText(writerBeanId)) { + RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); + propertyValues.addPropertyValue("itemWriter", writerRef); + } + + String taskExecutorBeanId = element.getAttribute("task-executor"); + if (StringUtils.hasText(taskExecutorBeanId)) { + RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); + propertyValues.addPropertyValue("taskExecutor", taskExecutorRef); + } + + String commitInterval = element.getAttribute("commit-interval"); + if (StringUtils.hasText(commitInterval)) { + propertyValues.addPropertyValue("commitInterval", commitInterval); + } + + String completionPolicyRef = element.getAttribute("chunk-completion-policy"); if (StringUtils.hasText(completionPolicyRef)) { RuntimeBeanReference completionPolicy = new RuntimeBeanReference(completionPolicyRef); propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy); } - if (propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) { + if (!isAbstract + && propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) { parserContext.getReaderContext().error( "The 'tasklet' element must contain either 'commit-interval' " + "or 'chunk-completion-policy', but not both.", element); } if (StringUtils.hasText(skipLimit)) { - propertyValues.addPropertyValue("skipLimit", skipLimit); - } + propertyValues.addPropertyValue("skipLimit", skipLimit); + } - if (StringUtils.hasText(retryLimit)) { - propertyValues.addPropertyValue("retryLimit", retryLimit); - } + if (StringUtils.hasText(retryLimit)) { + propertyValues.addPropertyValue("retryLimit", retryLimit); + } - if (StringUtils.hasText(cacheCapacity)) { - propertyValues.addPropertyValue("cacheCapacity", cacheCapacity); - } + if (StringUtils.hasText(cacheCapacity)) { + propertyValues.addPropertyValue("cacheCapacity", cacheCapacity); + } - String transactionAttribute = element.getAttribute("transaction-attribute"); - if (StringUtils.hasText(transactionAttribute)) { - propertyValues.addPropertyValue("transactionAttribute", transactionAttribute); - } + String transactionAttribute = element.getAttribute("transaction-attribute"); + if (StringUtils.hasText(transactionAttribute)) { + propertyValues.addPropertyValue("transactionAttribute", transactionAttribute); + } - if (StringUtils.hasText(isReaderTransactionalQueue)) { - if (isFaultTolerant) { - propertyValues.addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); - } - } + if (StringUtils.hasText(isReaderTransactionalQueue)) { + if (isFaultTolerant) { + propertyValues.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); + handleExceptionElement(element, parserContext, bd, "skippable-exception-classes", "skippableExceptionClasses", + isFaultTolerant, isAbstract); + + handleExceptionElement(element, parserContext, bd, "retryable-exception-classes", "retryableExceptionClasses", + isFaultTolerant, isAbstract); + + handleExceptionElement(element, parserContext, bd, "fatal-exception-classes", "fatalExceptionClasses", + isFaultTolerant, isAbstract); + + handleRetryListenersElement(element, bd, parserContext); + + handleStreamsElement(element, bd, parserContext); + + return bd; - handleRetryListenersElement(element, bd, parserContext); - - handleStreamsElement(element, bd, parserContext); - - return bd; - } private boolean checkIntValueForFaultToleranceNeeded(String stringValue) { if (StringUtils.hasText(stringValue)) { - int value = Integer.valueOf(stringValue); - if (value > 0) { + 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; - } + String exceptions = DomUtils.getChildElementValueByTagName(element, subElementName); + if (StringUtils.hasLength(exceptions)) { + return true; + } return false; } - private void handleExceptionElement(Element element, ParserContext parserContext, BeanDefinition bd, - String subElementName, String propertyName, boolean isFaultTolerant) { - String exceptions = - DomUtils.getChildElementValueByTagName(element, subElementName); - if (StringUtils.hasLength(exceptions)) { - if (isFaultTolerant) { - String[] exceptionArray = StringUtils.tokenizeToStringArray( - StringUtils.delete(exceptions, ","), "\n"); - if (exceptionArray.length > 0) { - bd.getPropertyValues().addPropertyValue(propertyName, exceptionArray); - } - } - else { - parserContext.getReaderContext().error(subElementName + " can only be specified for fault-tolerant " + - "configurations providing skip-limit, retry-limit or cache-capacity", element); - } - } + @SuppressWarnings("unchecked") + private void handleExceptionElement(Element element, ParserContext parserContext, BeanDefinition bd, + String subElementName, String propertyName, boolean isFaultTolerant, boolean isAbstract) { + Element child = DomUtils.getChildElementByTagName(element, subElementName); + if (child != null) { + String exceptions = DomUtils.getTextValue(child); + if (StringUtils.hasLength(exceptions) && (isFaultTolerant || isAbstract)) { + String[] exceptionArray = StringUtils.tokenizeToStringArray(StringUtils.delete(exceptions, ","), "\n"); + if (exceptionArray.length > 0) { + ManagedList managedList = new ManagedList(); + managedList.setMergeEnabled(Boolean.valueOf(child.getAttribute("merge"))); + managedList.addAll(Arrays.asList(exceptionArray)); + bd.getPropertyValues().addPropertyValue(propertyName, managedList); + } + } + 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 handleRetryListenersElement(Element element, BeanDefinition bd, ParserContext parserContext) { - Element listenersElement = - DomUtils.getChildElementByTagName(element, "retry-listeners"); + Element listenersElement = DomUtils.getChildElementByTagName(element, "retry-listeners"); if (listenersElement != null) { - CompositeComponentDefinition compositeDef = - new CompositeComponentDefinition(listenersElement.getTagName(), parserContext.extractSource(element)); + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(), + parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef); - List retryListenerBeans = new ArrayList(); - handleRetryListenerElements(parserContext, listenersElement, - retryListenerBeans); - ManagedList arguments = new ManagedList(); - arguments.addAll(retryListenerBeans); - bd.getPropertyValues().addPropertyValue("retryListeners", arguments); - parserContext.popAndRegisterContainingComponent(); + ManagedList retryListenerBeans = new ManagedList(); + retryListenerBeans.setMergeEnabled(Boolean.valueOf(listenersElement.getAttribute("merge"))); + handleRetryListenerElements(parserContext, listenersElement, retryListenerBeans); + bd.getPropertyValues().addPropertyValue("retryListeners", retryListenerBeans); + parserContext.popAndRegisterContainingComponent(); } } @SuppressWarnings("unchecked") - private void handleRetryListenerElements(ParserContext parserContext, - Element element, List beans) { - List listenerElements = - DomUtils.getChildElementsByTagName(element, "listener"); + private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList 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); + checkListenerElementAttributes(parserContext, element, listenerElement, id, listenerRef, className); if (StringUtils.hasText(listenerRef)) { - BeanReference bean = new RuntimeBeanReference(listenerRef); + BeanReference bean = new RuntimeBeanReference(listenerRef); beans.add(bean); } else if (StringUtils.hasText(className)) { @@ -241,17 +250,17 @@ public class TaskletElementParser { beans.add(beanDef); } else { - parserContext.getReaderContext().error("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", element); + parserContext.getReaderContext().error( + "Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element", + element); } } } } - - 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)) { + + 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++) { @@ -260,36 +269,35 @@ public class TaskletElementParser { } 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); + parserContext.getReaderContext().error( + "Both 'ref' and " + (StringUtils.hasText(id) ? "'id'" : "'class'") + + " specified; use 'class' with an optional 'id' or just 'ref' for <" + + listenerElement.getTagName() + "> element specified with attributes: " + attributes, + element); } } @SuppressWarnings("unchecked") private void handleStreamsElement(Element element, BeanDefinition bd, ParserContext parserContext) { - Element streamsElement = - DomUtils.getChildElementByTagName(element, "streams"); + Element streamsElement = DomUtils.getChildElementByTagName(element, "streams"); if (streamsElement != null) { - List streamBeans = new ArrayList(); - List streamElements = - DomUtils.getChildElementsByTagName(streamsElement, "stream"); + ManagedList streamBeans = new ManagedList(); + streamBeans.setMergeEnabled(Boolean.valueOf(streamsElement.getAttribute("merge"))); + 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); + BeanReference bean = new RuntimeBeanReference(streamRef); streamBeans.add(bean); } else { - parserContext.getReaderContext().error("ref not specified for <" + streamElement.getTagName() + "> element", element); + parserContext.getReaderContext().error( + "ref not specified for <" + streamElement.getTagName() + "> element", element); } } } - ManagedList arguments = new ManagedList(); - arguments.addAll(streamBeans); - bd.getPropertyValues().addPropertyValue("streams", arguments); + bd.getPropertyValues().addPropertyValue("streams", streamBeans); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelTaskletElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelTaskletElementParser.java new file mode 100644 index 000000000..0ac485bff --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelTaskletElementParser.java @@ -0,0 +1,41 @@ +/* + * Copyright 2006-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * Parser for the lt;tasklet/gt; top level element in the Batch namespace. Sets + * up and returns a bean definition. + * + * @author Dan Garrette + * @since 2.0 + */ +public class TopLevelTaskletElementParser extends AbstractBeanDefinitionParser { + + private TaskletElementParser taskletElementParser = new TaskletElementParser(); + + @Override + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + + return taskletElementParser.parse(element, parserContext); + + } + +} diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 3005d8de2..b6e0a4d5e 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -88,6 +88,23 @@ + + + + A bean definition for a tasklet. Useful for creating a "base" + tasklet from which others can extend. + + + + + + + + + + + + Defines a stage in job processing backed by a Step. The name attribute must be specified and - can - match the id of a bean definition + can match the id of a bean definition for a Step. If it does not, then you must provide a tasklet definition. The next attribute is a synonym for <next on="*" .../> @@ -275,14 +291,7 @@ - - - - Should this list be merged with the corresponding list provided - by the parent? If not, it will overwrite the parent list. - - - + @@ -322,6 +331,7 @@ + @@ -350,6 +360,7 @@ + @@ -359,31 +370,43 @@ ]]> - - - + + + + + + + - - - + + + + + + + - - - + + + + + + + @@ -394,7 +417,7 @@ ]]> - + - + + @@ -597,14 +621,7 @@ - - - - Should this list be merged with the corresponding list provided - by the parent? If not, it will overwrite the parent list. - - - + @@ -779,7 +796,7 @@ - The name of the parent step from which the configuration should inherit. + The name of the parent bean from which the configuration should inherit. @@ -804,4 +821,15 @@ + + + + + Should this list be merged with the corresponding list provided + by the parent? If not, it will overwrite the parent list. + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyRetryListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyRetryListener.java new file mode 100644 index 000000000..9e813e730 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyRetryListener.java @@ -0,0 +1,23 @@ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.batch.retry.RetryCallback; +import org.springframework.batch.retry.RetryContext; +import org.springframework.batch.retry.RetryListener; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class DummyRetryListener implements RetryListener { + + public boolean open(RetryContext context, RetryCallback callback) { + return false; + } + + public void close(RetryContext context, RetryCallback callback, Throwable throwable) { + } + + public void onError(RetryContext context, RetryCallback callback, Throwable throwable) { + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletElementParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletElementParserTests.java new file mode 100644 index 000000000..9b5d1cd5b --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletElementParserTests.java @@ -0,0 +1,233 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; +import org.junit.internal.runners.JUnit4ClassRunner; +import org.junit.runner.RunWith; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.support.CompositeItemStream; +import org.springframework.batch.retry.RetryListener; +import org.springframework.batch.retry.listener.RetryListenerSupport; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.dao.DeadlockLoserDataAccessException; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * @author Dan Garrette + * @since 2.0 + */ +@RunWith(JUnit4ClassRunner.class) +public class TaskletElementParserTests { + + @Test + public void testInheritSkippable() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection> skippable = getExceptionClasses("s1", "skippable", ctx); + assertEquals(2, skippable.size()); + boolean e = false; + boolean f = false; + for (Class cls : skippable) { + if (cls.equals(NullPointerException.class)) { + e = true; + } + else if (cls.equals(ArithmeticException.class)) { + f = true; + } + } + assertTrue(e); + assertTrue(f); + } + + @Test + public void testInheritFatal() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection> fatal = getExceptionClasses("s1", "fatal", ctx); + boolean a = false; + boolean b = false; + for (Class cls : fatal) { + if (cls.equals(CannotAcquireLockException.class)) { + a = true; + } + else if (cls.equals(DeadlockLoserDataAccessException.class)) { + b = true; + } + } + assertTrue(a); + assertTrue(b); + } + + @Test + public void testInheritStreams() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection streams = getStreams("s1", ctx); + assertEquals(2, streams.size()); + boolean c = false; + boolean d = false; + for (ItemStream o : streams) { + if (o instanceof CompositeItemStream) { + c = true; + } + else if (o instanceof TestReader) { + d = true; + } + } + assertTrue(c); + assertTrue(d); + } + + @Test + public void testInheritRetryListeners() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection retryListeners = getRetryListeners("s1", ctx); + assertEquals(2, retryListeners.size()); + boolean g = false; + boolean h = false; + for (RetryListener o : retryListeners) { + if (o instanceof RetryListenerSupport) { + g = true; + } + else if (o instanceof DummyRetryListener) { + h = true; + } + } + assertTrue(g); + assertTrue(h); + } + + @Test + public void testInheritSkippable_NoMerge() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection> skippable = getExceptionClasses("s2", "skippable", ctx); + assertEquals(1, skippable.size()); + boolean e = false; + for (Class cls : skippable) { + if (cls.equals(NullPointerException.class)) { + e = true; + } + } + assertTrue(e); + } + + @Test + public void testInheritFatal_NoMerge() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection> fatal = getExceptionClasses("s2", "fatal", ctx); + boolean a = false; + boolean b = false; + for (Class cls : fatal) { + if (cls.equals(CannotAcquireLockException.class)) { + a = true; + } + else if (cls.equals(DeadlockLoserDataAccessException.class)) { + b = true; + } + } + assertTrue(a); + assertTrue(!b); + } + + @Test + public void testInheritStreams_NoMerge() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection streams = getStreams("s2", ctx); + assertEquals(1, streams.size()); + boolean c = false; + for (ItemStream o : streams) { + if (o instanceof CompositeItemStream) { + c = true; + } + } + assertTrue(c); + } + + @Test + public void testInheritRetryListeners_NoMerge() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml"); + Collection retryListeners = getRetryListeners("s2", ctx); + assertEquals(1, retryListeners.size()); + boolean h = false; + for (RetryListener o : retryListeners) { + if (o instanceof DummyRetryListener) { + h = true; + } + } + assertTrue(h); + } + + @SuppressWarnings("unchecked") + private Set> getExceptionClasses(String stepName, String type, ApplicationContext ctx) throws Exception { + Map beans = ctx.getBeansOfType(Step.class); + assertTrue(beans.containsKey(stepName)); + Object step = ctx.getBean(stepName); + assertTrue(step instanceof TaskletStep); + + Object tasklet = ReflectionTestUtils.getField(step, "tasklet"); + Object chunkProvider = ReflectionTestUtils.getField(tasklet, "chunkProvider"); + Object skipPolicy = ReflectionTestUtils.getField(chunkProvider, "skipPolicy"); + Object classifier = ReflectionTestUtils.getField(skipPolicy, type + "ExceptionClassifier"); + Map, ?> classified = (Map, ?>) ReflectionTestUtils.getField(classifier, "classified"); + + return classified.keySet(); + } + + @SuppressWarnings("unchecked") + private Collection getStreams(String stepName, ApplicationContext ctx) throws Exception { + Map beans = ctx.getBeansOfType(Step.class); + assertTrue(beans.containsKey(stepName)); + Object step = ctx.getBean(stepName); + assertTrue(step instanceof TaskletStep); + + Object compositeStream = ReflectionTestUtils.getField(step, "stream"); + return (Collection) ReflectionTestUtils.getField(compositeStream, "streams"); + } + + @SuppressWarnings("unchecked") + private Collection getRetryListeners(String stepName, ApplicationContext ctx) throws Exception { + Map beans = ctx.getBeansOfType(Step.class); + assertTrue(beans.containsKey(stepName)); + Object step = ctx.getBean(stepName); + assertTrue(step instanceof TaskletStep); + + Object tasklet = ReflectionTestUtils.getField(step, "tasklet"); + Object chunkProcessor = ReflectionTestUtils.getField(tasklet, "chunkProcessor"); + Object retryTemplate = ReflectionTestUtils.getField(chunkProcessor, "batchRetryTemplate"); + Object regular = ReflectionTestUtils.getField(retryTemplate, "regular"); + RetryListener[] listeners = (RetryListener[]) ReflectionTestUtils.getField(regular, "listeners"); + return Arrays.asList(listeners); + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml new file mode 100644 index 000000000..c78f1d958 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletElementParentAttributeParserTests-context.xml @@ -0,0 +1,67 @@ + + + + + + + + + + java.lang.NullPointerException + + + org.springframework.dao.CannotAcquireLockException + + + + + + + + + + + + + + java.lang.NullPointerException + + + org.springframework.dao.CannotAcquireLockException + + + + + + + + + + + + + + java.lang.ArithmeticException + + + org.springframework.dao.DeadlockLoserDataAccessException + + + + + + + + + + + + + \ No newline at end of file