diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index 8de417499..f14d8cc66 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -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 chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE); - @SuppressWarnings("unchecked") - List beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE); - @SuppressWarnings("unchecked") - List 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 chunkElements, List beanElements, - List 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 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 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) 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 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 listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener"); - if (listenerElements != null) { - for (Element listenerElement : listenerElements) { - listenerBeans.add(stepListenerParser.parse(listenerElement, parserContext)); - } - } - propertyValues.addPropertyValue("listeners", listenerBeans); - parserContext.popAndRegisterContainingComponent(); - } - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java index 468b781b4..bfcca5644 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java @@ -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); } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java new file mode 100644 index 000000000..04aaf1977 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java @@ -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 chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE); + @SuppressWarnings("unchecked") + List beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE); + @SuppressWarnings("unchecked") + List 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 chunkElements, List beanElements, + List 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 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 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) 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 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 listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener"); + if (listenerElements != null) { + for (Element listenerElement : listenerElements) { + listenerBeans.add(stepListenerParser.parse(listenerElement, parserContext)); + } + } + propertyValues.addPropertyValue("listeners", listenerBeans); + parserContext.popAndRegisterContainingComponent(); + } + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java index 17d304ae8..a6970d1e9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelFlowParser.java @@ -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); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelJobListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelJobListenerParser.java index 415cb043b..c74beaa9f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelJobListenerParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelJobListenerParser.java @@ -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); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepListenerParser.java index f713e71d8..070dddddc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepListenerParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepListenerParser.java @@ -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); } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd index 7af4ea61f..2150cac4e 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd @@ -29,7 +29,7 @@ - + @@ -126,7 +126,7 @@ - + @@ -385,7 +385,7 @@ - + @@ -554,12 +554,20 @@ the Tasklet interface. - - - + + + + + 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). + + + - + - - - - + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java new file mode 100644 index 000000000..a3a9ac7a3 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java @@ -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()); + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests-context.xml new file mode 100644 index 000000000..df1e8c39f --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests-context.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java index 743dec31c..c3de06d50 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java @@ -89,7 +89,7 @@ public abstract class AbstractMethodInvokingDelegator 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); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/HippyMethodInvoker.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/HippyMethodInvoker.java new file mode 100644 index 000000000..a822849ef --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/HippyMethodInvoker.java @@ -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; + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/adapter/HippyMethodInvokerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/adapter/HippyMethodInvokerTests.java new file mode 100644 index 000000000..f1e4e1465 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/adapter/HippyMethodInvokerTests.java @@ -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 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); + } + } + + } + +} diff --git a/spring-batch-samples/src/main/resources/jobs/jobStepSample.xml b/spring-batch-samples/src/main/resources/jobs/jobStepSample.xml index 7dc3156b4..7baa0f761 100644 --- a/spring-batch-samples/src/main/resources/jobs/jobStepSample.xml +++ b/spring-batch-samples/src/main/resources/jobs/jobStepSample.xml @@ -1,30 +1,24 @@ - - + + + - + - - - - - - - - - - + + +