Skip to content

Commit 41dec7a

Browse files
author
lucasward
committed
BATCH-891: Created JobExecutionListenerAdapter which supports @BeforeJob and @AfterJob annotation. Also added listners tag to XSD.
1 parent 0d4777e commit 41dec7a

File tree

14 files changed

+836
-8
lines changed

14 files changed

+836
-8
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.springframework.batch.core.JobExecution;
1313
import org.springframework.batch.core.JobExecutionListener;
1414
import org.springframework.batch.core.Step;
15+
import org.springframework.beans.factory.annotation.Qualifier;
1516

1617
/**
1718
* Marks a method to be called before a {@link Job} is executed, which comes
@@ -24,6 +25,7 @@
2425
*/
2526
@Retention(RetentionPolicy.RUNTIME)
2627
@Target({ElementType.METHOD})
28+
@Qualifier("JobExecutionListener")
2729
public @interface BeforeJob {
2830

2931
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2002-2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.core.configuration.util;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Target;
22+
import java.lang.reflect.Method;
23+
import java.util.concurrent.atomic.AtomicReference;
24+
25+
import org.springframework.aop.support.AopUtils;
26+
import org.springframework.core.annotation.AnnotationUtils;
27+
import org.springframework.util.Assert;
28+
import org.springframework.util.ObjectUtils;
29+
import org.springframework.util.ReflectionUtils;
30+
31+
/**
32+
* MethodResolver implementation that finds a <em>single</em> Method on the
33+
* given Class that contains the specified annotation type.
34+
*
35+
* @author Mark Fisher
36+
*/
37+
public class AnnotationMethodResolver implements MethodResolver {
38+
39+
private Class<? extends Annotation> annotationType;
40+
41+
42+
/**
43+
* Create a MethodResolver for the specified Method-level annotation type
44+
*/
45+
public AnnotationMethodResolver(Class<? extends Annotation> annotationType) {
46+
Assert.notNull(annotationType, "annotationType must not be null");
47+
Assert.isTrue(ObjectUtils.containsElement(
48+
annotationType.getAnnotation(Target.class).value(), ElementType.METHOD),
49+
"Annotation [" + annotationType + "] is not a Method-level annotation.");
50+
this.annotationType = annotationType;
51+
}
52+
53+
54+
/**
55+
* Find a <em>single</em> Method on the Class of the given candidate object
56+
* that contains the annotation type for which this resolver is searching.
57+
*
58+
* @param candidate the instance whose Class will be checked for the
59+
* annotation
60+
*
61+
* @return a single matching Method instance or <code>null</code> if the
62+
* candidate's Class contains no Methods with the specified annotation
63+
*
64+
* @throws IllegalArgumentException if more than one Method has the
65+
* specified annotation
66+
*/
67+
public Method findMethod(Object candidate) {
68+
Assert.notNull(candidate, "candidate object must not be null");
69+
Class<?> targetClass = AopUtils.getTargetClass(candidate);
70+
if (targetClass == null) {
71+
targetClass = candidate.getClass();
72+
}
73+
return this.findMethod(targetClass);
74+
}
75+
76+
/**
77+
* Find a <em>single</em> Method on the given Class that contains the
78+
* annotation type for which this resolver is searching.
79+
*
80+
* @param clazz the Class instance to check for the annotation
81+
*
82+
* @return a single matching Method instance or <code>null</code> if the
83+
* Class contains no Methods with the specified annotation
84+
*
85+
* @throws IllegalArgumentException if more than one Method has the
86+
* specified annotation
87+
*/
88+
public Method findMethod(final Class<?> clazz) {
89+
Assert.notNull(clazz, "class must not be null");
90+
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
91+
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
92+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
93+
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
94+
if (annotation != null) {
95+
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
96+
+ clazz + "] with the annotation type [" + annotationType + "]");
97+
annotatedMethod.set(method);
98+
}
99+
}
100+
});
101+
return annotatedMethod.get();
102+
}
103+
104+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.core.configuration.util;
18+
19+
/**
20+
* A strategy interface for invoking a method.
21+
* Typically used by adapters.
22+
*
23+
* @author Mark Fisher
24+
*/
25+
public interface MethodInvoker {
26+
27+
Object invokeMethod(Object ... args);
28+
29+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2002-2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.core.configuration.util;
18+
19+
import java.lang.reflect.Method;
20+
21+
/**
22+
* Strategy interface for detecting a single Method on a Class.
23+
*
24+
* @author Mark Fisher
25+
*/
26+
public interface MethodResolver {
27+
28+
/**
29+
* Find a single Method on the provided Object that matches this resolver's
30+
* criteria.
31+
*
32+
* @param candidate the candidate Object whose Class should be searched for
33+
* a Method
34+
*
35+
* @return a single Method or <code>null</code> if no Method matching this
36+
* resolver's criteria can be found.
37+
*
38+
* @throws IllegalArgumentException if more than one Method defined on the
39+
* given candidate's Class matches this resolver's criteria
40+
*/
41+
Method findMethod(Object candidate) throws IllegalArgumentException;
42+
43+
/**
44+
* Find a <em>single</em> Method on the given Class that matches this
45+
* resolver's criteria.
46+
*
47+
* @param clazz the Class instance on which to search for a Method
48+
*
49+
* @return a single Method or <code>null</code> if no Method matching this
50+
* resolver's criteria can be found.
51+
*
52+
* @throws IllegalArgumentException if more than one Method defined on the
53+
* given Class matches this resolver's criteria
54+
*/
55+
Method findMethod(Class<?> clazz);
56+
57+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2002-2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*
18+
* Copyright 2002-2008 the original author or authors.
19+
*
20+
* Licensed under the Apache License, Version 2.0 (the "License");
21+
* you may not use this file except in compliance with the License.
22+
* You may obtain a copy of the License at
23+
*
24+
* http://www.apache.org/licenses/LICENSE-2.0
25+
*
26+
* Unless required by applicable law or agreed to in writing, software
27+
* distributed under the License is distributed on an "AS IS" BASIS,
28+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29+
* See the License for the specific language governing permissions and
30+
* limitations under the License.
31+
*/
32+
package org.springframework.batch.core.configuration.util;
33+
34+
import java.lang.reflect.Method;
35+
36+
import org.springframework.util.Assert;
37+
import org.springframework.util.ClassUtils;
38+
39+
/**
40+
* Simple implementation of the {@link MethodInvoker} interface that invokes a method on an
41+
* object. If the method has no arguments, but arguments are provided, they are ignored and the method
42+
* is invoked anyway. If there are more arguments than there are provided, then an exception is thrown.
43+
*
44+
* @author Lucas Ward
45+
* @since 2.0
46+
*/
47+
public class SimpleMethodInvoker implements MethodInvoker {
48+
49+
private final Object object;
50+
private Method method;
51+
52+
public SimpleMethodInvoker(Object object, Method method) {
53+
Assert.notNull(object, "Object to invoke must not be null");
54+
Assert.notNull(method, "Method to invoke must not be null");
55+
this.object = object;
56+
this.method = method;
57+
}
58+
59+
public SimpleMethodInvoker(Object object, String methodName, Class<?>... paramTypes){
60+
Assert.notNull(object, "Object to invoke must not be null");
61+
this.object = object;
62+
this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
63+
if(this.method == null){
64+
//try with no params
65+
this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[]{});
66+
}
67+
if(this.method == null){
68+
throw new IllegalArgumentException("No methods found for name: [" + methodName + "] in class: [" +
69+
object.getClass() + "] with arguments of type: [" + paramTypes + "]");
70+
}
71+
}
72+
73+
/* (non-Javadoc)
74+
* @see org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod(java.lang.Object[])
75+
*/
76+
public Object invokeMethod(Object... args) {
77+
78+
Class<?>[] parameterTypes = method.getParameterTypes();
79+
Object[] invokeArgs = new Object[parameterTypes.length];
80+
if(parameterTypes.length == 0){
81+
invokeArgs = new Object[]{};
82+
}
83+
else if(parameterTypes.length > args.length){
84+
throw new IllegalArgumentException("Wrong number of arguments, expected no more than: [" + parameterTypes.length + "]");
85+
}
86+
87+
method.setAccessible(true);
88+
89+
try {
90+
return method.invoke(object, invokeArgs);
91+
} catch (Exception e) {
92+
throw new IllegalArgumentException("Unable to invoke method: [" + method + "] on object: [" +
93+
object + "] with arguments: [" + args + "]");
94+
}
95+
}
96+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2002-2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.core.configuration.xml;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.batch.core.JobExecutionListener;
23+
import org.springframework.batch.core.listener.JobExecutionListenerAdapter;
24+
import org.springframework.beans.factory.config.BeanReference;
25+
import org.springframework.beans.factory.config.RuntimeBeanReference;
26+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
27+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
28+
import org.springframework.beans.factory.support.ManagedList;
29+
import org.springframework.beans.factory.xml.BeanDefinitionParser;
30+
import org.springframework.beans.factory.xml.ParserContext;
31+
import org.springframework.util.StringUtils;
32+
import org.springframework.util.xml.DomUtils;
33+
import org.w3c.dom.Element;
34+
35+
/**
36+
* {@link BeanDefinitionParser} for {@link JobExecutionListener}s
37+
*
38+
* @author Lucas Ward
39+
*
40+
*/
41+
public class JobExecutionListenerParser {
42+
43+
44+
public ManagedList parse(Element element,
45+
ParserContext parserContext) {
46+
List<BeanReference> listeners = new ArrayList<BeanReference>();
47+
48+
@SuppressWarnings("unchecked")
49+
List<Element> listenerElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "listener");
50+
for(Element listenerElement : listenerElements){
51+
BeanDefinitionBuilder listenerBuilder = BeanDefinitionBuilder.genericBeanDefinition(JobExecutionListenerAdapter.class);
52+
String delegateName = listenerElement.getAttribute("ref");
53+
listenerBuilder.addConstructorArgReference(delegateName);
54+
55+
String beforeMethod = listenerElement.getAttribute("before-method");
56+
if(StringUtils.hasText(beforeMethod)){
57+
listenerBuilder.addPropertyValue("beforeMethod", beforeMethod);
58+
}
59+
60+
String afterMethod = listenerElement.getAttribute("after-method");
61+
if(StringUtils.hasText(beforeMethod)){
62+
listenerBuilder.addPropertyValue("afterMethod", afterMethod);
63+
}
64+
AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition();
65+
String id = listenerElement.getAttribute("id");
66+
if (!StringUtils.hasText(id)) {
67+
id = parserContext.getReaderContext().generateBeanName(beanDef);
68+
}
69+
parserContext.getRegistry().registerBeanDefinition(id, beanDef);
70+
BeanReference bean = new RuntimeBeanReference(id);
71+
listeners.add(bean);
72+
}
73+
74+
ManagedList managedList = new ManagedList();
75+
@SuppressWarnings( { "unchecked", "unused" })
76+
boolean dummy = managedList.addAll(listeners);
77+
78+
return managedList;
79+
}
80+
81+
}

0 commit comments

Comments
 (0)