Skip to content

Commit cb71ecf

Browse files
committed
FEATURE: validate step listeners
1 parent 10fd371 commit cb71ecf

File tree

10 files changed

+204
-142
lines changed

10 files changed

+204
-142
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,11 @@ public static boolean isListener(Object target, Class<?> listenerType, ListenerM
214214
if (target == null) {
215215
return false;
216216
}
217+
217218
if (listenerType.isInstance(target)) {
218219
return true;
219220
}
221+
220222
if (target instanceof Advised) {
221223
TargetSource targetSource = ((Advised) target).getTargetSource();
222224
if (targetSource != null && targetSource.getTargetClass() != null
@@ -228,11 +230,21 @@ public static boolean isListener(Object target, Class<?> listenerType, ListenerM
228230
logger.warn(String.format("%s is an interface. The implementing class will not be queried for annotation based listener configurations. If using @StepScope on a @Bean method, be sure to return the implementing class so listener annotations can be used.", targetSource.getTargetClass().getName()));
229231
}
230232
}
233+
231234
for (ListenerMetaData metaData : metaDataValues) {
232-
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
235+
Class<?> listenerInterface = metaData.getListenerInterface();
236+
if (listenerInterface.isInstance(target)) {
233237
return true;
234238
}
239+
240+
if (metaData.getAnnotation() != null) {
241+
MethodInvoker annotatedMethod = getMethodInvokerByAnnotation(metaData.getAnnotation(), target);
242+
if (annotatedMethod != null) {
243+
return true;
244+
}
245+
}
235246
}
247+
236248
return false;
237249
}
238250
}

spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
*/
2828
public interface ListenerMetaData {
2929

30-
public String getMethodName();
30+
String getMethodName();
3131

32-
public Class<? extends Annotation> getAnnotation();
32+
Class<? extends Annotation> getAnnotation();
3333

34-
public Class<?> getListenerInterface();
34+
Class<?> getListenerInterface();
3535

36-
public String getPropertyName();
36+
String getPropertyName();
3737

38-
public Class<?>[] getParamTypes();
38+
Class<?>[] getParamTypes();
3939

4040
}

spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,34 @@
1515
*/
1616
package org.springframework.batch.core.listener;
1717

18+
import java.util.stream.Collectors;
19+
import java.util.stream.Stream;
20+
1821
import org.springframework.batch.core.StepListener;
22+
import org.springframework.batch.core.jsr.JsrStepListenerMetaData;
1923

2024
/**
2125
* This {@link AbstractListenerFactoryBean} implementation is used to create a
2226
* {@link StepListener}.
2327
*
2428
* @author Lucas Ward
2529
* @author Dan Garrette
30+
* @author Alexei KLENIN
2631
* @since 2.0
2732
* @see AbstractListenerFactoryBean
2833
* @see StepListenerMetaData
2934
*/
3035
public class StepListenerFactoryBean extends AbstractListenerFactoryBean<StepListener> {
36+
private static final String STR_STEP_LISTENER_ANNOTATIONS_LIST = Stream
37+
.of(StepListenerMetaData.values())
38+
.map(StepListenerMetaData::getAnnotation)
39+
.map(Class::getSimpleName)
40+
.map(annotation -> String.format("\t- @%s\n", annotation))
41+
.collect(Collectors.joining());
42+
private static final String ERR_OBJECT_NOT_STEP_LISTENER_TEMPLATE =
43+
"Object of type [%s] is not a valid step listener. " +
44+
"It must ether implement StepListener interface or have methods annotated with any of:\n" +
45+
STR_STEP_LISTENER_ANNOTATIONS_LIST;
3146

3247
@Override
3348
protected ListenerMetaData getMetaDataFromPropertyName(String propertyName) {
@@ -49,6 +64,11 @@ public Class<StepListener> getObjectType() {
4964
return StepListener.class;
5065
}
5166

67+
@Override
68+
public void setDelegate(Object delegate) {
69+
super.setDelegate(assertListener(delegate));
70+
}
71+
5272
/**
5373
* Convenience method to wrap any object and expose the appropriate
5474
* {@link StepListener} interfaces.
@@ -72,6 +92,30 @@ public static StepListener getListener(Object delegate) {
7292
* annotations
7393
*/
7494
public static boolean isListener(Object delegate) {
75-
return AbstractListenerFactoryBean.isListener(delegate, StepListener.class, StepListenerMetaData.values());
95+
ListenerMetaData[] metaDataValues = Stream
96+
.<ListenerMetaData> concat(
97+
Stream.of(StepListenerMetaData.values()),
98+
Stream.of(JsrStepListenerMetaData.values()))
99+
.toArray(ListenerMetaData[]::new);
100+
101+
return AbstractListenerFactoryBean.isListener(delegate, StepListener.class, metaDataValues);
102+
}
103+
104+
/**
105+
* Asserts that {@code delegate} is a valid step listener. If this is not a case, throws an
106+
* {@link IllegalArgumentException} with message detailing the problem. Object is a valid
107+
* step listener is it ether implements interface {@link StepListener} or has any method
108+
* annotated with one of marker annotations.
109+
*
110+
* @param delegate the object to check
111+
* @return valid step execution listener
112+
*/
113+
public static Object assertListener(Object delegate) {
114+
if (!isListener(delegate)) {
115+
throw new IllegalArgumentException(String.format(
116+
ERR_OBJECT_NOT_STEP_LISTENER_TEMPLATE, delegate.getClass().getName()));
117+
}
118+
119+
return delegate;
76120
}
77121
}

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.batch.core.ChunkListener;
2424
import org.springframework.batch.core.Step;
2525
import org.springframework.batch.core.StepExecutionListener;
26+
import org.springframework.batch.core.StepListener;
2627
import org.springframework.batch.core.annotation.AfterChunk;
2728
import org.springframework.batch.core.annotation.AfterChunkError;
2829
import org.springframework.batch.core.annotation.BeforeChunk;
@@ -47,6 +48,7 @@
4748
* @author Dave Syer
4849
* @author Michael Minella
4950
* @author Mahmoud Ben Hassine
51+
* @author Alexei Klenin
5052
*
5153
* @since 2.2
5254
*
@@ -159,10 +161,9 @@ public B listener(Object listener) {
159161
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class));
160162
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunkError.class));
161163

162-
if(chunkListenerMethods.size() > 0) {
163-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
164-
factory.setDelegate(listener);
165-
this.listener((ChunkListener) factory.getObject());
164+
if (!chunkListenerMethods.isEmpty()) {
165+
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
166+
this.listener((ChunkListener) stepListener);
166167
}
167168

168169
@SuppressWarnings("unchecked")

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
* @author Chris Schaefer
9292
* @author Michael Minella
9393
* @author Mahmoud Ben Hassine
94+
* @author Alexei Klenin
9495
*
9596
* @since 2.2
9697
*/
@@ -204,18 +205,14 @@ public SimpleStepBuilder<I, O> listener(Object listener) {
204205
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class));
205206
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class));
206207

207-
if(skipListenerMethods.size() > 0) {
208-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
209-
factory.setDelegate(listener);
210-
skipListeners.add((SkipListener) factory.getObject());
208+
if (!skipListenerMethods.isEmpty()) {
209+
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
210+
skipListeners.add((SkipListener<I, O>) stepListener);
211211
}
212212

213-
@SuppressWarnings("unchecked")
214-
SimpleStepBuilder<I, O> result = this;
215-
return result;
213+
return this;
216214
}
217215

218-
219216
/**
220217
* Register a skip listener.
221218
*

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
*
6565
* @author Dave Syer
6666
* @author Mahmoud Ben Hassine
67+
* @author Alexei Klenin
6768
*
6869
* @since 2.2
6970
*/
@@ -265,7 +266,6 @@ public SimpleStepBuilder<I, O> readerIsTransactionalQueue() {
265266
* @param listener the object that has a method configured with listener annotation
266267
* @return this for fluent chaining
267268
*/
268-
@SuppressWarnings("unchecked")
269269
@Override
270270
public SimpleStepBuilder<I, O> listener(Object listener) {
271271
super.listener(listener);
@@ -281,18 +281,14 @@ public SimpleStepBuilder<I, O> listener(Object listener) {
281281
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class));
282282
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class));
283283

284-
if(itemListenerMethods.size() > 0) {
285-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
286-
factory.setDelegate(listener);
287-
itemListeners.add((StepListener) factory.getObject());
284+
if (!itemListenerMethods.isEmpty()) {
285+
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
286+
itemListeners.add(stepListener);
288287
}
289288

290-
@SuppressWarnings("unchecked")
291-
SimpleStepBuilder<I, O> result = this;
292-
return result;
289+
return this;
293290
}
294291

295-
296292
/**
297293
* Register an item reader listener.
298294
*

spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import org.apache.commons.logging.Log;
1919
import org.apache.commons.logging.LogFactory;
20+
2021
import org.springframework.batch.core.Step;
2122
import org.springframework.batch.core.StepExecutionListener;
23+
import org.springframework.batch.core.StepListener;
2224
import org.springframework.batch.core.annotation.AfterStep;
2325
import org.springframework.batch.core.annotation.BeforeStep;
2426
import org.springframework.batch.core.listener.StepListenerFactoryBean;
@@ -40,6 +42,7 @@
4042
*
4143
* @author Dave Syer
4244
* @author Michael Minella
45+
* @author Alexei Klenin
4346
*
4447
* @since 2.2
4548
*/
@@ -91,14 +94,15 @@ public B startLimit(int startLimit) {
9194
* @return this for fluent chaining
9295
*/
9396
public B listener(Object listener) {
97+
StepListenerFactoryBean.assertListener(listener);
98+
9499
Set<Method> stepExecutionListenerMethods = new HashSet<>();
95100
stepExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeStep.class));
96101
stepExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterStep.class));
97102

98-
if(stepExecutionListenerMethods.size() > 0) {
99-
StepListenerFactoryBean factory = new StepListenerFactoryBean();
100-
factory.setDelegate(listener);
101-
properties.addStepExecutionListener((StepExecutionListener) factory.getObject());
103+
if (!stepExecutionListenerMethods.isEmpty()) {
104+
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
105+
properties.addStepExecutionListener((StepExecutionListener) stepListener);
102106
}
103107

104108
@SuppressWarnings("unchecked")

spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPojoStepExecutionListener.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
*/
1616
package org.springframework.batch.core.configuration.xml;
1717

18+
import org.springframework.batch.core.StepExecution;
19+
import org.springframework.batch.core.annotation.BeforeStep;
1820

1921
/**
2022
* @author Dave Syer
23+
* @author Alexei Klenin
2124
* @since 2.1.2
2225
*/
2326
public class DummyPojoStepExecutionListener extends AbstractTestComponent {
@@ -26,4 +29,8 @@ public void execute() {
2629
executed = true;
2730
}
2831

32+
@BeforeStep
33+
public void beforeStep(StepExecution execution) {
34+
}
35+
2936
}

0 commit comments

Comments
 (0)