BATCH-1118:

*Updated JobListenerFactoryBean so that listener methods specified via annotations or the metaDataMap are checked eagerly for correct signatures
 *Extracted common processing of Job/StepListenerFactoryBean classes into new superclass AbstractListenerFactoryBean
 *Created AbstractListenerMetaData interface for Job/StepListenerMetaData enums
 *Fixed JobExecutionListenerParser which had a bug in parsing listener method attributes
 *Corrected job listener attributes in xsd to use before/after-job-method
 *Added unit tests for job listener annotations and metaDataMap
 *Fixed typo in StepListenerFactoryBeanTests
This commit is contained in:
dhgarrette
2009-03-10 19:57:18 +00:00
parent 944b6aa4c8
commit 28d04d424a
11 changed files with 395 additions and 279 deletions

View File

@@ -83,14 +83,15 @@ public class JobExecutionListenerParser {
}
ManagedMap metaDataMap = new ManagedMap();
String beforeMethod = listenerElement.getAttribute("before-method");
if (StringUtils.hasText(beforeMethod)) {
metaDataMap.put("beforeMethod", beforeMethod);
}
String afterMethod = listenerElement.getAttribute("after-method");
if (StringUtils.hasText(beforeMethod)) {
metaDataMap.put("afterMethod", afterMethod);
String[] methodNameAttributes = new String[] {
"before-job-method",
"after-job-method"
};
for (String metaDataPropertyName : methodNameAttributes) {
String listenerMethod = listenerElement.getAttribute(metaDataPropertyName);
if(StringUtils.hasText(listenerMethod)){
metaDataMap.put(metaDataPropertyName, listenerMethod);
}
}
listenerBuilder.addPropertyValue("metaDataMap", metaDataMap);
AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition();

View File

@@ -0,0 +1,208 @@
/*
* 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.springframework.batch.support.MethodInvokerUtils.getParamTypesString;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.batch.support.MethodInvoker;
import org.springframework.batch.support.MethodInvokerUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link FactoryBean} implementation that builds a listener based on the
* various lifecycle methods or annotations that are provided. There are three
* possible ways of having a method called as part of a listener lifecycle:
*
* <ul>
* <li>Interface implementation: By implementing any of the subclasses of a
* listener interface, methods on said interface will be called
* <li>Annotations: Annotating a method will result in registration.
* <li>String name of the method to be called, which is tied to an
* {@link AbstractListenerMetaData} in the metaDatMap.
* </ul>
*
* It should be noted that methods obtained by name or annotation that don't
* match the listener method signatures to which they belong will cause errors.
* However, it is acceptable to have no parameters at all. If the same method is
* marked in more than one way. (i.e. the method name is given and it is
* annotated) the method will only be called once. However, if the same class
* has multiple methods tied to a particular listener, each method will be
* called. Also note that the same annotations cannot be applied to two separate
* methods in a single class.
*
* @author Lucas Ward
* @author Dan Garrette
* @since 2.0
* @see StepListenerMetaData
*/
public abstract class AbstractListenerFactoryBean implements FactoryBean, InitializingBean {
private static Log logger = LogFactory.getLog(StepListenerFactoryBean.class);
private Object delegate;
private Map<String, String> metaDataMap;
public Object getObject() {
if (metaDataMap == null) {
metaDataMap = new HashMap<String, String>();
}
// Because all annotations and interfaces should be checked for, make
// sure that each meta data
// entry is represented.
for (AbstractListenerMetaData metaData : this.getMetaDataValues()) {
if (!metaDataMap.containsKey(metaData.getPropertyName())) {
// put null so that the annotation and interface is checked
metaDataMap.put(metaData.getPropertyName(), null);
}
}
return this.doGetObject(delegate, metaDataMap);
}
protected abstract Object doGetObject(Object delegate, Map<String, String> metaDataMap);
protected abstract AbstractListenerMetaData[] getMetaDataValues();
/**
* Create a MethodInvoker from the delegate based on the annotationType.
* Ensure that the annotated method has a valid set of parameters.
*
* @param metaData
* @return a MethodInvoker
*/
protected MethodInvoker getMethodInvokerByAnnotation(final AbstractListenerMetaData metaData) {
final Class<? extends Annotation> annotationType = metaData.getAnnotation();
MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, delegate);
if (mi != null) {
ReflectionUtils.doWithMethods(delegate.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length > 0) {
Class<?>[] expectedParamTypes = metaData.getParamTypes();
String errorMsg = "The method [" + method.getName() + "] on target class ["
+ delegate.getClass().getSimpleName() + "] is incompatable with the signature ["
+ getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
+ metaData.getAnnotation().getSimpleName() + "].";
Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
for (int i = 0; i < paramTypes.length; i++) {
Assert.isTrue(paramTypes[i].equals(expectedParamTypes[i]), errorMsg);
}
}
}
}
});
}
return mi;
}
protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
if (methodName != null) {
return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params);
}
else {
return null;
}
}
public boolean isSingleton() {
return false;
}
public void setDelegate(Object delegate) {
if (delegate instanceof Advised) {
try {
setDelegate(((Advised) delegate).getTargetSource().getTarget());
} catch (Exception e) {
throw new IllegalStateException("Cannot generate listener for proxy with no target", e);
}
}
else {
this.delegate = delegate;
}
}
public void setMetaDataMap(Map<String, String> metaDataMap) {
this.metaDataMap = metaDataMap;
}
/*
* Extension of HashSet that ignores nulls, rather than putting them into
* the set.
*/
protected static class NullIgnoringSet<E> extends HashSet<E> {
@Override
public boolean add(E e) {
if (e == null) {
return false;
}
else {
return super.add(e);
}
};
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "Delegate must not be null");
}
/**
* Convenience method to check whether the given object is or can be made
* into a listener.
*
* @param delegate the object to check
* @return true if the delegate is an instance of any of the listener
* interface, or contains the marker annotations
*/
public static boolean isListener(Object delegate, Class<?> listenerType, AbstractListenerMetaData[] metaDataValues) {
if (listenerType.isInstance(delegate)) {
return true;
}
if (delegate instanceof Advised) {
try {
return isListener(((Advised) delegate).getTargetSource().getTarget(), listenerType, metaDataValues);
} catch (Exception e) {
logger.debug("Error obtaining target for Proxy. Assume not a listener.", e);
return false;
}
}
for (AbstractListenerMetaData metaData : metaDataValues) {
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate) != null) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.annotation.Annotation;
/**
* A common interface for ListenerMetaData enumerations.
*
* @author Dan Garrette
* @since 2.0
* @see JobListenerMetaData
* @see StepListenerMetaData
*/
public interface AbstractListenerMetaData {
public String getMethodName();
public Class<? extends Annotation> getAnnotation();
public String getPropertyName();
public Class<?>[] getParamTypes();
}

View File

@@ -15,11 +15,9 @@
*/
package org.springframework.batch.core.listener;
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation;
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
@@ -29,76 +27,33 @@ import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.support.MethodInvoker;
import org.springframework.batch.support.MethodInvokerUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.batch.core.listener.MethodInvokerMethodInterceptor;
/**
* {@link FactoryBean} implementation that builds a {@link JobExecutionListener}
* based on the various lifecycle methods or annotations that are provided.
* There are three possible ways of having a method called as part of a
* {@link JobExecutionListener} lifecyle:
*
* <ul>
* <li>Interface implementation: By implementing JobExecutionListener, methods
* on said interface will be called.
* <li>Annotations: Annotating a method will result in registration.
* <li>String name of the method to be called, which is tied to
* {@link JobListenerMetaData} in the metaDatMap.
* </ul>
*
* It should be noted that methods obtained by name or annotation that don't
* match the listener method signatures to which they belong, will cause errors.
* However, it is acceptable to have no parameters at all. If the same method is
* marked in more than one way. (i.e. the method name is given and it's
* annotated) the method will only be called once. However, if the same class
* has multiple methods tied to a particular listener, each method will be
* called.
* This {@link AbstractListenerFactoryBean} implementation is used to create a
* {@link JobExecutionListener}.
*
* @author Lucas Ward
* @author Dan Garrette
* @since 2.0
* @see AbstractListenerFactoryBean
* @see JobListenerMetaData
*/
public class JobListenerFactoryBean implements FactoryBean, InitializingBean {
public class JobListenerFactoryBean extends AbstractListenerFactoryBean {
private Object delegate;
private Map<String, String> metaDataMap;
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
public void setMetaDataMap(Map<String, String> metaDataMap) {
this.metaDataMap = metaDataMap;
}
public Object getObject() {
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
if (metaDataMap == null) {
metaDataMap = new HashMap<String, String>();
}
// Because all annotations and interfaces should be checked for, make
// sure that each meta data
// entry is represented.
for (JobListenerMetaData metaData : JobListenerMetaData.values()) {
if (!metaDataMap.containsKey(metaData.getPropertyName())) {
// put null so that the annotation and interface is checked
metaDataMap.put(metaData.getPropertyName(), null);
}
}
public Object doGetObject(Object delegate, Map<String, String> metaDataMap) {
// For every entry in the map, try and find a method by interface, name,
// or annotation. If the same
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
for (Entry<String, String> entry : metaDataMap.entrySet()) {
JobListenerMetaData metaData = JobListenerMetaData.fromPropertyName(entry.getKey());
Set<MethodInvoker> invokers = new NullIgnoringSet<MethodInvoker>();
invokers.add(getMethodInvokerByName(entry.getValue(), delegate, JobExecution.class));
invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes()));
invokers.add(getMethodInvokerForInterface(JobExecutionListener.class, metaData.getMethodName(), delegate,
JobExecution.class));
invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate));
invokers.add(getMethodInvokerByAnnotation(metaData));
if (!invokers.isEmpty()) {
invokerMap.put(metaData.getMethodName(), invokers);
}
@@ -108,7 +63,7 @@ public class JobListenerFactoryBean implements FactoryBean, InitializingBean {
// be called
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(delegate);
boolean ordered = false;
if (delegate instanceof Ordered) {
ordered = true;
@@ -120,13 +75,8 @@ public class JobListenerFactoryBean implements FactoryBean, InitializingBean {
return proxyFactory.getProxy();
}
private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
if (methodName != null) {
return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params);
}
else {
return null;
}
protected AbstractListenerMetaData[] getMetaDataValues() {
return JobListenerMetaData.values();
}
@SuppressWarnings("unchecked")
@@ -134,17 +84,10 @@ public class JobListenerFactoryBean implements FactoryBean, InitializingBean {
return JobExecutionListener.class;
}
public boolean isSingleton() {
return false;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "Delegate listener must not be null");
}
/**
* Convenience method to wrap any object and expose the appropriate
* {@link JobExecutionListener} interfaces.
*
* @param delegate a delegate object
* @return a JobListener instance constructed from the delegate
*/
@@ -157,38 +100,13 @@ public class JobListenerFactoryBean implements FactoryBean, InitializingBean {
/**
* Convenience method to check whether the given object is or can be made
* into a {@link JobExecutionListener}.
*
* @param delegate the object to check
* @return true if the delegate is an instance of
* {@link JobExecutionListener}, or contains the marker annotations
* {@link JobExecutionListener}, or contains the marker annotations
*/
public static boolean isListener(Object delegate) {
if (delegate instanceof JobExecutionListener) {
return true;
}
for (JobListenerMetaData metaData : JobListenerMetaData.values()) {
Set<MethodInvoker> invokers = new NullIgnoringSet<MethodInvoker>();
invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate));
if (!invokers.isEmpty()) {
return true;
}
}
return false;
}
/*
* Extension of HashSet that ignores nulls, rather than putting them into
* the set.
*/
private static class NullIgnoringSet<E> extends HashSet<E> {
@Override
public boolean add(E e) {
if (e == null) {
return false;
}
else {
return super.add(e);
}
};
return AbstractListenerFactoryBean.isListener(delegate, JobExecutionListener.class, JobListenerMetaData
.values());
}
}

View File

@@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
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;
@@ -31,7 +32,7 @@ import org.springframework.batch.core.annotation.BeforeJob;
* @since 2.0
* @see JobListenerFactoryBean
*/
public enum JobListenerMetaData {
public enum JobListenerMetaData implements AbstractListenerMetaData {
BEFORE_JOB("beforeJob", "before-job-method", BeforeJob.class),
AFTER_JOB("afterJob", "after-job-method", AfterJob.class);
@@ -63,11 +64,14 @@ public enum JobListenerMetaData {
return annotation;
}
public String getPropertyName() {
return propertyName;
}
public Class<?>[] getParamTypes() {
return new Class<?>[]{ JobExecution.class };
}
/**
* Return the relevant meta data for the provided property name.
*

View File

@@ -16,92 +16,45 @@
package org.springframework.batch.core.listener;
import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
import static org.springframework.batch.support.MethodInvokerUtils.getParamTypesString;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.support.MethodInvoker;
import org.springframework.batch.support.MethodInvokerUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link FactoryBean} implementation that builds a {@link StepListener} based
* on the various lifecycle methods or annotations that are provided. There are
* three possible ways of having a method called as part of a
* {@link StepListener} lifecycle:
*
* <ul>
* <li>Interface implementation: By implementing any of the subclasses of
* StepListener, methods on said interface will be called
* <li>Annotations: Annotating a method will result in registration.
* <li>String name of the method to be called, which is tied to
* {@link StepListenerMetaData} in the metaDatMap.
* </ul>
*
* It should be noted that methods obtained by name or annotation that don't
* match the StepListener method signatures to which they belong, will cause
* errors. However, it is acceptable to have no parameters at all. If the same
* method is marked in more than one way. (i.e. the method name is given and
* it's annotated) the method will only be called once. However, if the same
* class has multiple methods tied to a particular listener, each method will be
* called.
* This {@link AbstractListenerFactoryBean} implementation is used to create a
* {@link StepListener}.
*
* @author Lucas Ward
* @author Dan Garrette
* @since 2.0
* @see AbstractListenerFactoryBean
* @see StepListenerMetaData
*/
public class StepListenerFactoryBean implements FactoryBean, InitializingBean {
public class StepListenerFactoryBean extends AbstractListenerFactoryBean {
private static Log logger = LogFactory.getLog(StepListenerFactoryBean.class);
private Object delegate;
private Map<String, String> metaDataMap;
public Object getObject() {
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
if (metaDataMap == null) {
metaDataMap = new HashMap<String, String>();
}
// Because all annotations and interfaces should be checked for, make
// sure that each meta data
// entry is represented.
for (StepListenerMetaData metaData : StepListenerMetaData.values()) {
if (!metaDataMap.containsKey(metaData.getPropertyName())) {
// put null so that the annotation and interface is checked
metaDataMap.put(metaData.getPropertyName(), null);
}
}
public Object doGetObject(Object delegate, Map<String, String> metaDataMap) {
Set<Class<?>> listenerInterfaces = new HashSet<Class<?>>();
// For every entry in the map, try and find a method by interface, name,
// or annotation. If the same
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
for (Entry<String, String> entry : metaDataMap.entrySet()) {
final StepListenerMetaData metaData = StepListenerMetaData.fromPropertyName(entry.getKey());
Set<MethodInvoker> invokers = new NullIgnoringSet<MethodInvoker>();
invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes()));
invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(),
delegate, metaData.getParamTypes()));
invokers.add(getMethodInvokerByAnnotation(metaData, metaData.getAnnotation()));
invokers.add(getMethodInvokerByAnnotation(metaData));
if (!invokers.isEmpty()) {
invokerMap.put(metaData.getMethodName(), invokers);
listenerInterfaces.add(metaData.getListenerInterface());
@@ -127,50 +80,8 @@ public class StepListenerFactoryBean implements FactoryBean, InitializingBean {
return proxyFactory.getProxy();
}
/**
* Create a MethodInvoker from the delegate based on the annotationType.
* Ensure that the annotated method has a valid set of parameters.
*
* @param metaData
* @param annotationType
* @return
*/
private MethodInvoker getMethodInvokerByAnnotation(final StepListenerMetaData metaData,
final Class<? extends Annotation> annotationType) {
MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, delegate);
if (mi != null) {
ReflectionUtils.doWithMethods(delegate.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length > 0) {
Class<?>[] expectedParamTypes = metaData.getParamTypes();
String errorMsg = "The method [" + method.getName() + "] on target class ["
+ delegate.getClass().getSimpleName() + "] is incompatable with the signature ["
+ getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
+ metaData.getAnnotation().getSimpleName() + "].";
Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
for (int i = 0; i < paramTypes.length; i++) {
Assert.isTrue(paramTypes[i].equals(expectedParamTypes[i]), errorMsg);
}
}
}
}
});
}
return mi;
}
private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
if (methodName != null) {
return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params);
}
else {
return null;
}
protected AbstractListenerMetaData[] getMetaDataValues() {
return StepListenerMetaData.values();
}
@SuppressWarnings("unchecked")
@@ -178,28 +89,6 @@ public class StepListenerFactoryBean implements FactoryBean, InitializingBean {
return StepListener.class;
}
public boolean isSingleton() {
return false;
}
public void setDelegate(Object delegate) {
if (delegate instanceof Advised) {
try {
setDelegate(((Advised) delegate).getTargetSource().getTarget());
}
catch (Exception e) {
throw new IllegalStateException("Cannot generate listener for proxy with no target", e);
}
}
else {
this.delegate = delegate;
}
}
public void setMetaDataMap(Map<String, String> metaDataMap) {
this.metaDataMap = metaDataMap;
}
/**
* Convenience method to wrap any object and expose the appropriate
* {@link StepListener} interfaces.
@@ -222,44 +111,6 @@ public class StepListenerFactoryBean implements FactoryBean, InitializingBean {
* {@link StepListener} interfaces, or contains the marker annotations
*/
public static boolean isListener(Object delegate) {
if (delegate instanceof StepListener) {
return true;
}
if (delegate instanceof Advised) {
try {
return isListener(((Advised) delegate).getTargetSource().getTarget());
}
catch (Exception e) {
logger.debug("Error obtaining target for Proxy. Assume not a listener.", e);
return false;
}
}
for (StepListenerMetaData metaData : StepListenerMetaData.values()) {
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate) != null) {
return true;
}
}
return false;
}
/*
* Extension of HashSet that ignores nulls, rather than putting them into
* the set.
*/
private static class NullIgnoringSet<E> extends HashSet<E> {
@Override
public boolean add(E e) {
if (e == null) {
return false;
}
else {
return super.add(e);
}
};
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "Delegate must not be null");
return AbstractListenerFactoryBean.isListener(delegate, StepListener.class, StepListenerMetaData.values());
}
}

View File

@@ -53,7 +53,7 @@ import org.springframework.batch.core.annotation.OnWriteError;
* @since 2.0
* @see StepListenerFactoryBean
*/
public enum StepListenerMetaData {
public enum StepListenerMetaData implements AbstractListenerMetaData {
BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class),
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),

View File

@@ -229,7 +229,8 @@
<xsd:attribute name="ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A reference to a JobExecutionListener or a POJO (with before-method / after-method).
A reference to a JobExecutionListener or a POJO
(with before-job-method / after-job-method).
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
@@ -248,8 +249,8 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="before-method" type="xsd:string" />
<xsd:attribute name="after-method" type="xsd:string" />
<xsd:attribute name="before-job-method" type="xsd:string" />
<xsd:attribute name="after-job-method" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>

View File

@@ -17,8 +17,11 @@ package org.springframework.batch.core.listener;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.springframework.batch.core.listener.JobListenerMetaData.AFTER_JOB;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
@@ -28,6 +31,7 @@ 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.xml.AbstractTestComponent;
import org.springframework.core.Ordered;
/**
@@ -138,6 +142,100 @@ public class JobListenerFactoryBeanTests {
assertEquals(listener1, listener2);
}
@Test
public void testEmptySignatureAnnotation() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterJob
public void aMethod() {
executed = true;
}
};
factoryBean.setDelegate(delegate);
JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject();
listener.afterJob(new JobExecution(1L));
assertTrue(delegate.isExecuted());
}
@Test
public void testRightSignatureAnnotation() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterJob
public void aMethod(JobExecution jobExecution) {
executed = true;
assertEquals(new Long(25), jobExecution.getId());
}
};
factoryBean.setDelegate(delegate);
JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject();
listener.afterJob(new JobExecution(25L));
assertTrue(delegate.isExecuted());
}
@Test(expected = IllegalArgumentException.class)
public void testWrongSignatureAnnotation() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterJob
public void aMethod(Integer item) {
executed = true;
}
};
factoryBean.setDelegate(delegate);
factoryBean.getObject();
}
@Test
public void testEmptySignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
public void aMethod() {
executed = true;
}
};
factoryBean.setDelegate(delegate);
Map<String, String> metaDataMap = new HashMap<String, String>();
metaDataMap.put(AFTER_JOB.getPropertyName(), "aMethod");
factoryBean.setMetaDataMap(metaDataMap);
JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject();
listener.afterJob(new JobExecution(1L));
assertTrue(delegate.isExecuted());
}
@Test
public void testRightSignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
public void aMethod(JobExecution jobExecution) {
executed = true;
assertEquals(new Long(25), jobExecution.getId());
}
};
factoryBean.setDelegate(delegate);
Map<String, String> metaDataMap = new HashMap<String, String>();
metaDataMap.put(AFTER_JOB.getPropertyName(), "aMethod");
factoryBean.setMetaDataMap(metaDataMap);
JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject();
listener.afterJob(new JobExecution(25L));
assertTrue(delegate.isExecuted());
}
@Test(expected = IllegalArgumentException.class)
public void testWrongSignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
public void aMethod(Integer item) {
executed = true;
}
};
factoryBean.setDelegate(delegate);
Map<String, String> metaDataMap = new HashMap<String, String>();
metaDataMap.put(AFTER_JOB.getPropertyName(), "aMethod");
factoryBean.setMetaDataMap(metaDataMap);
factoryBean.getObject();
}
private class JobListenerWithInterface implements JobExecutionListener {
boolean beforeJobCalled = false;

View File

@@ -311,7 +311,6 @@ public class StepListenerFactoryBeanTests {
public void testEmptySignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterWrite
public void aMethod() {
executed = true;
}
@@ -330,7 +329,6 @@ public class StepListenerFactoryBeanTests {
public void testRightSignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterWrite
public void aMethod(List<String> items) {
executed = true;
assertEquals("foo", items.get(0));
@@ -351,7 +349,6 @@ public class StepListenerFactoryBeanTests {
public void testWrongSignatureNamedMethod() {
AbstractTestComponent delegate = new AbstractTestComponent() {
@SuppressWarnings("unused")
@AfterWrite
public void aMethod(Integer item) {
executed = true;
}

View File

@@ -9,8 +9,8 @@
<job id="job" incrementer="testIncrementer" job-repository="jobRepository">
<step id="s1" ref="step1"/>
<listeners>
<listener after-method="afterJob" ref="testListener"/>
<listener after-method="afterJob" class="org.springframework.batch.core.configuration.xml.TestJobListener"/>
<listener after-job-method="afterJob" ref="testListener"/>
<listener after-job-method="afterJob" class="org.springframework.batch.core.configuration.xml.TestJobListener"/>
<listener class="org.springframework.batch.core.configuration.xml.JobExecutionListenerParserTests$TestComponent"/>
</listeners>
</job>