RESOLVED - BATCH-1139:
*Add a "parent" attribute to <tasklet/>. This will allow tasklets to inherit properties from other tasklets that have been configured.
*A "merge" attribute for will have to be added to the following elements that occur with in <tasklet/>:
<skippable-exception-classes/>
<fatal-exception-classes/>
<retryable-exception-classes/>
<streams/>
<retry-listeners/>
*<tasklet/> should become a top-level element with an "abstract" attribute.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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<Object> retryListenerBeans = new ArrayList<Object>();
|
||||
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<Object> beans) {
|
||||
List<Element> listenerElements =
|
||||
DomUtils.getChildElementsByTagName(element, "listener");
|
||||
private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList beans) {
|
||||
List<Element> 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<BeanReference> streamBeans = new ArrayList<BeanReference>();
|
||||
List<Element> streamElements =
|
||||
DomUtils.getChildElementsByTagName(streamsElement, "stream");
|
||||
ManagedList streamBeans = new ManagedList();
|
||||
streamBeans.setMergeEnabled(Boolean.valueOf(streamsElement.getAttribute("merge")));
|
||||
List<Element> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,6 +88,23 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="tasklet">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A bean definition for a tasklet. Useful for creating a "base"
|
||||
tasklet from which others can extend.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="taskletType">
|
||||
<xsd:attribute name="id" type="xsd:ID" use="required"/>
|
||||
<xsd:attributeGroup ref="abstractAttribute"/>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="job-repository">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
@@ -143,8 +160,7 @@
|
||||
<xsd:documentation>
|
||||
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 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="merge" type="xsd:boolean" use="optional" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Should this list be merged with the corresponding list provided
|
||||
by the parent? If not, it will overwrite the parent list.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
@@ -322,6 +331,7 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element name="listener" type="listenerType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="streams" minOccurs="0" maxOccurs="1">
|
||||
@@ -350,6 +360,7 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="skippable-exception-classes" minOccurs="0" maxOccurs="1">
|
||||
@@ -359,31 +370,43 @@
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType>
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="retryable-exception-classes" minOccurs="0" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The newline-separated, list of exception classes that are skippable
|
||||
The newline-separated, list of exception classes that are retryable
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType>
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="fatal-exception-classes" minOccurs="0" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The newline-separated, list of exception classes that are skippable
|
||||
The newline-separated, list of exception classes that are fatal
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType>
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:all>
|
||||
<xsd:attribute name="commit-interval" type="xsd:string" use="optional">
|
||||
@@ -394,7 +417,7 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reader" type="xsd:string" use="required">
|
||||
<xsd:attribute name="reader" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The bean name of the item reader that is to be used for the process.
|
||||
@@ -416,7 +439,7 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="writer" type="xsd:string" use="required">
|
||||
<xsd:attribute name="writer" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The bean name of the item writer that is to be used for the process.
|
||||
@@ -494,6 +517,7 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="parentAttribute"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="nextType">
|
||||
@@ -597,14 +621,7 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element name="listener" type="stepListenerType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="merge" type="xsd:boolean" use="optional" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Should this list be merged with the corresponding list provided
|
||||
by the parent? If not, it will overwrite the parent list.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="mergeAttribute"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
@@ -779,7 +796,7 @@
|
||||
<xsd:attribute name="parent" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The name of the parent step from which the configuration should inherit.
|
||||
The name of the parent bean from which the configuration should inherit.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref"/>
|
||||
@@ -804,4 +821,15 @@
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:attributeGroup name="mergeAttribute">
|
||||
<xsd:attribute name="merge" type="xsd:boolean" default="false" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Should this list be merged with the corresponding list provided
|
||||
by the parent? If not, it will overwrite the parent list.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -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 <T> boolean open(RetryContext context, RetryCallback<T> callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
public <T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Class<?>> 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<Class<?>> 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<ItemStream> 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<RetryListener> 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<Class<?>> 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<Class<?>> 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<ItemStream> 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<RetryListener> 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<Class<?>> getExceptionClasses(String stepName, String type, ApplicationContext ctx) throws Exception {
|
||||
Map<String, Object> 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<Class<?>, ?> classified = (Map<Class<?>, ?>) ReflectionTestUtils.getField(classifier, "classified");
|
||||
|
||||
return classified.keySet();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<ItemStream> getStreams(String stepName, ApplicationContext ctx) throws Exception {
|
||||
Map<String, Object> 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<ItemStream>) ReflectionTestUtils.getField(compositeStream, "streams");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<RetryListener> getRetryListeners(String stepName, ApplicationContext ctx) throws Exception {
|
||||
Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<beans:import resource="common-context.xml" />
|
||||
|
||||
<job id="job">
|
||||
<step id="s1">
|
||||
<tasklet reader="reader" writer="writer"
|
||||
commit-interval="5" skip-limit="5"
|
||||
parent="baseTasklet">
|
||||
<skippable-exception-classes merge="true">
|
||||
java.lang.NullPointerException
|
||||
</skippable-exception-classes>
|
||||
<fatal-exception-classes merge="true">
|
||||
org.springframework.dao.CannotAcquireLockException
|
||||
</fatal-exception-classes>
|
||||
<streams merge="true">
|
||||
<stream ref="stream1"/>
|
||||
</streams>
|
||||
<retry-listeners merge="true">
|
||||
<listener class="org.springframework.batch.core.configuration.xml.DummyRetryListener"/>
|
||||
</retry-listeners>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<step id="s2">
|
||||
<tasklet reader="reader" writer="writer"
|
||||
commit-interval="5" skip-limit="5"
|
||||
parent="baseTasklet">
|
||||
<skippable-exception-classes>
|
||||
java.lang.NullPointerException
|
||||
</skippable-exception-classes>
|
||||
<fatal-exception-classes>
|
||||
org.springframework.dao.CannotAcquireLockException
|
||||
</fatal-exception-classes>
|
||||
<streams>
|
||||
<stream ref="stream1"/>
|
||||
</streams>
|
||||
<retry-listeners>
|
||||
<listener class="org.springframework.batch.core.configuration.xml.DummyRetryListener"/>
|
||||
</retry-listeners>
|
||||
</tasklet>
|
||||
</step>
|
||||
</job>
|
||||
|
||||
<tasklet id="baseTasklet" abstract="true">
|
||||
<skippable-exception-classes>
|
||||
java.lang.ArithmeticException
|
||||
</skippable-exception-classes>
|
||||
<fatal-exception-classes>
|
||||
org.springframework.dao.DeadlockLoserDataAccessException
|
||||
</fatal-exception-classes>
|
||||
<streams>
|
||||
<stream ref="stream2"/>
|
||||
</streams>
|
||||
<retry-listeners>
|
||||
<listener class="org.springframework.batch.retry.listener.RetryListenerSupport"/>
|
||||
</retry-listeners>
|
||||
</tasklet>
|
||||
|
||||
<beans:bean id="stream1" class="org.springframework.batch.item.support.CompositeItemStream"/>
|
||||
<beans:bean id="stream2" class="org.springframework.batch.core.configuration.xml.TestReader"/>
|
||||
|
||||
</beans:beans>
|
||||
Reference in New Issue
Block a user