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:
dhgarrette
2009-03-14 02:58:20 +00:00
parent cf062493e2
commit d2a5aeb616
8 changed files with 85 additions and 150 deletions

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -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);
}
}

View File

@@ -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">

View File

@@ -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"/>

View File

@@ -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"/>