Skip to content

Commit

Permalink
FEATURE: validate step listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
hosuaby committed May 10, 2020
1 parent 10fd371 commit cb71ecf
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,11 @@ public static boolean isListener(Object target, Class<?> listenerType, ListenerM
if (target == null) {
return false;
}

if (listenerType.isInstance(target)) {
return true;
}

if (target instanceof Advised) {
TargetSource targetSource = ((Advised) target).getTargetSource();
if (targetSource != null && targetSource.getTargetClass() != null
Expand All @@ -228,11 +230,21 @@ public static boolean isListener(Object target, Class<?> listenerType, ListenerM
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()));
}
}

for (ListenerMetaData metaData : metaDataValues) {
if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
Class<?> listenerInterface = metaData.getListenerInterface();
if (listenerInterface.isInstance(target)) {
return true;
}

if (metaData.getAnnotation() != null) {
MethodInvoker annotatedMethod = getMethodInvokerByAnnotation(metaData.getAnnotation(), target);
if (annotatedMethod != null) {
return true;
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
*/
public interface ListenerMetaData {

public String getMethodName();
String getMethodName();

public Class<? extends Annotation> getAnnotation();
Class<? extends Annotation> getAnnotation();

public Class<?> getListenerInterface();
Class<?> getListenerInterface();

public String getPropertyName();
String getPropertyName();

public Class<?>[] getParamTypes();
Class<?>[] getParamTypes();

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,34 @@
*/
package org.springframework.batch.core.listener;

import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.jsr.JsrStepListenerMetaData;

/**
* This {@link AbstractListenerFactoryBean} implementation is used to create a
* {@link StepListener}.
*
* @author Lucas Ward
* @author Dan Garrette
* @author Alexei KLENIN
* @since 2.0
* @see AbstractListenerFactoryBean
* @see StepListenerMetaData
*/
public class StepListenerFactoryBean extends AbstractListenerFactoryBean<StepListener> {
private static final String STR_STEP_LISTENER_ANNOTATIONS_LIST = Stream
.of(StepListenerMetaData.values())
.map(StepListenerMetaData::getAnnotation)
.map(Class::getSimpleName)
.map(annotation -> String.format("\t- @%s\n", annotation))
.collect(Collectors.joining());
private static final String ERR_OBJECT_NOT_STEP_LISTENER_TEMPLATE =
"Object of type [%s] is not a valid step listener. " +
"It must ether implement StepListener interface or have methods annotated with any of:\n" +
STR_STEP_LISTENER_ANNOTATIONS_LIST;

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

@Override
public void setDelegate(Object delegate) {
super.setDelegate(assertListener(delegate));
}

/**
* Convenience method to wrap any object and expose the appropriate
* {@link StepListener} interfaces.
Expand All @@ -72,6 +92,30 @@ public static StepListener getListener(Object delegate) {
* annotations
*/
public static boolean isListener(Object delegate) {
return AbstractListenerFactoryBean.isListener(delegate, StepListener.class, StepListenerMetaData.values());
ListenerMetaData[] metaDataValues = Stream
.<ListenerMetaData> concat(
Stream.of(StepListenerMetaData.values()),
Stream.of(JsrStepListenerMetaData.values()))
.toArray(ListenerMetaData[]::new);

return AbstractListenerFactoryBean.isListener(delegate, StepListener.class, metaDataValues);
}

/**
* Asserts that {@code delegate} is a valid step listener. If this is not a case, throws an
* {@link IllegalArgumentException} with message detailing the problem. Object is a valid
* step listener is it ether implements interface {@link StepListener} or has any method
* annotated with one of marker annotations.
*
* @param delegate the object to check
* @return valid step execution listener
*/
public static Object assertListener(Object delegate) {
if (!isListener(delegate)) {
throw new IllegalArgumentException(String.format(
ERR_OBJECT_NOT_STEP_LISTENER_TEMPLATE, delegate.getClass().getName()));
}

return delegate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.annotation.AfterChunk;
import org.springframework.batch.core.annotation.AfterChunkError;
import org.springframework.batch.core.annotation.BeforeChunk;
Expand All @@ -47,6 +48,7 @@
* @author Dave Syer
* @author Michael Minella
* @author Mahmoud Ben Hassine
* @author Alexei Klenin
*
* @since 2.2
*
Expand Down Expand Up @@ -159,10 +161,9 @@ public B listener(Object listener) {
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class));
chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunkError.class));

if(chunkListenerMethods.size() > 0) {
StepListenerFactoryBean factory = new StepListenerFactoryBean();
factory.setDelegate(listener);
this.listener((ChunkListener) factory.getObject());
if (!chunkListenerMethods.isEmpty()) {
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
this.listener((ChunkListener) stepListener);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
* @author Chris Schaefer
* @author Michael Minella
* @author Mahmoud Ben Hassine
* @author Alexei Klenin
*
* @since 2.2
*/
Expand Down Expand Up @@ -204,18 +205,14 @@ public SimpleStepBuilder<I, O> listener(Object listener) {
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class));
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class));

if(skipListenerMethods.size() > 0) {
StepListenerFactoryBean factory = new StepListenerFactoryBean();
factory.setDelegate(listener);
skipListeners.add((SkipListener) factory.getObject());
if (!skipListenerMethods.isEmpty()) {
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
skipListeners.add((SkipListener<I, O>) stepListener);
}

@SuppressWarnings("unchecked")
SimpleStepBuilder<I, O> result = this;
return result;
return this;
}


/**
* Register a skip listener.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @author Alexei Klenin
*
* @since 2.2
*/
Expand Down Expand Up @@ -265,7 +266,6 @@ public SimpleStepBuilder<I, O> readerIsTransactionalQueue() {
* @param listener the object that has a method configured with listener annotation
* @return this for fluent chaining
*/
@SuppressWarnings("unchecked")
@Override
public SimpleStepBuilder<I, O> listener(Object listener) {
super.listener(listener);
Expand All @@ -281,18 +281,14 @@ public SimpleStepBuilder<I, O> listener(Object listener) {
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class));

if(itemListenerMethods.size() > 0) {
StepListenerFactoryBean factory = new StepListenerFactoryBean();
factory.setDelegate(listener);
itemListeners.add((StepListener) factory.getObject());
if (!itemListenerMethods.isEmpty()) {
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
itemListeners.add(stepListener);
}

@SuppressWarnings("unchecked")
SimpleStepBuilder<I, O> result = this;
return result;
return this;
}


/**
* Register an item reader listener.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.annotation.AfterStep;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
Expand All @@ -40,6 +42,7 @@
*
* @author Dave Syer
* @author Michael Minella
* @author Alexei Klenin
*
* @since 2.2
*/
Expand Down Expand Up @@ -91,14 +94,15 @@ public B startLimit(int startLimit) {
* @return this for fluent chaining
*/
public B listener(Object listener) {
StepListenerFactoryBean.assertListener(listener);

Set<Method> stepExecutionListenerMethods = new HashSet<>();
stepExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeStep.class));
stepExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterStep.class));

if(stepExecutionListenerMethods.size() > 0) {
StepListenerFactoryBean factory = new StepListenerFactoryBean();
factory.setDelegate(listener);
properties.addStepExecutionListener((StepExecutionListener) factory.getObject());
if (!stepExecutionListenerMethods.isEmpty()) {
StepListener stepListener = StepListenerFactoryBean.getListener(listener);
properties.addStepExecutionListener((StepExecutionListener) stepListener);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*/
package org.springframework.batch.core.configuration.xml;

import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;

/**
* @author Dave Syer
* @author Alexei Klenin
* @since 2.1.2
*/
public class DummyPojoStepExecutionListener extends AbstractTestComponent {
Expand All @@ -26,4 +29,8 @@ public void execute() {
executed = true;
}

@BeforeStep
public void beforeStep(StepExecution execution) {
}

}
Loading

0 comments on commit cb71ecf

Please sign in to comment.