RESOLVED - issue BATCH-1478: Add auto registration of StepScope to all the top-level elements in XML

RESOLVED - issue BATCH-1479: HippyMethodInvoker
RESOLVED - issue BATCH-1480: Allow method adapter for Tasklet to be configured in namespace
This commit is contained in:
dsyer
2010-01-06 13:34:22 +00:00
parent 0d8cb85c10
commit eb7f543620
13 changed files with 582 additions and 258 deletions

View File

@@ -15,18 +15,13 @@
*/
package org.springframework.batch.core.configuration.xml;
import java.util.List;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -51,12 +46,6 @@ public abstract class AbstractStepParser {
private static final String PARENT_ATTR = "parent";
private static final String TASKLET_REF_ATTR = "ref";
private static final String BEAN_ELE = "bean";
private static final String REF_ELE = "ref";
private static final String REF_ATTR = "ref";
private static final String TASKLET_ELE = "tasklet";
@@ -83,20 +72,8 @@ public abstract class AbstractStepParser {
private static final String FLOW_ELE = "flow";
private static final String CHUNK_ELE = "chunk";
private static final String LISTENERS_ELE = "listeners";
private static final String MERGE_ATTR = "merge";
private static final String TX_ATTRIBUTES_ELE = "transaction-attributes";
private static final String JOB_REPO_ATTR = "job-repository";
private static final ChunkElementParser chunkElementParser = new ChunkElementParser();
private static final StepListenerParser stepListenerParser = new StepListenerParser();
/**
* @param stepElement The <step/> element
* @param parserContext
@@ -111,7 +88,7 @@ public abstract class AbstractStepParser {
Element taskletElement = DomUtils.getChildElementByTagName(stepElement, TASKLET_ELE);
if (taskletElement != null) {
boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
parseTasklet(stepElement, taskletElement, bd, parserContext, stepUnderspecified);
new TaskletParser().parseTasklet(stepElement, taskletElement, bd, parserContext, stepUnderspecified);
}
Element flowElement = DomUtils.getChildElementByTagName(stepElement, FLOW_ELE);
@@ -228,46 +205,6 @@ public abstract class AbstractStepParser {
}
private void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
String taskletRef = taskletElement.getAttribute(TASKLET_REF_ATTR);
@SuppressWarnings("unchecked")
List<Element> chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE);
@SuppressWarnings("unchecked")
List<Element> beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE);
@SuppressWarnings("unchecked")
List<Element> refElements = DomUtils.getChildElementsByTagName(taskletElement, REF_ELE);
validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef,
chunkElements, beanElements, refElements);
if (chunkElements.size() == 1) {
chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified);
}
else {
BeanMetadataElement bme = null;
if (StringUtils.hasText(taskletRef)) {
bme = new RuntimeBeanReference(taskletRef);
}
else if (beanElements.size() == 1) {
bme = parserContext.getDelegate().parseBeanDefinitionElement(beanElements.get(0));
}
else if (refElements.size() == 1) {
bme = (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0),
null);
}
if (bme != null) {
bd.getPropertyValues().addPropertyValue("tasklet", bme);
}
}
handleTaskletElement(taskletElement, bd, parserContext);
}
private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
@@ -288,163 +225,4 @@ public abstract class AbstractStepParser {
}
private void validateTaskletAttributesAndSubelements(Element taskletElement, ParserContext parserContext,
boolean stepUnderspecified, String taskletRef, List<Element> chunkElements, List<Element> beanElements,
List<Element> refElements) {
int total = (StringUtils.hasText(taskletRef) ? 1 : 0) + chunkElements.size() + beanElements.size()
+ refElements.size();
StringBuilder found = new StringBuilder();
if (total > 1) {
if (StringUtils.hasText(taskletRef)) {
found.append("'" + TASKLET_REF_ATTR + "' attribute, ");
}
if (chunkElements.size() == 1) {
found.append("<" + CHUNK_ELE + "/> element, ");
}
else if (chunkElements.size() > 1) {
found.append(chunkElements.size() + " <" + CHUNK_ELE + "/> elements, ");
}
if (beanElements.size() == 1) {
found.append("<" + BEAN_ELE + "/> element, ");
}
else if (beanElements.size() > 1) {
found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, ");
}
if (refElements.size() == 1) {
found.append("<" + REF_ELE + "/> element, ");
}
else if (refElements.size() > 1) {
found.append(refElements.size() + " <" + REF_ELE + "/> elements, ");
}
found.delete(found.length() - 2, found.length());
}
else {
found.append("None");
}
String error = null;
if (stepUnderspecified) {
if (total > 1) {
error = "may not have more than";
}
}
else if (total != 1) {
error = "must have exactly";
}
if (error != null) {
parserContext.getReaderContext().error(
"The <" + taskletElement.getTagName() + "/> element " + error + " one of: '" + TASKLET_REF_ATTR
+ "' attribute, <" + CHUNK_ELE + "/> element, <" + BEAN_ELE + "/> attribute, or <"
+ REF_ELE + "/> element. Found: " + found + ".", taskletElement);
}
}
private void handleTaskletElement(Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext) {
MutablePropertyValues propertyValues = bd.getPropertyValues();
handleTaskletAttributes(taskletElement, propertyValues);
handleTransactionAttributesElement(taskletElement, propertyValues);
handleListenersElement(taskletElement, propertyValues, parserContext);
handleExceptionElement(taskletElement, parserContext, propertyValues, "no-rollback-exception-classes",
"noRollbackExceptionClasses");
bd.setRole(BeanDefinition.ROLE_SUPPORT);
bd.setSource(parserContext.extractSource(taskletElement));
}
private void handleTransactionAttributesElement(Element stepElement, MutablePropertyValues propertyValues) {
@SuppressWarnings("unchecked")
List<Element> txAttrElements = DomUtils.getChildElementsByTagName(stepElement, TX_ATTRIBUTES_ELE);
if (txAttrElements.size() == 1) {
Element txAttrElement = txAttrElements.get(0);
String propagation = txAttrElement.getAttribute("propagation");
if (StringUtils.hasText(propagation)) {
propertyValues.addPropertyValue("propagation", propagation);
}
String isolation = txAttrElement.getAttribute("isolation");
if (StringUtils.hasText(isolation)) {
propertyValues.addPropertyValue("isolation", isolation);
}
String timeout = txAttrElement.getAttribute("timeout");
if (StringUtils.hasText(timeout)) {
propertyValues.addPropertyValue("transactionTimeout", timeout);
}
}
}
@SuppressWarnings("unchecked")
private void handleExceptionElement(Element element, ParserContext parserContext,
MutablePropertyValues propertyValues, String exceptionListName, String propertyName) {
List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName);
if (children.size() == 1) {
Element exceptionClassesElement = children.get(0);
ManagedList list = new ManagedList();
list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
&& Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR)));
addExceptionClasses("include", exceptionClassesElement, list, parserContext);
propertyValues.addPropertyValue(propertyName, list);
}
else if (children.size() > 1) {
parserContext.getReaderContext().error(
"The <" + exceptionListName + "/> element may not appear more than once in a single <"
+ element.getNodeName() + "/>.", element);
}
}
@SuppressWarnings("unchecked")
private void addExceptionClasses(String elementName, Element exceptionClassesElement, ManagedList list,
ParserContext parserContext) {
for (Element child : (List<Element>) DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
String className = child.getAttribute("class");
list.add(new TypedStringValue(className, Class.class));
}
}
private void handleTaskletAttributes(Element taskletElement, MutablePropertyValues propertyValues) {
String transactionManagerRef = taskletElement.getAttribute("transaction-manager");
if (StringUtils.hasText(transactionManagerRef)) {
propertyValues.addPropertyValue("transactionManager", new RuntimeBeanReference(transactionManagerRef));
}
String startLimit = taskletElement.getAttribute("start-limit");
if (StringUtils.hasText(startLimit)) {
propertyValues.addPropertyValue("startLimit", startLimit);
}
String allowStartIfComplete = taskletElement.getAttribute("allow-start-if-complete");
if (StringUtils.hasText(allowStartIfComplete)) {
propertyValues.addPropertyValue("allowStartIfComplete", allowStartIfComplete);
}
String taskExecutorBeanId = taskletElement.getAttribute(TASK_EXECUTOR_ATTR);
if (StringUtils.hasText(taskExecutorBeanId)) {
RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId);
propertyValues.addPropertyValue("taskExecutor", taskExecutorRef);
}
String throttleLimit = taskletElement.getAttribute("throttle-limit");
if (StringUtils.hasText(throttleLimit)) {
propertyValues.addPropertyValue("throttleLimit", throttleLimit);
}
}
@SuppressWarnings("unchecked")
private void handleListenersElement(Element stepElement, MutablePropertyValues propertyValues,
ParserContext parserContext) {
List<Element> listenersElements = DomUtils.getChildElementsByTagName(stepElement, LISTENERS_ELE);
if (listenersElements.size() == 1) {
Element listenersElement = listenersElements.get(0);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(),
parserContext.extractSource(stepElement));
parserContext.pushContainingComponent(compositeDef);
ManagedList listenerBeans = new ManagedList();
listenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR)
&& Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR)));
List<Element> listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener");
if (listenerElements != null) {
for (Element listenerElement : listenerElements) {
listenerBeans.add(stepListenerParser.parse(listenerElement, parserContext));
}
}
propertyValues.addPropertyValue("listeners", listenerBeans);
parserContext.popAndRegisterContainingComponent();
}
}
}

View File

@@ -19,13 +19,14 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the lt;job-repository/gt; element in the Batch namespace. Sets up and returns
* a JobRepositoryFactoryBean.
* Parser for the lt;job-repository/gt; element in the Batch namespace. Sets up
* and returns a JobRepositoryFactoryBean.
*
* @author Thomas Risberg
* @since 2.0
@@ -33,15 +34,19 @@ import org.w3c.dom.Element;
*/
public class JobRepositoryParser extends AbstractSingleBeanDefinitionParser {
protected String getBeanClassName(Element element) {
return "org.springframework.batch.core.repository.support.JobRepositoryFactoryBean";
}
protected String getBeanClassName(Element element) {
return "org.springframework.batch.core.repository.support.JobRepositoryFactoryBean";
}
/**
* Parse and create a bean definition for a
* {@link org.springframework.batch.core.repository.support.JobRepositoryFactoryBean}.
* Parse and create a bean definition for a
* {@link org.springframework.batch.core.repository.support.JobRepositoryFactoryBean}
* .
*/
protected void doParse(Element element, BeanDefinitionBuilder builder) {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, element);
String dataSource = element.getAttribute("data-source");
@@ -75,5 +80,4 @@ public class JobRepositoryParser extends AbstractSingleBeanDefinitionParser {
builder.setRole(BeanDefinition.ROLE_SUPPORT);
}
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright 2006-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.configuration.xml;
import java.util.List;
import org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parse a tasklet element for a step.
*
* @author Dave Syer
*
* @since 2.1
*
*/
public class TaskletParser {
private static final String TASKLET_REF_ATTR = "ref";
private static final String TASKLET_METHOD_ATTR = "method";
private static final String BEAN_ELE = "bean";
private static final String REF_ELE = "ref";
private static final String TASK_EXECUTOR_ATTR = "task-executor";
private static final String CHUNK_ELE = "chunk";
private static final String TX_ATTRIBUTES_ELE = "transaction-attributes";
private static final String LISTENERS_ELE = "listeners";
private static final String MERGE_ATTR = "merge";
private static final ChunkElementParser chunkElementParser = new ChunkElementParser();
private static final StepListenerParser stepListenerParser = new StepListenerParser();
public void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
String taskletRef = taskletElement.getAttribute(TASKLET_REF_ATTR);
String taskletMethod = taskletElement.getAttribute(TASKLET_METHOD_ATTR);
@SuppressWarnings("unchecked")
List<Element> chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE);
@SuppressWarnings("unchecked")
List<Element> beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE);
@SuppressWarnings("unchecked")
List<Element> refElements = DomUtils.getChildElementsByTagName(taskletElement, REF_ELE);
validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef,
chunkElements, beanElements, refElements);
if (chunkElements.size() == 1) {
chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified);
}
else {
BeanMetadataElement bme = null;
if (StringUtils.hasText(taskletRef)) {
bme = new RuntimeBeanReference(taskletRef);
}
else if (beanElements.size() == 1) {
bme = parserContext.getDelegate().parseBeanDefinitionElement(beanElements.get(0));
}
else if (refElements.size() == 1) {
bme = (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0),
null);
}
if (StringUtils.hasText(taskletMethod)) {
bme = getTaskletAdapter(bme, taskletMethod);
}
if (bme != null) {
bd.getPropertyValues().addPropertyValue("tasklet", bme);
}
}
handleTaskletElement(taskletElement, bd, parserContext);
}
/**
* Create a {@link MethodInvokingTaskletAdapter} for the POJO specified.
*/
private BeanMetadataElement getTaskletAdapter(BeanMetadataElement bme, String taskletMethod) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingTaskletAdapter.class);
builder.addPropertyValue("targetMethod", taskletMethod);
builder.addPropertyValue("targetObject", bme);
return builder.getBeanDefinition();
}
private void validateTaskletAttributesAndSubelements(Element taskletElement, ParserContext parserContext,
boolean stepUnderspecified, String taskletRef, List<Element> chunkElements, List<Element> beanElements,
List<Element> refElements) {
int total = (StringUtils.hasText(taskletRef) ? 1 : 0) + chunkElements.size() + beanElements.size()
+ refElements.size();
StringBuilder found = new StringBuilder();
if (total > 1) {
if (StringUtils.hasText(taskletRef)) {
found.append("'" + TASKLET_REF_ATTR + "' attribute, ");
}
if (chunkElements.size() == 1) {
found.append("<" + CHUNK_ELE + "/> element, ");
}
else if (chunkElements.size() > 1) {
found.append(chunkElements.size() + " <" + CHUNK_ELE + "/> elements, ");
}
if (beanElements.size() == 1) {
found.append("<" + BEAN_ELE + "/> element, ");
}
else if (beanElements.size() > 1) {
found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, ");
}
if (refElements.size() == 1) {
found.append("<" + REF_ELE + "/> element, ");
}
else if (refElements.size() > 1) {
found.append(refElements.size() + " <" + REF_ELE + "/> elements, ");
}
found.delete(found.length() - 2, found.length());
}
else {
found.append("None");
}
String error = null;
if (stepUnderspecified) {
if (total > 1) {
error = "may not have more than";
}
}
else if (total != 1) {
error = "must have exactly";
}
if (error != null) {
parserContext.getReaderContext().error(
"The <" + taskletElement.getTagName() + "/> element " + error + " one of: '" + TASKLET_REF_ATTR
+ "' attribute, <" + CHUNK_ELE + "/> element, <" + BEAN_ELE + "/> attribute, or <"
+ REF_ELE + "/> element. Found: " + found + ".", taskletElement);
}
}
private void handleTaskletElement(Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext) {
MutablePropertyValues propertyValues = bd.getPropertyValues();
handleTaskletAttributes(taskletElement, propertyValues);
handleTransactionAttributesElement(taskletElement, propertyValues);
handleListenersElement(taskletElement, propertyValues, parserContext);
handleExceptionElement(taskletElement, parserContext, propertyValues, "no-rollback-exception-classes",
"noRollbackExceptionClasses");
bd.setRole(BeanDefinition.ROLE_SUPPORT);
bd.setSource(parserContext.extractSource(taskletElement));
}
private void handleTransactionAttributesElement(Element stepElement, MutablePropertyValues propertyValues) {
@SuppressWarnings("unchecked")
List<Element> txAttrElements = DomUtils.getChildElementsByTagName(stepElement, TX_ATTRIBUTES_ELE);
if (txAttrElements.size() == 1) {
Element txAttrElement = txAttrElements.get(0);
String propagation = txAttrElement.getAttribute("propagation");
if (StringUtils.hasText(propagation)) {
propertyValues.addPropertyValue("propagation", propagation);
}
String isolation = txAttrElement.getAttribute("isolation");
if (StringUtils.hasText(isolation)) {
propertyValues.addPropertyValue("isolation", isolation);
}
String timeout = txAttrElement.getAttribute("timeout");
if (StringUtils.hasText(timeout)) {
propertyValues.addPropertyValue("transactionTimeout", timeout);
}
}
}
@SuppressWarnings("unchecked")
private void handleExceptionElement(Element element, ParserContext parserContext,
MutablePropertyValues propertyValues, String exceptionListName, String propertyName) {
List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName);
if (children.size() == 1) {
Element exceptionClassesElement = children.get(0);
ManagedList list = new ManagedList();
list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
&& Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR)));
addExceptionClasses("include", exceptionClassesElement, list, parserContext);
propertyValues.addPropertyValue(propertyName, list);
}
else if (children.size() > 1) {
parserContext.getReaderContext().error(
"The <" + exceptionListName + "/> element may not appear more than once in a single <"
+ element.getNodeName() + "/>.", element);
}
}
@SuppressWarnings("unchecked")
private void addExceptionClasses(String elementName, Element exceptionClassesElement, ManagedList list,
ParserContext parserContext) {
for (Element child : (List<Element>) DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
String className = child.getAttribute("class");
list.add(new TypedStringValue(className, Class.class));
}
}
private void handleTaskletAttributes(Element taskletElement, MutablePropertyValues propertyValues) {
String transactionManagerRef = taskletElement.getAttribute("transaction-manager");
if (StringUtils.hasText(transactionManagerRef)) {
propertyValues.addPropertyValue("transactionManager", new RuntimeBeanReference(transactionManagerRef));
}
String startLimit = taskletElement.getAttribute("start-limit");
if (StringUtils.hasText(startLimit)) {
propertyValues.addPropertyValue("startLimit", startLimit);
}
String allowStartIfComplete = taskletElement.getAttribute("allow-start-if-complete");
if (StringUtils.hasText(allowStartIfComplete)) {
propertyValues.addPropertyValue("allowStartIfComplete", allowStartIfComplete);
}
String taskExecutorBeanId = taskletElement.getAttribute(TASK_EXECUTOR_ATTR);
if (StringUtils.hasText(taskExecutorBeanId)) {
RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId);
propertyValues.addPropertyValue("taskExecutor", taskExecutorRef);
}
String throttleLimit = taskletElement.getAttribute("throttle-limit");
if (StringUtils.hasText(throttleLimit)) {
propertyValues.addPropertyValue("throttleLimit", throttleLimit);
}
}
@SuppressWarnings("unchecked")
private void handleListenersElement(Element stepElement, MutablePropertyValues propertyValues,
ParserContext parserContext) {
List<Element> listenersElements = DomUtils.getChildElementsByTagName(stepElement, LISTENERS_ELE);
if (listenersElements.size() == 1) {
Element listenersElement = listenersElements.get(0);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(),
parserContext.extractSource(stepElement));
parserContext.pushContainingComponent(compositeDef);
ManagedList listenerBeans = new ManagedList();
listenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR)
&& Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR)));
List<Element> listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener");
if (listenerElements != null) {
for (Element listenerElement : listenerElements) {
listenerBeans.add(stepListenerParser.parse(listenerElement, parserContext));
}
}
propertyValues.addPropertyValue("listeners", listenerBeans);
parserContext.popAndRegisterContainingComponent();
}
}
}

View File

@@ -36,6 +36,7 @@ public class TopLevelFlowParser extends AbstractFlowParser {
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, element);
String flowName = element.getAttribute(ID_ATTR);
builder.getRawBeanDefinition().setAttribute("flowName", flowName);
builder.addPropertyValue("name", flowName);

View File

@@ -18,6 +18,7 @@ public class TopLevelJobListenerParser extends AbstractSingleBeanDefinitionParse
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, element);
jobListenerParser.doParse(element, parserContext, builder);
}

View File

@@ -18,6 +18,7 @@ public class TopLevelStepListenerParser extends AbstractSingleBeanDefinitionPars
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, element);
stepListenerParser.doParse(element, parserContext, builder);
}

View File

@@ -29,7 +29,7 @@
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="description" minOccurs="0" />
<xsd:element name="description" type="description" minOccurs="0" />
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="flowGroup" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="listeners">
@@ -126,7 +126,7 @@
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="description" minOccurs="0" />
<xsd:element name="description" type="description" minOccurs="0" />
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="flowGroup" />
</xsd:choice>
@@ -385,7 +385,7 @@
<xsd:complexType name="stepType">
<xsd:sequence>
<xsd:element ref="description" minOccurs="0" />
<xsd:element name="description" type="description" minOccurs="0" />
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="tasklet" type="taskletType" />
<xsd:element name="partition" type="partitionType" />
@@ -554,12 +554,20 @@
the Tasklet interface.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.batch.core.step.tasklet.Tasklet" />
</tool:annotation>
<tool:annotation kind="ref" />
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
If the tasklet is specified as a bean definition, then a method can be specified and a POJO
will
be adapted to the Tasklet interface. The method suggested should have the same arguments
as Tasklet.execute (or a subset), and have a compatible return type (boolean, void or RepeatStatus).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="start-limit" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -1227,16 +1235,14 @@
</xsd:attribute>
</xsd:attributeGroup>
<xsd:element name="description">
<xsd:simpleType name="description">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains informative text describing the purpose of the enclosing element.
Used primarily for user documentation of XML bean definition documents.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType mixed="true">
<xsd:choice minOccurs="0" maxOccurs="unbounded" />
</xsd:complexType>
</xsd:element>
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.configuration.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dave Syer
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class TaskletParserAdapterTests {
@Autowired
@Qualifier("job1")
private Job job1;
@Autowired
@Qualifier("job2")
private Job job2;
@Autowired
private JobRepository jobRepository;
@Autowired
private MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean;
@Before
public void setUp() {
mapJobRepositoryFactoryBean.clear();
}
@Test
public void testTaskletRef() throws Exception {
assertNotNull(job1);
JobExecution jobExecution = jobRepository.createJobExecution(job1.getName(), new JobParameters());
job1.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
}
@Test
public void testTaskletInline() throws Exception {
assertNotNull(job2);
JobExecution jobExecution = jobRepository.createJobExecution(job2.getName(), new JobParameters());
job2.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
}
}

View File

@@ -0,0 +1,29 @@
<?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.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<job id="job1">
<step id="step1">
<tasklet ref="reader" method="read" />
</step>
</job>
<job id="job2">
<step id="ste2">
<tasklet method="read">
<beans:bean class="org.springframework.batch.core.configuration.xml.TestReader" />
</tasklet>
</step>
</job>
<beans:bean id="reader" class="org.springframework.batch.core.configuration.xml.TestReader" />
<beans:bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<beans:bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<beans:property name="transactionManager" ref="transactionManager" />
</beans:bean>
</beans:beans>

View File

@@ -89,7 +89,7 @@ public abstract class AbstractMethodInvokingDelegator<T> implements Initializing
* Create a new configured instance of {@link MethodInvoker}.
*/
private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
MethodInvoker invoker = new MethodInvoker();
HippyMethodInvoker invoker = new HippyMethodInvoker();
invoker.setTargetObject(targetObject);
invoker.setTargetMethod(targetMethod);
invoker.setArguments(arguments);

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2006-2010 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.item.adapter;
import java.lang.reflect.Method;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
import org.springframework.util.ReflectionUtils;
/**
* A {@link MethodInvoker} that is a bit relaxed about its arguments. You can
* give it arguments in the wrong order or you can give it too many arguments
* and it will try and find a method that matches a subset.
*
* @author Dave Syer
*
* @since 2.1
*/
public class HippyMethodInvoker extends MethodInvoker {
@Override
protected Method findMatchingMethod() {
String targetMethod = getTargetMethod();
Object[] arguments = getArguments();
Object[] transformedArguments = arguments;
int argCount = arguments.length;
Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass());
int minTypeDiffWeight = Integer.MAX_VALUE;
Method matchingMethod = null;
for (int i = 0; i < candidates.length; i++) {
Method candidate = candidates[i];
if (candidate.getName().equals(targetMethod)) {
Class<?>[] paramTypes = candidate.getParameterTypes();
transformedArguments = new Object[paramTypes.length];
for (int j = 0; j < arguments.length; j++) {
for (int k = 0; k < paramTypes.length; k++) {
if (ClassUtils.isAssignableValue(paramTypes[k], arguments[j])) {
transformedArguments[k] = arguments[j];
}
}
}
if (paramTypes.length <= argCount) {
int typeDiffWeight = getTypeDifferenceWeight(paramTypes, transformedArguments);
if (typeDiffWeight < minTypeDiffWeight) {
minTypeDiffWeight = typeDiffWeight;
matchingMethod = candidate;
}
}
}
}
setArguments(transformedArguments);
return matchingMethod;
}
}

View File

@@ -0,0 +1,77 @@
package org.springframework.batch.item.adapter;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class HippyMethodInvokerTests {
@Test
public void testVanillaMethodInvoker() throws Exception {
TestMethodAdapter adapter = new TestMethodAdapter();
adapter.setTargetMethod("handle");
adapter.setTargetObject(new PlainPojo());
assertEquals("2.0.foo", adapter.getMessage(2, "foo"));
}
@Test
public void testEmptyParameters() throws Exception {
TestMethodAdapter adapter = new TestMethodAdapter();
adapter.setTargetMethod("empty");
adapter.setTargetObject(new PlainPojo());
assertEquals(".", adapter.getMessage(2, "foo"));
}
@Test
public void testMissingArgument() throws Exception {
TestMethodAdapter adapter = new TestMethodAdapter();
adapter.setTargetMethod("missing");
adapter.setTargetObject(new PlainPojo());
assertEquals("foo.foo", adapter.getMessage(2, "foo"));
}
@Test
public void testWrongOrder() throws Exception {
TestMethodAdapter adapter = new TestMethodAdapter();
adapter.setTargetMethod("disorder");
adapter.setTargetObject(new PlainPojo());
assertEquals("2.0.foo", adapter.getMessage(2, "foo"));
}
public static class PlainPojo {
public String handle(double value, String input) {
return value+"."+input;
}
public String disorder(String input, double value) {
return value+"."+input;
}
public String missing(String input) {
return input+"."+input;
}
public String empty() {
return ".";
}
}
public static interface Service {
String getMessage(double value, String input);
}
public static class TestMethodAdapter extends AbstractMethodInvokingDelegator<String> implements Service {
public String getMessage(double value, String input) {
try {
return invokeDelegateMethodWithArguments(new Object[] {value, input});
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -1,30 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<beans xmlns="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.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<job id="jobStepJob" restartable="true" xmlns="http://www.springframework.org/schema/batch">
<step id="jobStepJob.step1" parent="jobStep"/>
<step id="jobStepJob.step1">
<job ref="tradeJob" job-parameters-extractor="jobParametersExtractor" />
</step>
</job>
<job id="tradeJob" restartable="true" xmlns="http://www.springframework.org/schema/batch">
<step id="step1">
<tasklet>
<chunk reader="itemReader" processor="processor" writer="tradeWriter" commit-interval="2"/>
<chunk reader="itemReader" processor="processor" writer="tradeWriter" commit-interval="2" />
</tasklet>
</step>
</job>
<bean id="jobStep" class="org.springframework.batch.core.step.job.JobStep">
<property name="jobRepository" ref="jobRepository"/>
<property name="jobLauncher" ref="jobLauncher"/>
<property name="job" ref="tradeJob"/>
<property name="jobParametersExtractor">
<bean class="org.springframework.batch.core.step.job.DefaultJobParametersExtractor">
<property name="keys" value="input.file"/>
</bean>
</property>
<bean id="jobParametersExtractor" class="org.springframework.batch.core.step.job.DefaultJobParametersExtractor">
<property name="keys" value="input.file" />
</bean>
<!-- INFRASTRUCTURE SETUP -->