BATCH-1080:
BATCH-1145: Since both <step/> and <tasklet/> set directly to the *StepFactoryBean, their "parent" attributes were overwriting each other. As a solution, the top-level <tasklet/> element has been removed. To achieve the same result, the user can use an abstract top-level step with nothing declared but the <tasklet/>.
This commit is contained in:
@@ -50,15 +50,16 @@ public abstract class AbstractStepParser {
|
||||
private StepListenerParser stepListenerParser = new StepListenerParser();
|
||||
|
||||
/**
|
||||
* @param element
|
||||
* @param stepElement
|
||||
* @param parserContext
|
||||
* @return a BeanDefinition if possible
|
||||
*/
|
||||
protected AbstractBeanDefinition parseTasklet(Element element, ParserContext parserContext, String jobRepositoryRef) {
|
||||
protected AbstractBeanDefinition parseTasklet(Element stepElement, ParserContext parserContext,
|
||||
String jobRepositoryRef) {
|
||||
|
||||
String taskletRef = element.getAttribute("tasklet");
|
||||
String taskletRef = stepElement.getAttribute("tasklet");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Element> taskletElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "tasklet");
|
||||
List<Element> taskletElements = (List<Element>) DomUtils.getChildElementsByTagName(stepElement, "tasklet");
|
||||
boolean taskletElementExists = taskletElements.size() > 0;
|
||||
AbstractBeanDefinition bd = null;
|
||||
if (StringUtils.hasText(taskletRef)) {
|
||||
@@ -66,20 +67,35 @@ public abstract class AbstractStepParser {
|
||||
parserContext.getReaderContext().error(
|
||||
"The <" + taskletElements.get(0).getNodeName()
|
||||
+ "> element can't be combined with the 'tasklet=\"" + taskletRef
|
||||
+ "\"' attribute specification for <" + element.getNodeName() + ">", element);
|
||||
+ "\"' attribute specification for <" + stepElement.getNodeName() + ">", stepElement);
|
||||
}
|
||||
bd = parseTaskletRef(element, taskletRef, parserContext, jobRepositoryRef);
|
||||
setUpBeanDefinition(element, bd, parserContext, jobRepositoryRef);
|
||||
bd = parseTaskletRef(stepElement, taskletRef, parserContext, jobRepositoryRef);
|
||||
}
|
||||
else if (taskletElementExists) {
|
||||
Element taskElement = taskletElements.get(0);
|
||||
bd = taskletElementParser.parse(taskElement, parserContext);
|
||||
setUpBeanDefinition(element, bd, parserContext, jobRepositoryRef);
|
||||
bd = taskletElementParser.parse(taskElement, parserContext, stepUnderspecified(stepElement));
|
||||
}
|
||||
|
||||
if (bd != null) {
|
||||
setUpBeanDefinition(stepElement, bd, parserContext, jobRepositoryRef);
|
||||
}
|
||||
|
||||
return bd;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this step should be treated as incomplete? If it has a parent or
|
||||
* is abstract, then it may not have all properties.
|
||||
*
|
||||
* @param stepElement
|
||||
* @return TRUE if
|
||||
*/
|
||||
private boolean stepUnderspecified(Element stepElement) {
|
||||
return Boolean.valueOf(stepElement.getAttribute("abstract"))
|
||||
|| StringUtils.hasText(stepElement.getAttribute("parent"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stepElement
|
||||
* @param taskletRef
|
||||
@@ -105,6 +121,8 @@ public abstract class AbstractStepParser {
|
||||
String jobRepositoryRef) {
|
||||
checkStepAttributes(stepElement, bd);
|
||||
|
||||
bd.setAbstract(stepElement.hasAttribute("abstract") && Boolean.valueOf(stepElement.getAttribute("abstract")));
|
||||
|
||||
RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(jobRepositoryRef);
|
||||
bd.getPropertyValues().addPropertyValue("jobRepository", jobRepositoryBeanRef);
|
||||
|
||||
@@ -131,6 +149,7 @@ public abstract class AbstractStepParser {
|
||||
bd.setRole(BeanDefinition.ROLE_SUPPORT);
|
||||
|
||||
bd.setSource(parserContext.extractSource(stepElement));
|
||||
|
||||
}
|
||||
|
||||
private void checkStepAttributes(Element stepElement, AbstractBeanDefinition bd) {
|
||||
|
||||
@@ -32,7 +32,6 @@ 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("job-listener", new TopLevelJobListenerParser());
|
||||
this.registerBeanDefinitionParser("step-listener", new TopLevelStepListenerParser());
|
||||
|
||||
@@ -44,7 +44,6 @@ public class StandaloneStepParser extends AbstractStepParser {
|
||||
bd = new GenericBeanDefinition();
|
||||
setUpBeanDefinition(element, bd, parserContext, element.getAttribute("job-repository"));
|
||||
}
|
||||
bd.setAbstract(Boolean.valueOf(element.getAttribute("abstract")));
|
||||
|
||||
return bd;
|
||||
}
|
||||
|
||||
@@ -48,48 +48,27 @@ public class TaskletElementParser {
|
||||
* @param element
|
||||
* @param parserContext
|
||||
*/
|
||||
protected AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
|
||||
boolean isFaultTolerant = false;
|
||||
protected AbstractBeanDefinition parse(Element element, ParserContext parserContext, boolean underspecified) {
|
||||
|
||||
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");
|
||||
|
||||
boolean useFaultTolerant = underspecified
|
||||
|| (StringUtils.hasText(isReaderTransactionalQueue) && Boolean.valueOf(isReaderTransactionalQueue))
|
||||
|| isPositive(skipLimit) || isPositive(retryLimit) || isPositive(cacheCapacity)
|
||||
|| hasElement(element, "skippable-exception-classes")
|
||||
|| hasElement(element, "retryable-exception-classes") || hasElement(element, "fatal-exception-classes");
|
||||
|
||||
GenericBeanDefinition bd = new GenericBeanDefinition();
|
||||
if (isFaultTolerant) {
|
||||
if (useFaultTolerant) {
|
||||
bd.setBeanClass(FaultTolerantStepFactoryBean.class);
|
||||
}
|
||||
else {
|
||||
bd.setBeanClass(SimpleStepFactoryBean.class);
|
||||
}
|
||||
|
||||
boolean isAbstract = Boolean.valueOf(element.getAttribute("abstract"));
|
||||
bd.setAbstract(isAbstract);
|
||||
|
||||
String parentRef = element.getAttribute("parent");
|
||||
if (StringUtils.hasText(parentRef)) {
|
||||
bd.setParentName(parentRef);
|
||||
}
|
||||
|
||||
MutablePropertyValues propertyValues = bd.getPropertyValues();
|
||||
|
||||
String readerBeanId = element.getAttribute("reader");
|
||||
@@ -127,7 +106,7 @@ public class TaskletElementParser {
|
||||
propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy);
|
||||
}
|
||||
|
||||
if (!isAbstract
|
||||
if (!underspecified
|
||||
&& propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) {
|
||||
parserContext.getReaderContext().error(
|
||||
"The 'tasklet' element must contain either 'commit-interval' "
|
||||
@@ -147,19 +126,19 @@ public class TaskletElementParser {
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(isReaderTransactionalQueue)) {
|
||||
if (isFaultTolerant) {
|
||||
if (useFaultTolerant) {
|
||||
propertyValues.addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue);
|
||||
}
|
||||
}
|
||||
|
||||
handleExceptionElement(element, parserContext, bd, "skippable-exception-classes", "skippableExceptionClasses",
|
||||
isFaultTolerant, isAbstract);
|
||||
useFaultTolerant, underspecified);
|
||||
|
||||
handleExceptionElement(element, parserContext, bd, "retryable-exception-classes", "retryableExceptionClasses",
|
||||
isFaultTolerant, isAbstract);
|
||||
useFaultTolerant, underspecified);
|
||||
|
||||
handleExceptionElement(element, parserContext, bd, "fatal-exception-classes", "fatalExceptionClasses",
|
||||
isFaultTolerant, isAbstract);
|
||||
useFaultTolerant, underspecified);
|
||||
|
||||
handleRetryListenersElement(element, bd, parserContext);
|
||||
|
||||
@@ -169,22 +148,17 @@ public class TaskletElementParser {
|
||||
|
||||
}
|
||||
|
||||
private boolean checkIntValueForFaultToleranceNeeded(String stringValue) {
|
||||
private boolean isPositive(String stringValue) {
|
||||
if (StringUtils.hasText(stringValue)) {
|
||||
int value = Integer.valueOf(stringValue);
|
||||
if (value > 0) {
|
||||
if (Integer.valueOf(stringValue) > 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 boolean hasElement(Element element, String subElementName) {
|
||||
return StringUtils.hasLength(DomUtils.getChildElementValueByTagName(element, subElementName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
|
||||
if (!Boolean.valueOf(element.getAttribute("abstract"))) {
|
||||
parserContext.getReaderContext().error(
|
||||
"The <tasklet/> element, when not contained with a <step/>, must be abstract", element);
|
||||
}
|
||||
|
||||
return taskletElementParser.parse(element, parserContext);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -111,24 +111,6 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="tasklet">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A bean definition for a tasklet that can be injected into a Step.
|
||||
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[
|
||||
@@ -508,7 +490,6 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="parentAttribute" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="transaction-attributesType">
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
<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">
|
||||
<step id="s1" parent="baseStep">
|
||||
<tasklet reader="reader" writer="writer" commit-interval="5" skip-limit="5">
|
||||
<skippable-exception-classes merge="true">
|
||||
java.lang.NullPointerException
|
||||
</skippable-exception-classes>
|
||||
@@ -26,10 +24,8 @@
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<step id="s2">
|
||||
<tasklet reader="reader" writer="writer"
|
||||
commit-interval="5" skip-limit="5"
|
||||
parent="baseTasklet">
|
||||
<step id="s2" parent="baseStep">
|
||||
<tasklet reader="reader" writer="writer" commit-interval="5" skip-limit="5">
|
||||
<skippable-exception-classes>
|
||||
java.lang.NullPointerException
|
||||
</skippable-exception-classes>
|
||||
@@ -46,20 +42,22 @@
|
||||
</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>
|
||||
<step id="baseStep" abstract="true">
|
||||
<tasklet>
|
||||
<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>
|
||||
</step>
|
||||
|
||||
<beans:bean id="stream1" class="org.springframework.batch.item.support.CompositeItemStream"/>
|
||||
<beans:bean id="stream2" class="org.springframework.batch.core.configuration.xml.TestReader"/>
|
||||
|
||||
@@ -35,23 +35,34 @@
|
||||
|
||||
<step id="errorPrint2" tasklet="errorLogTasklet"/>
|
||||
</job>
|
||||
|
||||
<step id="secondPass" parent="baseStep">
|
||||
<tasklet reader="tradeSqlItemReader" processor="tradeProcessor" writer="itemTrackingWriter"
|
||||
commit-interval="2" skip-limit="10">
|
||||
<skippable-exception-classes>
|
||||
org.springframework.batch.item.validator.ValidationException
|
||||
java.lang.RuntimeException
|
||||
|
||||
<step id="secondPass" parent="t2">
|
||||
<tasklet writer="itemTrackingWriter">
|
||||
<skippable-exception-classes merge="true">
|
||||
java.lang.RuntimeException
|
||||
</skippable-exception-classes>
|
||||
</tasklet>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
|
||||
<step id="t2" parent="t3" abstract="true"/>
|
||||
|
||||
<step id="t3" parent="baseStep" abstract="true">
|
||||
<tasklet reader="tradeSqlItemReader" processor="tradeProcessor" writer="dummyWriter"
|
||||
commit-interval="2" skip-limit="10">
|
||||
<skippable-exception-classes>
|
||||
org.springframework.batch.item.validator.ValidationException
|
||||
</skippable-exception-classes>
|
||||
</tasklet>
|
||||
</step>
|
||||
|
||||
<step id="baseStep" abstract="true">
|
||||
<listeners>
|
||||
<listener class="org.springframework.batch.sample.common.SkipCheckingListener"/>
|
||||
<listener ref="promotionListener"/>
|
||||
</listeners>
|
||||
</step>
|
||||
|
||||
<beans:bean id="dummyWriter" class="org.springframework.batch.sample.support.DummyItemWriter"/>
|
||||
|
||||
<beans:bean id="promotionListener" class="org.springframework.batch.core.listener.ExecutionContextPromotionListener">
|
||||
<beans:property name="keys" value="stepName"/>
|
||||
|
||||
Reference in New Issue
Block a user