diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java
index ebc071adf..ba531acce 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java
@@ -12,6 +12,7 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.Step;
+import org.springframework.beans.factory.annotation.Qualifier;
/**
* Marks a method to be called before a {@link Job} is executed, which comes
@@ -24,6 +25,7 @@ import org.springframework.batch.core.Step;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
+@Qualifier("JobExecutionListener")
public @interface BeforeJob {
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java
new file mode 100644
index 000000000..37dd88a00
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java
@@ -0,0 +1,104 @@
+/*
+ * 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.configuration.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * MethodResolver implementation that finds a single Method on the
+ * given Class that contains the specified annotation type.
+ *
+ * @author Mark Fisher
+ */
+public class AnnotationMethodResolver implements MethodResolver {
+
+ private Class extends Annotation> annotationType;
+
+
+ /**
+ * Create a MethodResolver for the specified Method-level annotation type
+ */
+ public AnnotationMethodResolver(Class extends Annotation> annotationType) {
+ Assert.notNull(annotationType, "annotationType must not be null");
+ Assert.isTrue(ObjectUtils.containsElement(
+ annotationType.getAnnotation(Target.class).value(), ElementType.METHOD),
+ "Annotation [" + annotationType + "] is not a Method-level annotation.");
+ this.annotationType = annotationType;
+ }
+
+
+ /**
+ * Find a single Method on the Class of the given candidate object
+ * that contains the annotation type for which this resolver is searching.
+ *
+ * @param candidate the instance whose Class will be checked for the
+ * annotation
+ *
+ * @return a single matching Method instance or null if the
+ * candidate's Class contains no Methods with the specified annotation
+ *
+ * @throws IllegalArgumentException if more than one Method has the
+ * specified annotation
+ */
+ public Method findMethod(Object candidate) {
+ Assert.notNull(candidate, "candidate object must not be null");
+ Class> targetClass = AopUtils.getTargetClass(candidate);
+ if (targetClass == null) {
+ targetClass = candidate.getClass();
+ }
+ return this.findMethod(targetClass);
+ }
+
+ /**
+ * Find a single Method on the given Class that contains the
+ * annotation type for which this resolver is searching.
+ *
+ * @param clazz the Class instance to check for the annotation
+ *
+ * @return a single matching Method instance or null if the
+ * Class contains no Methods with the specified annotation
+ *
+ * @throws IllegalArgumentException if more than one Method has the
+ * specified annotation
+ */
+ public Method findMethod(final Class> clazz) {
+ Assert.notNull(clazz, "class must not be null");
+ final AtomicReference annotatedMethod = new AtomicReference();
+ ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
+ if (annotation != null) {
+ Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
+ + clazz + "] with the annotation type [" + annotationType + "]");
+ annotatedMethod.set(method);
+ }
+ }
+ });
+ return annotatedMethod.get();
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java
new file mode 100644
index 000000000..0b797095a
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java
@@ -0,0 +1,29 @@
+/*
+ * 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.configuration.util;
+
+/**
+ * A strategy interface for invoking a method.
+ * Typically used by adapters.
+ *
+ * @author Mark Fisher
+ */
+public interface MethodInvoker {
+
+ Object invokeMethod(Object ... args);
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java
new file mode 100644
index 000000000..cf63278ff
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java
@@ -0,0 +1,57 @@
+/*
+ * 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.configuration.util;
+
+import java.lang.reflect.Method;
+
+/**
+ * Strategy interface for detecting a single Method on a Class.
+ *
+ * @author Mark Fisher
+ */
+public interface MethodResolver {
+
+ /**
+ * Find a single Method on the provided Object that matches this resolver's
+ * criteria.
+ *
+ * @param candidate the candidate Object whose Class should be searched for
+ * a Method
+ *
+ * @return a single Method or null if no Method matching this
+ * resolver's criteria can be found.
+ *
+ * @throws IllegalArgumentException if more than one Method defined on the
+ * given candidate's Class matches this resolver's criteria
+ */
+ Method findMethod(Object candidate) throws IllegalArgumentException;
+
+ /**
+ * Find a single Method on the given Class that matches this
+ * resolver's criteria.
+ *
+ * @param clazz the Class instance on which to search for a Method
+ *
+ * @return a single Method or null if no Method matching this
+ * resolver's criteria can be found.
+ *
+ * @throws IllegalArgumentException if more than one Method defined on the
+ * given Class matches this resolver's criteria
+ */
+ Method findMethod(Class> clazz);
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java
new file mode 100644
index 000000000..6f6a6f68e
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.configuration.util;
+
+import java.lang.reflect.Method;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Simple implementation of the {@link MethodInvoker} interface that invokes a method on an
+ * object. If the method has no arguments, but arguments are provided, they are ignored and the method
+ * is invoked anyway. If there are more arguments than there are provided, then an exception is thrown.
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ */
+public class SimpleMethodInvoker implements MethodInvoker {
+
+ private final Object object;
+ private Method method;
+
+ public SimpleMethodInvoker(Object object, Method method) {
+ Assert.notNull(object, "Object to invoke must not be null");
+ Assert.notNull(method, "Method to invoke must not be null");
+ this.object = object;
+ this.method = method;
+ }
+
+ public SimpleMethodInvoker(Object object, String methodName, Class>... paramTypes){
+ Assert.notNull(object, "Object to invoke must not be null");
+ this.object = object;
+ this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
+ if(this.method == null){
+ //try with no params
+ this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[]{});
+ }
+ if(this.method == null){
+ throw new IllegalArgumentException("No methods found for name: [" + methodName + "] in class: [" +
+ object.getClass() + "] with arguments of type: [" + paramTypes + "]");
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod(java.lang.Object[])
+ */
+ public Object invokeMethod(Object... args) {
+
+ Class>[] parameterTypes = method.getParameterTypes();
+ Object[] invokeArgs = new Object[parameterTypes.length];
+ if(parameterTypes.length == 0){
+ invokeArgs = new Object[]{};
+ }
+ else if(parameterTypes.length > args.length){
+ throw new IllegalArgumentException("Wrong number of arguments, expected no more than: [" + parameterTypes.length + "]");
+ }
+
+ method.setAccessible(true);
+
+ try {
+ return method.invoke(object, invokeArgs);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to invoke method: [" + method + "] on object: [" +
+ object + "] with arguments: [" + args + "]");
+ }
+ }
+}
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
new file mode 100644
index 000000000..0f92d4064
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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.configuration.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.batch.core.JobExecutionListener;
+import org.springframework.batch.core.listener.JobExecutionListenerAdapter;
+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.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;
+
+/**
+ * {@link BeanDefinitionParser} for {@link JobExecutionListener}s
+ *
+ * @author Lucas Ward
+ *
+ */
+public class JobExecutionListenerParser {
+
+
+ public ManagedList parse(Element element,
+ ParserContext parserContext) {
+ List listeners = new ArrayList();
+
+ @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);
+
+ String beforeMethod = listenerElement.getAttribute("before-method");
+ if(StringUtils.hasText(beforeMethod)){
+ listenerBuilder.addPropertyValue("beforeMethod", beforeMethod);
+ }
+
+ String afterMethod = listenerElement.getAttribute("after-method");
+ if(StringUtils.hasText(beforeMethod)){
+ listenerBuilder.addPropertyValue("afterMethod", afterMethod);
+ }
+ AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition();
+ String id = listenerElement.getAttribute("id");
+ if (!StringUtils.hasText(id)) {
+ id = parserContext.getReaderContext().generateBeanName(beanDef);
+ }
+ parserContext.getRegistry().registerBeanDefinition(id, beanDef);
+ BeanReference bean = new RuntimeBeanReference(id);
+ listeners.add(bean);
+ }
+
+ ManagedList managedList = new ManagedList();
+ @SuppressWarnings( { "unchecked", "unused" })
+ boolean dummy = managedList.addAll(listeners);
+
+ return managedList;
+ }
+
+}
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 a475ae384..3579562be 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
@@ -20,6 +20,7 @@ import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.repository.JobRepository;
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.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
@@ -62,9 +63,12 @@ public class JobParser extends AbstractBeanDefinitionParser {
FlowParser flowParser = new FlowParser();
AbstractBeanDefinition flowDef = flowParser.parse(element, parserContext, jobName);
-
builder.addPropertyValue("flow", flowDef);
-
+
+ JobExecutionListenerParser listenerParser = new JobExecutionListenerParser();
+ ManagedList managedList = listenerParser.parse(element, parserContext);
+ builder.addPropertyValue("jobExecutionListeners", managedList);
+
return builder.getBeanDefinition();
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerAdapter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerAdapter.java
new file mode 100644
index 000000000..4e5de7793
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * 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 java.lang.reflect.Method;
+
+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;
+import org.springframework.batch.core.configuration.util.AnnotationMethodResolver;
+import org.springframework.batch.core.configuration.util.MethodInvoker;
+import org.springframework.batch.core.configuration.util.MethodResolver;
+import org.springframework.batch.core.configuration.util.SimpleMethodInvoker;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+/**
+ * {@link JobExecutionListener} implementation that adapts a delegate object to the
+ * {@link JobExecutionListener} interface, using either string method names, or the
+ * {@link BeforeJob} and {@link AfterJob} annotations. It should be noted that priority
+ * is given to method names.
+ *
+ * @author Lucas Ward
+ * @since 2.0
+ */
+public class JobExecutionListenerAdapter implements JobExecutionListener, InitializingBean{
+
+ private final Object delegate;
+
+ private String beforeMethod;
+ private MethodInvoker beforeInvoker;
+
+ private String afterMethod;
+ private MethodInvoker afterInvoker;
+
+ public JobExecutionListenerAdapter(Object delegate) {
+ Assert.notNull(delegate, "Delegate must not be null");
+ this.delegate = delegate;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ if(beforeMethod != null){
+ beforeInvoker = new SimpleMethodInvoker(delegate, beforeMethod, JobExecution.class);
+ }
+ else{
+ MethodResolver resolver = new AnnotationMethodResolver(BeforeJob.class);
+ Method method = resolver.findMethod(delegate);
+ if(method != null){
+ beforeInvoker = new SimpleMethodInvoker(delegate, method);
+ }
+ }
+
+ if(afterMethod != null){
+ afterInvoker = new SimpleMethodInvoker(delegate, afterMethod, JobExecution.class);
+ }
+ else{
+ MethodResolver resolver = new AnnotationMethodResolver(AfterJob.class);
+ Method method = resolver.findMethod(delegate);
+ if(method != null){
+ afterInvoker = new SimpleMethodInvoker(delegate, method);
+ }
+ }
+
+ if(beforeInvoker == null && afterInvoker == null){
+ throw new IllegalArgumentException("No methods found with the provided method name or appropriate annotations");
+ }
+ }
+
+ public void setBeforeMethod(String beforeMethod) {
+ this.beforeMethod = beforeMethod;
+ }
+
+ public void setAfterMethod(String afterMethod) {
+ this.afterMethod = afterMethod;
+ }
+
+ public void afterJob(JobExecution jobExecution) {
+ if(afterInvoker != null){
+ afterInvoker.invokeMethod(jobExecution);
+ }
+ }
+
+ public void beforeJob(JobExecution jobExecution) {
+ if(beforeInvoker != null){
+ beforeInvoker.invokeMethod(jobExecution);
+ }
+ }
+
+}
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 2e4d140b3..3eb398659 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
@@ -20,8 +20,10 @@
Defines a job composed of a set of steps and
transitions between steps. The job will be exposed
- in the enclosing bean factory as a component of type Job
- that can be launched using a JobLauncher.
+ in the enclosing
+ bean factory as a component of type Job
+ that can be launched using a
+ JobLauncher.
@@ -37,7 +39,8 @@
-
+
-
+
@@ -104,8 +107,10 @@
- Defines a stage in job processing backed by a Step. The name
- attribute has to match the id of a bean definition for a Step.
+ Defines a stage in job processing backed by a
+ Step. The name
+ attribute has to match the id of a bean definition
+ for a Step.
The next attribute is a synonym for <next on="*" .../>
@@ -167,6 +172,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -492,4 +519,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java
new file mode 100644
index 000000000..feb41f6da
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.configuration.util;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Method;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobExecutionListener;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+public class SimpleMethodInvokerTests {
+
+ TestClass testClass;
+ JobExecution jobExecution = new JobExecution(11L);
+
+ @Before
+ public void setUp(){
+ testClass = new TestClass();
+ }
+
+ @Test
+ public void testMethod() throws Exception{
+
+ Method method = TestClass.class.getMethod("beforeJob");
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method);
+ methodInvoker.invokeMethod(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ }
+
+ @Test
+ public void testMethodByName() throws Exception{
+
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJob", JobExecutionListener.class);
+ methodInvoker.invokeMethod(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ }
+
+ @Test
+ public void testMethodWithExecution() throws Exception{
+ Method method = TestClass.class.getMethod("beforeJobWithExecution", JobExecution.class);
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method);
+ methodInvoker.invokeMethod(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ }
+
+ @Test
+ public void testMethodByNameWithExecution() throws Exception{
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJobWithExecution", JobExecution.class);
+ methodInvoker.invokeMethod(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testMethodWithTooManyArguments() throws Exception{
+ Method method = TestClass.class.getMethod("beforeJobWithTooManyArguments", JobExecution.class, int.class);
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method);
+ methodInvoker.invokeMethod(jobExecution);
+ assertFalse(testClass.beforeJobCalled);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testMethodByNameWithTooManyArguments() throws Exception{
+ MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJobWithTooManyArguments", JobExecution.class);
+ methodInvoker.invokeMethod(jobExecution);
+ assertFalse(testClass.beforeJobCalled);
+ }
+
+ private class TestClass{
+
+ boolean beforeJobCalled = false;
+
+ public void beforeJob(){
+ beforeJobCalled = true;
+ }
+
+ public void beforeJobWithExecution(JobExecution jobExecution){
+ beforeJobCalled = true;
+ }
+
+ public void beforeJobWithTooManyArguments(JobExecution jobExecution, int someInt){
+ beforeJobCalled = true;
+ }
+ }
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java
new file mode 100644
index 000000000..43a96f959
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java
@@ -0,0 +1,43 @@
+/*
+ * 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.configuration.xml;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.batch.core.Job;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class JobExecutionListenerParserTests {
+
+ @Autowired
+ Job job;
+
+ @Test
+ public void testListners(){
+ Assert.assertNotNull(job);
+ }
+
+}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestJobListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestJobListener.java
new file mode 100644
index 000000000..4b14b0725
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestJobListener.java
@@ -0,0 +1,15 @@
+package org.springframework.batch.core.configuration.xml;
+
+import org.springframework.batch.core.annotation.BeforeJob;
+
+public class TestJobListener {
+
+ @BeforeJob
+ public void beforeJob(){
+
+ }
+
+ public void afterJob(){
+
+ }
+}
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
new file mode 100644
index 000000000..97367c7a8
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobExecutionListenerAdapterTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.assertFalse;
+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.annotation.AfterJob;
+import org.springframework.batch.core.annotation.BeforeJob;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+public class JobExecutionListenerAdapterTests {
+
+ private TestClass testClass;
+ private AnnotatedTestClass annotatedTestClass;
+ private JobExecution jobExecution = new JobExecution(11L);
+
+ @Before
+ public void setUp(){
+ testClass = new TestClass();
+ annotatedTestClass = new AnnotatedTestClass();
+ }
+
+ @Test
+ public void testBeforeJob() throws Exception{
+ JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(testClass);
+ adapter.setBeforeMethod("beforeJob");
+ adapter.afterPropertiesSet();
+ adapter.beforeJob(jobExecution);
+ adapter.afterJob(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ assertFalse(testClass.afterJobCalled);
+ }
+
+ @Test
+ public void testAfterJob() throws Exception{
+ JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(testClass);
+ adapter.setAfterMethod("afterJob");
+ adapter.afterPropertiesSet();
+ adapter.beforeJob(jobExecution);
+ adapter.afterJob(jobExecution);
+ assertFalse(testClass.beforeJobCalled);
+ assertTrue(testClass.afterJobCalled);
+ }
+
+ @Test
+ public void testBoth() throws Exception{
+ JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(testClass);
+ adapter.setAfterMethod("afterJob");
+ adapter.setBeforeMethod("beforeJob");
+ adapter.afterPropertiesSet();
+ adapter.beforeJob(jobExecution);
+ adapter.afterJob(jobExecution);
+ assertTrue(testClass.beforeJobCalled);
+ assertTrue(testClass.afterJobCalled);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testNeither() throws Exception{
+ JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(testClass);
+ adapter.afterPropertiesSet();
+ }
+
+ @Test
+ public void testAnnotation() throws Exception{
+ JobExecutionListenerAdapter adapter = new JobExecutionListenerAdapter(annotatedTestClass);
+ adapter.afterPropertiesSet();
+ adapter.beforeJob(jobExecution);
+ adapter.afterJob(jobExecution);
+ assertTrue(annotatedTestClass.beforeJobCalled);
+ assertTrue(annotatedTestClass.afterJobCalled);
+ }
+
+ private class TestClass{
+
+ boolean beforeJobCalled = false;
+ boolean afterJobCalled = false;
+
+ public void beforeJob(JobExecution jobExecution){
+ beforeJobCalled = true;
+ }
+
+ public void afterJob(JobExecution jobExecution){
+ afterJobCalled = true;
+ }
+ }
+
+ private class AnnotatedTestClass extends TestClass{
+
+ @BeforeJob
+ public void before(){
+ super.beforeJobCalled = true;
+ }
+
+ @AfterJob
+ public void after(){
+ super.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
new file mode 100644
index 000000000..547e0b16c
--- /dev/null
+++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests-context.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file