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 60c3be298..f918f35bd 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 @@ -18,15 +18,19 @@ package org.springframework.batch.core.configuration.xml; import org.springframework.batch.core.listener.StepListenerMetaData; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; 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.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * Internal parser for the <step/> elements inside a job. A step element @@ -92,28 +96,58 @@ public abstract class AbstractStepParser { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); AbstractBeanDefinition bd = builder.getRawBeanDefinition(); - Element taskletElement = DomUtils.getChildElementByTagName(stepElement, TASKLET_ELE); - if (taskletElement != null) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - new TaskletParser().parseTasklet(stepElement, taskletElement, bd, parserContext, stepUnderspecified); - } + // look at all nested elements + NodeList children = stepElement.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node nd = children.item(i); - Element flowElement = DomUtils.getChildElementByTagName(stepElement, FLOW_ELE); - if (flowElement != null) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parseFlow(stepElement, flowElement, bd, parserContext, stepUnderspecified); - } + if (nd instanceof Element) { + Element nestedElement = (Element) nd; + String name = nestedElement.getLocalName(); - Element partitionElement = DomUtils.getChildElementByTagName(stepElement, PARTITION_ELE); - if (partitionElement != null) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parsePartition(stepElement, partitionElement, bd, parserContext, stepUnderspecified, jobFactoryRef); - } + if (TASKLET_ELE.equals(name)) { + boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); + new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified); + } + else if (FLOW_ELE.equals(name)) { + boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); + parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified); + } + else if (PARTITION_ELE.equals(name)) { + boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); + parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef); + } + else if (JOB_ELE.equals(name)) { + boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); + parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified); + } + else if ("description".equals(name)) { + bd.setDescription(nestedElement.getTextContent()); + } - Element jobElement = DomUtils.getChildElementByTagName(stepElement, JOB_ELE); - if (jobElement != null) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parseJob(stepElement, jobElement, bd, parserContext, stepUnderspecified); + // nested bean reference/declaration + else { + String ns = nestedElement.getNamespaceURI(); + Object value = null; + + // Spring NS + if ((ns == null && name.equals(BeanDefinitionParserDelegate.BEAN_ELEMENT)) + || ns.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) { + BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement(nestedElement); + value = parserContext.getDelegate().decorateBeanDefinitionIfRequired(nestedElement, holder); + } + // Custom NS + else { + value = parserContext.getDelegate().parseCustomElement(nestedElement); + } + + bd.setBeanClass(StepParserStepFactoryBean.class); + bd.setAttribute("isNamespaceStep", true); + + builder.addPropertyValue("tasklet", value); + } + } } String parentRef = stepElement.getAttribute(PARENT_ATTR); @@ -135,15 +169,8 @@ public abstract class AbstractStepParser { bd.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef); } - Element description = DomUtils.getChildElementByTagName(stepElement, "description"); - if (description != null) { - bd.setDescription(description.getTextContent()); - } - stepListenerParser.handleListenersElement(stepElement, bd, parserContext); - return bd; - } private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef ) { 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 17b703322..c921ea677 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 @@ -386,7 +386,11 @@ ref" is not required, and only needs to be specified explicitly - + + + + + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java index f3927611b..224d6dc24 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java @@ -15,8 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import java.lang.reflect.Field; import org.junit.Before; import org.junit.Test; @@ -29,11 +28,16 @@ import org.springframework.batch.core.Step; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.test.namespace.config.DummyNamespaceHandler; 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; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; /** @@ -52,6 +56,15 @@ public class TaskletParserBeanPropertiesTests { @Qualifier("job2") private FlowJob job2; + @Autowired + @Qualifier("job3") + private Job job3; + + + @Autowired + @Qualifier("job4") + private Job job4; + @Autowired @Qualifier("tasklet") private TestTasklet tasklet; @@ -86,4 +99,32 @@ public class TaskletParserBeanPropertiesTests { assertEquals("foo", tasklet.getName()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); } -} + + @Test + public void testTasklet3() throws Exception { + assertNotNull(job3); + JobExecution jobExecution = jobRepository.createJobExecution(job3.getName(), new JobParameters()); + job3.execute(jobExecution); + assertEquals(FlowJob.class, job3.getClass()); + Step step = ((FlowJob) job3).getStep("step3"); + Field field = ReflectionUtils.findField(TaskletStep.class, "tasklet"); + ReflectionUtils.makeAccessible(field); + TestTasklet tasklet = (TestTasklet) ReflectionUtils.getField(field, step); + assertEquals("foobar", tasklet.getName()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + public void testCustomNestedTasklet() throws Exception { + assertNotNull(job4); + JobExecution jobExecution = jobRepository.createJobExecution(job4.getName(), new JobParameters()); + job4.execute(jobExecution); + assertEquals(FlowJob.class, job4.getClass()); + Step step = ((FlowJob) job4).getStep("step4"); + Field field = ReflectionUtils.findField(TaskletStep.class, "tasklet"); + ReflectionUtils.makeAccessible(field); + TestTasklet tasklet = (TestTasklet) ReflectionUtils.getField(field, step); + assertEquals(DummyNamespaceHandler.LABEL, tasklet.getName()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/DummyNamespaceHandler.java b/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/DummyNamespaceHandler.java new file mode 100644 index 000000000..8a605d5b7 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/DummyNamespaceHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2006-2012 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.test.namespace.config; + +import java.util.Random; + +import org.springframework.batch.core.configuration.xml.TestTasklet; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.NamespaceHandler; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Empty implementation used for testing only. + * + * @author Costin Leau + */ +public class DummyNamespaceHandler implements NamespaceHandler { + + public static String LABEL = new Random().toString(); + + public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) { + return null; + } + + public void init() { + } + + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(TestTasklet.class); + builder.addPropertyValue("name", LABEL); + return builder.getBeanDefinition(); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/test.xsd b/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/test.xsd new file mode 100644 index 000000000..b3563bfe5 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/test/namespace/config/test.xsd @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/META-INF/spring.handlers b/spring-batch-core/src/test/resources/META-INF/spring.handlers new file mode 100644 index 000000000..a492dc396 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/batch/test=org.springframework.batch.test.namespace.config.DummyNamespaceHandler \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/META-INF/spring.schemas b/spring-batch-core/src/test/resources/META-INF/spring.schemas new file mode 100644 index 000000000..6e2680e47 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.springframework.org/schema/batch/test/test.xsd=org/springframework/batch/test/namespace/config/test.xsd \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests-context.xml index 5d0737c7a..a75ba8f6b 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests-context.xml @@ -2,8 +2,10 @@ + http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd + http://www.springframework.org/schema/batch/test http://www.springframework.org/schema/batch/test/test.xsd"> @@ -18,6 +20,18 @@ + + + + + + + + + + + +