diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParser.java index 0f92d4064..bb79010b6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParser.java @@ -16,21 +16,26 @@ package org.springframework.batch.core.configuration.xml; +import static org.springframework.util.StringUtils.hasText; + import java.util.ArrayList; import java.util.List; import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.listener.JobExecutionListenerAdapter; +import org.springframework.batch.core.listener.JobExecutionListenerFactoryBean; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; 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.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; 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.NamedNodeMap; /** * {@link BeanDefinitionParser} for {@link JobExecutionListener}s @@ -48,9 +53,39 @@ public class JobExecutionListenerParser { @SuppressWarnings("unchecked") List listenerElements = (List) DomUtils.getChildElementsByTagName(element, "listener"); for(Element listenerElement : listenerElements){ - BeanDefinitionBuilder listenerBuilder = BeanDefinitionBuilder.genericBeanDefinition(JobExecutionListenerAdapter.class); - String delegateName = listenerElement.getAttribute("ref"); - listenerBuilder.addConstructorArgReference(delegateName); + BeanDefinitionBuilder listenerBuilder = BeanDefinitionBuilder.genericBeanDefinition(JobExecutionListenerFactoryBean.class); + String id = listenerElement.getAttribute("id"); + String listenerRef = listenerElement.getAttribute("ref"); + String className = listenerElement.getAttribute("class"); + if ((StringUtils.hasText(id) || StringUtils.hasText(className)) + && StringUtils.hasText(listenerRef)) { + NamedNodeMap attributeNodes = listenerElement.getAttributes(); + StringBuilder attributes = new StringBuilder(); + for (int i = 0; i < attributeNodes.getLength(); i++) { + if (i > 0) { + attributes.append(" "); + } + attributes.append(attributeNodes.item(i)); + } + throw new BeanCreationException("Both 'ref' and 'class' specified; use 'class' with an optional 'id' or just 'ref' for <" + + listenerElement.getTagName() + "> element with attributes: " + attributes); + } + + if(hasText(listenerRef)){ + listenerBuilder.addPropertyReference("delegate", listenerRef); + } + else if(hasText(className)){ + RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); + if (!StringUtils.hasText(id)) { + id = parserContext.getReaderContext().generateBeanName(beanDef); + } + parserContext.getRegistry().registerBeanDefinition(id, beanDef); + listenerBuilder.addPropertyReference("delegate", id); + } + else { + throw new BeanCreationException("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element"); + } + String beforeMethod = listenerElement.getAttribute("before-method"); if(StringUtils.hasText(beforeMethod)){ @@ -62,7 +97,6 @@ public class JobExecutionListenerParser { listenerBuilder.addPropertyValue("afterMethod", afterMethod); } AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition(); - String id = listenerElement.getAttribute("id"); if (!StringUtils.hasText(id)) { id = parserContext.getReaderContext().generateBeanName(beanDef); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerFactoryBean.java new file mode 100644 index 000000000..783408b03 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerFactoryBean.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.listener; + +import org.springframework.batch.core.JobExecutionListener; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * {@link FactoryBean} implementation that accepts a delgate, checking whether + * or not it implements the {@link JobExecutionListener} interface, and if so, passes directly + * through. However, if it does not implement the interface, {@link JobExecutionListenerAdapter} + * is used to + * + * @author Lucas Ward + * + */ +public class JobExecutionListenerFactoryBean implements FactoryBean, InitializingBean{ + + private Object delegate; + private String beforeMethod; + private String afterMethod; + + public void setDelegate(Object delegate) { + this.delegate = delegate; + } + + public void setBeforeMethod(String beforeMethod) { + this.beforeMethod = beforeMethod; + } + + public void setAfterMethod(String afterMethod) { + this.afterMethod = afterMethod; + } + + public Object getObject() throws Exception { + if(delegate instanceof JobExecutionListener){ + return delegate; + } + else{ + JobExecutionListenerAdapter listenerAdapter = new JobExecutionListenerAdapter(delegate); + listenerAdapter.setBeforeMethod(beforeMethod); + listenerAdapter.setAfterMethod(afterMethod); + listenerAdapter.afterPropertiesSet(); + return listenerAdapter; + } + } + + @SuppressWarnings("unchecked") + public Class getObjectType() { + return JobExecutionListener.class; + } + + public boolean isSingleton() { + return false; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(delegate, "Delegate listener must not be null"); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListenerAdapterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListenerAdapterTests.java index 97367c7a8..c335ee999 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListenerAdapterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListenerAdapterTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; @@ -32,12 +33,15 @@ public class JobExecutionListenerAdapterTests { private TestClass testClass; private AnnotatedTestClass annotatedTestClass; + private InterfaceTestClass interfaceTestClass; private JobExecution jobExecution = new JobExecution(11L); + @Before public void setUp(){ testClass = new TestClass(); annotatedTestClass = new AnnotatedTestClass(); + interfaceTestClass = new InterfaceTestClass(); } @Test @@ -90,6 +94,16 @@ public class JobExecutionListenerAdapterTests { assertTrue(annotatedTestClass.afterJobCalled); } + @Test + public void testWithInterface() throws Exception{ + JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(interfaceTestClass); + adapter.afterPropertiesSet(); + adapter.beforeJob(jobExecution); + adapter.afterJob(jobExecution); + assertTrue(annotatedTestClass.beforeJobCalled); + assertTrue(annotatedTestClass.afterJobCalled); + } + private class TestClass{ boolean beforeJobCalled = false; @@ -116,4 +130,9 @@ public class JobExecutionListenerAdapterTests { super.afterJobCalled = true; } } + + private class InterfaceTestClass extends TestClass implements JobExecutionListener { + + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListnerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListnerFactoryBeanTests.java new file mode 100644 index 000000000..a16dd01c5 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListnerFactoryBeanTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.listener; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.annotation.AfterJob; +import org.springframework.batch.core.annotation.BeforeJob; + +/** + * @author Lucas Ward + * + */ +public class JobExecutionListnerFactoryBeanTests { + + JobExecutionListenerFactoryBean factoryBean; + + @Before + public void setUp(){ + factoryBean = new JobExecutionListenerFactoryBean(); + } + + @Test + public void testWithInterface() throws Exception{ + JobListenerWithInterface delegate = new JobListenerWithInterface(); + factoryBean.setDelegate(delegate); + assertEquals(delegate,factoryBean.getObject()); + } + + @Test + public void testWithAnnotations() throws Exception{ + AnnotatedTestClass delegate = new AnnotatedTestClass(); + factoryBean.setDelegate(delegate); + JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject(); + JobExecution jobExecution = new JobExecution(11L); + listener.beforeJob(jobExecution); + listener.afterJob(jobExecution); + assertTrue(delegate.beforeJobCalled); + assertTrue(delegate.afterJobCalled); + } + + private class JobListenerWithInterface implements JobExecutionListener{ + + public void afterJob(JobExecution jobExecution) { + } + + public void beforeJob(JobExecution jobExecution) { + } + + } + + private class AnnotatedTestClass { + + boolean beforeJobCalled = false; + boolean afterJobCalled = false; + + @BeforeJob + public void before(){ + beforeJobCalled = true; + } + + @AfterJob + public void after(){ + afterJobCalled = true; + } + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests-context.xml index 069f2470e..6b3238942 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests-context.xml @@ -12,6 +12,7 @@ +