diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java index 9bf9c3ff2..3d8a85070 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java @@ -15,7 +15,6 @@ */ package org.springframework.batch.core.configuration.xml; -import java.util.ArrayList; import java.util.List; import org.springframework.batch.core.job.flow.FlowJob; @@ -37,29 +36,39 @@ import org.w3c.dom.Element; * */ public class JobParser extends AbstractSingleBeanDefinitionParser { - + @Override protected Class getBeanClass(Element element) { return FlowJob.class; } /** - * Create a bean definition for a {@link org.springframework.batch.core.job.flow.FlowJob}. The + * Create a bean definition for a + * {@link org.springframework.batch.core.job.flow.FlowJob}. The * jobRepository attribute is a reference to a - * {@link org.springframework.batch.core.repository.JobRepository} and defaults to "jobRepository". Nested step - * elements are delegated to an {@link InlineStepParser}. + * {@link org.springframework.batch.core.repository.JobRepository} and + * defaults to "jobRepository". Nested step elements are delegated to an + * {@link InlineStepParser}. * - * @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext, BeanDefinitionBuilder) + * @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext, + * BeanDefinitionBuilder) */ @SuppressWarnings("unchecked") @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - + CoreNamespaceUtils.checkForStepScope(parserContext, parserContext.extractSource(element)); String jobName = element.getAttribute("id"); builder.addConstructorArgValue(jobName); - + + builder.setAbstract(Boolean.valueOf(element.getAttribute("abstract"))); + + String parentRef = element.getAttribute("parent"); + if (StringUtils.hasText(parentRef)) { + builder.setParentName(parentRef); + } + String repositoryAttribute = element.getAttribute("job-repository"); if (!StringUtils.hasText(repositoryAttribute)) { repositoryAttribute = "jobRepository"; @@ -70,33 +79,33 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { if (StringUtils.hasText(restartableAttribute)) { builder.addPropertyValue("restartable", restartableAttribute); } - + String incrementer = (element.getAttribute("incrementer")); - if(StringUtils.hasText(incrementer)){ + if (StringUtils.hasText(incrementer)) { builder.addPropertyReference("jobParametersIncrementer", incrementer); } FlowParser flowParser = new FlowParser(jobName, repositoryAttribute); BeanDefinition flowDef = flowParser.parse(element, parserContext); builder.addPropertyValue("flow", flowDef); - + JobExecutionListenerParser listenerParser = new JobExecutionListenerParser(); - Element listenersElement = (Element)DomUtils.getChildElementByTagName(element, "listeners"); - if(listenersElement != null){ - CompositeComponentDefinition compositeDef = - new CompositeComponentDefinition(listenersElement.getTagName(), parserContext.extractSource(element)); + Element listenersElement = (Element) DomUtils.getChildElementByTagName(element, "listeners"); + if (listenersElement != null) { + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(), + parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef); - List listeners = new ArrayList(); - List listenerElements = (List) DomUtils.getChildElementsByTagName(listenersElement, "listener"); + ManagedList listeners = new ManagedList(); + listeners.setMergeEnabled(Boolean.valueOf(listenersElement.getAttribute("merge"))); + List listenerElements = (List) DomUtils.getChildElementsByTagName(listenersElement, + "listener"); for (Element listenerElement : listenerElements) { listeners.add(listenerParser.parse(listenerElement, parserContext)); } - ManagedList managedList = new ManagedList(); - managedList.addAll(listeners); - builder.addPropertyValue("jobExecutionListeners", managedList); + builder.addPropertyValue("jobExecutionListeners", listeners); parserContext.popAndRegisterContainingComponent(); } } - + } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 61ac86637..632c01123 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -46,6 +46,29 @@ ]]> + + + + The name of the parent job from which the configuration should inherit. + + + + + + + + + + Is this bean "abstract", that is, not meant to be instantiated itself + but rather just serving as parent for concrete child bean definitions? + The default is "false". Specify "true" to tell the bean factory to not + try to instantiate that particular bean in any case. + + Note: This attribute will not be inherited by child bean definitions. + Hence, it needs to be specified per abstract bean definition. + + + @@ -66,7 +89,7 @@ - + Is this bean "abstract", that is, not meant to be instantiated itself @@ -146,7 +169,7 @@ - + @@ -280,6 +303,14 @@ + + + + Should this list be merged with the corresponding list provided + by the parent? If not, it will overwrite the parent list. + + + @@ -597,7 +628,7 @@ - Should this list of listeners be merged with the list provided + Should this list be merged with the corresponding list provided by the parent? If not, it will overwrite the parent list. @@ -769,7 +800,7 @@ ]]> - + The name of the parent step from which the configuration should inherit. diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyAnnotationJobExecutionListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyAnnotationJobExecutionListener.java new file mode 100644 index 000000000..1acef2c0a --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyAnnotationJobExecutionListener.java @@ -0,0 +1,15 @@ +package org.springframework.batch.core.configuration.xml; + +import org.springframework.batch.core.annotation.BeforeJob; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class DummyAnnotationJobExecutionListener { + + @BeforeJob + public void execute() { + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java new file mode 100644 index 000000000..cefafcd9a --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2006-2009 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.assertTrue; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.aop.framework.Advised; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.AbstractJob; +import org.springframework.batch.core.listener.CompositeExecutionJobListener; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Dan Garrette + * @since 2.0 + */ +public class JobParserTests { + + @SuppressWarnings("unchecked") + @Test + public void testJobParserParentAttribute() throws Exception { + ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml"); + Map beans = ctx.getBeansOfType(Job.class); + + assertTrue(beans.containsKey("job1")); + Job job1 = (Job) ctx.getBean("job1"); + List job1Listeners = getListeners(job1); + assertEquals(2, job1Listeners.size()); + boolean a = false; + boolean b = false; + for (Object l : job1Listeners) { + if (l instanceof DummyAnnotationJobExecutionListener) { + a = true; + } + else if (l instanceof JobExecutionListenerSupport) { + b = true; + } + } + assertTrue(a); + assertTrue(b); + + assertTrue(beans.containsKey("job2")); + Job job2 = (Job) ctx.getBean("job2"); + List job2Listeners = getListeners(job2); + assertEquals(1, job2Listeners.size()); + boolean c = false; + for (Object l : job2Listeners) { + if (l instanceof JobExecutionListenerSupport) { + c = true; + } + } + assertTrue(c); + } + + @SuppressWarnings("unchecked") + private List getListeners(Job job) throws Exception { + assertTrue(job instanceof AbstractJob); + Field listenerField = AbstractJob.class.getDeclaredField("listener"); + listenerField.setAccessible(true); + Object compositeListener = listenerField.get(job); + + Field compositeField = CompositeExecutionJobListener.class.getDeclaredField("listeners"); + compositeField.setAccessible(true); + Object composite = compositeField.get(compositeListener); + + Class cls = Class.forName("org.springframework.batch.core.listener.OrderedComposite"); + Field listField = cls.getDeclaredField("list"); + listField.setAccessible(true); + List list = (List) listField.get(composite); + + List listeners = new ArrayList(); + for (Object listener : list) { + while (listener instanceof Advised) { + listener = ((Advised) listener).getTargetSource().getTarget(); + } + listeners.add(listener); + } + return listeners; + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml new file mode 100644 index 000000000..9a28af00a --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file