diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java index 39a8568c47..b1bc6f40f2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java @@ -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 @@ -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; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java index 56f14b1b31..00762216aa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ListenerMetaData.java @@ -27,14 +27,14 @@ */ public interface ListenerMetaData { - public String getMethodName(); + String getMethodName(); - public Class getAnnotation(); + Class getAnnotation(); - public Class getListenerInterface(); + Class getListenerInterface(); - public String getPropertyName(); + String getPropertyName(); - public Class[] getParamTypes(); + Class[] getParamTypes(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java index bd96859460..ef105d7037 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java @@ -15,7 +15,11 @@ */ 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 @@ -23,11 +27,22 @@ * * @author Lucas Ward * @author Dan Garrette + * @author Alexei KLENIN * @since 2.0 * @see AbstractListenerFactoryBean * @see StepListenerMetaData */ public class StepListenerFactoryBean extends AbstractListenerFactoryBean { + 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) { @@ -49,6 +64,11 @@ public Class 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. @@ -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 + . 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; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java index d3288e8a8a..31ea7765d4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java @@ -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; @@ -47,6 +48,7 @@ * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Alexei Klenin * * @since 2.2 * @@ -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") diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index be7fad7388..a3f57ee523 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -91,6 +91,7 @@ * @author Chris Schaefer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Alexei Klenin * * @since 2.2 */ @@ -204,18 +205,14 @@ public SimpleStepBuilder 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) stepListener); } - @SuppressWarnings("unchecked") - SimpleStepBuilder result = this; - return result; + return this; } - /** * Register a skip listener. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java index 5eb78db585..a1ec402825 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java @@ -64,6 +64,7 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Alexei Klenin * * @since 2.2 */ @@ -265,7 +266,6 @@ public SimpleStepBuilder readerIsTransactionalQueue() { * @param listener the object that has a method configured with listener annotation * @return this for fluent chaining */ - @SuppressWarnings("unchecked") @Override public SimpleStepBuilder listener(Object listener) { super.listener(listener); @@ -281,18 +281,14 @@ public SimpleStepBuilder 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 result = this; - return result; + return this; } - /** * Register an item reader listener. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java index 5b6fb190e4..333de270ed 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java @@ -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; @@ -40,6 +42,7 @@ * * @author Dave Syer * @author Michael Minella + * @author Alexei Klenin * * @since 2.2 */ @@ -91,14 +94,15 @@ public B startLimit(int startLimit) { * @return this for fluent chaining */ public B listener(Object listener) { + StepListenerFactoryBean.assertListener(listener); + Set 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") diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPojoStepExecutionListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPojoStepExecutionListener.java index 0caf866e74..f199aa1ab0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPojoStepExecutionListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPojoStepExecutionListener.java @@ -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 { @@ -26,4 +29,8 @@ public void execute() { executed = true; } + @BeforeStep + public void beforeStep(StepExecution execution) { + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java index 9bd1a3dd7e..b2df37f822 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java @@ -22,7 +22,6 @@ import javax.sql.DataSource; import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; import org.junit.Before; import org.junit.Test; @@ -58,15 +57,18 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_STEP; import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_WRITE; /** * @author Lucas Ward - * + * @author Alexei Klenin */ public class StepListenerFactoryBeanTests { @@ -83,13 +85,9 @@ public void setUp() { @Test @SuppressWarnings("unchecked") - public void testStepAndChunk() throws Exception { + public void testStepAndChunk() { TestListener testListener = new TestListener(); factoryBean.setDelegate(testListener); - // Map metaDataMap = new HashMap(); - // metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy"); - // metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk"); - // factoryBean.setMetaDataMap(metaDataMap); String readItem = "item"; Integer writeItem = 2; List writeItems = Arrays.asList(writeItem); @@ -129,11 +127,13 @@ public void testStepAndChunk() throws Exception { assertTrue(testListener.onSkipInWriteCalled); } + /** + * Test to make sure if someone has annotated a method, implemented the + * interface, and given a string + * method name, that all three will be called + */ @Test - public void testAllThreeTypes() throws Exception { - // Test to make sure if someone has annotated a method, implemented the - // interface, and given a string - // method name, that all three will be called + public void testAllThreeTypes() { ThreeStepExecutionListener delegate = new ThreeStepExecutionListener(); factoryBean.setDelegate(delegate); Map metaDataMap = new HashMap<>(); @@ -145,7 +145,7 @@ public void testAllThreeTypes() throws Exception { } @Test - public void testAnnotatingInterfaceResultsInOneCall() throws Exception { + public void testAnnotatingInterfaceResultsInOneCall() { MultipleAfterStep delegate = new MultipleAfterStep(); factoryBean.setDelegate(delegate); Map metaDataMap = new HashMap<>(); @@ -157,7 +157,7 @@ public void testAnnotatingInterfaceResultsInOneCall() throws Exception { } @Test - public void testVanillaInterface() throws Exception { + public void testVanillaInterface() { MultipleAfterStep delegate = new MultipleAfterStep(); factoryBean.setDelegate(delegate); Object listener = factoryBean.getObject(); @@ -167,7 +167,7 @@ public void testVanillaInterface() throws Exception { } @Test - public void testVanillaInterfaceWithProxy() throws Exception { + public void testVanillaInterfaceWithProxy() { MultipleAfterStep delegate = new MultipleAfterStep(); ProxyFactory factory = new ProxyFactory(delegate); factoryBean.setDelegate(factory.getProxy()); @@ -178,7 +178,7 @@ public void testVanillaInterfaceWithProxy() throws Exception { } @Test - public void testFactoryMethod() throws Exception { + public void testFactoryMethod() { MultipleAfterStep delegate = new MultipleAfterStep(); Object listener = StepListenerFactoryBean.getListener(delegate); assertTrue(listener instanceof StepExecutionListener); @@ -188,7 +188,7 @@ public void testFactoryMethod() throws Exception { } @Test - public void testAnnotationsWithOrdered() throws Exception { + public void testAnnotationsWithOrdered() { Object delegate = new Ordered() { @BeforeStep public void foo(StepExecution execution) { @@ -205,14 +205,14 @@ public int getOrder() { } @Test - public void testProxiedAnnotationsFactoryMethod() throws Exception { + public void testProxiedAnnotationsFactoryMethod() { Object delegate = new InitializingBean() { @BeforeStep public void foo(StepExecution execution) { } @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { } }; ProxyFactory factory = new ProxyFactory(delegate); @@ -221,12 +221,12 @@ public void afterPropertiesSet() throws Exception { } @Test - public void testInterfaceIsListener() throws Exception { + public void testInterfaceIsListener() { assertTrue(StepListenerFactoryBean.isListener(new ThreeStepExecutionListener())); } @Test - public void testAnnotationsIsListener() throws Exception { + public void testAnnotationsIsListener() { assertTrue(StepListenerFactoryBean.isListener(new Object() { @BeforeStep public void foo(StepExecution execution) { @@ -235,28 +235,23 @@ public void foo(StepExecution execution) { } @Test - public void testProxyWithNoTarget() throws Exception { + public void testProxyWithNoTarget() { ProxyFactory factory = new ProxyFactory(); factory.addInterface(DataSource.class); - factory.addAdvice(new MethodInterceptor() { - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - return null; - } - }); + factory.addAdvice((MethodInterceptor) invocation -> null); Object proxy = factory.getProxy(); assertFalse(StepListenerFactoryBean.isListener(proxy)); } @Test - public void testProxiedAnnotationsIsListener() throws Exception { + public void testProxiedAnnotationsIsListener() { Object delegate = new InitializingBean() { @BeforeStep public void foo(StepExecution execution) { } @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { } }; ProxyFactory factory = new ProxyFactory(delegate); @@ -266,15 +261,18 @@ public void afterPropertiesSet() throws Exception { } @Test - public void testMixedIsListener() throws Exception { + public void testMixedIsListener() { assertTrue(StepListenerFactoryBean.isListener(new MultipleAfterStep())); } @Test - public void testNonListener() throws Exception { + public void testNonListener() { Object delegate = new Object(); - factoryBean.setDelegate(delegate); - assertTrue(factoryBean.getObject() instanceof StepListener); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, () -> factoryBean.setDelegate(delegate)); + assertThat( + exception.getMessage(), + startsWith("Object of type [java.lang.Object] is not a valid step listener.")); } @Test @@ -328,6 +326,10 @@ public void testEmptySignatureNamedMethod() { public void aMethod() { executed = true; } + + @BeforeStep + public void beforeStep(StepExecution execution) { + } }; factoryBean.setDelegate(delegate); Map metaDataMap = new HashMap<>(); @@ -348,6 +350,10 @@ public void aMethod(List items) { assertEquals("foo", items.get(0)); assertEquals("bar", items.get(1)); } + + @BeforeStep + public void beforeStep(StepExecution execution) { + } }; factoryBean.setDelegate(delegate); Map metaDataMap = new HashMap<>(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java index 81f2f073d0..f81603befe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java @@ -15,10 +15,17 @@ */ package org.springframework.batch.core.step.builder; -import java.util.ArrayList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import java.util.Arrays; import java.util.List; import java.util.function.Function; +import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.BatchStatus; @@ -50,43 +57,51 @@ import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; -import static org.junit.Assert.assertEquals; - /** * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine - * + * @author Alexei Klenin */ -@SuppressWarnings("serial") public class StepBuilderTests { + private final PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + private JobRepository jobRepository; + private StepExecution execution; + + @Before + public void init() { + try { + jobRepository = new MapJobRepositoryFactoryBean().getObject(); + execution = jobRepository + .createJobExecution("foo", new JobParameters()) + .createStepExecution("step"); + jobRepository.add(execution); + } catch (Exception repositoryException) { + fail(repositoryException.getMessage()); + } + } @Test - public void test() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution( - "step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); - TaskletStepBuilder builder = new StepBuilder("step").repository(jobRepository) - .transactionManager(transactionManager).tasklet((contribution, chunkContext) -> null); + public void testStepBuilder() throws Exception { + TaskletStepBuilder builder = new StepBuilder("step") + .repository(jobRepository) + .transactionManager(transactionManager) + .tasklet((contribution, chunkContext) -> null); builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); } @Test - public void testListeners() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + public void testStepBuilderWithListeners() throws Exception { TaskletStepBuilder builder = new StepBuilder("step") - .repository(jobRepository) - .transactionManager(transactionManager) - .listener(new InterfaceBasedStepExecutionListener()) - .listener(new AnnotationBasedStepExecutionListener()) - .tasklet((contribution, chunkContext) -> null); + .repository(jobRepository) + .transactionManager(transactionManager) + .listener(new InterfaceBasedStepExecutionListener()) + .listener(new AnnotationBasedStepExecutionListener()) + .tasklet((contribution, chunkContext) -> null); builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(1, InterfaceBasedStepExecutionListener.beforeStepCount); assertEquals(1, InterfaceBasedStepExecutionListener.afterStepCount); @@ -98,16 +113,13 @@ public void testListeners() throws Exception { @Test public void testAnnotationBasedChunkListenerForTaskletStep() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); TaskletStepBuilder builder = new StepBuilder("step") .repository(jobRepository) .transactionManager(transactionManager) .tasklet((contribution, chunkContext) -> null) .listener(new AnnotationBasedChunkListener()); builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(1, AnnotationBasedChunkListener.beforeChunkCount); assertEquals(1, AnnotationBasedChunkListener.afterChunkCount); @@ -115,10 +127,6 @@ public void testAnnotationBasedChunkListenerForTaskletStep() throws Exception { @Test public void testAnnotationBasedChunkListenerForSimpleTaskletStep() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); SimpleStepBuilder builder = new StepBuilder("step") .repository(jobRepository) .transactionManager(transactionManager) @@ -127,6 +135,7 @@ public void testAnnotationBasedChunkListenerForSimpleTaskletStep() throws Except .writer(new DummyItemWriter()) .listener(new AnnotationBasedChunkListener()); builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(1, AnnotationBasedChunkListener.beforeChunkCount); assertEquals(1, AnnotationBasedChunkListener.afterChunkCount); @@ -134,10 +143,6 @@ public void testAnnotationBasedChunkListenerForSimpleTaskletStep() throws Except @Test public void testAnnotationBasedChunkListenerForFaultTolerantTaskletStep() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); SimpleStepBuilder builder = new StepBuilder("step") .repository(jobRepository) .transactionManager(transactionManager) @@ -146,6 +151,7 @@ public void testAnnotationBasedChunkListenerForFaultTolerantTaskletStep() throws .writer(new DummyItemWriter()) .faultTolerant() .listener(new AnnotationBasedChunkListener()); // TODO should this return FaultTolerantStepBuilder? + builder.build().execute(execution); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(1, AnnotationBasedChunkListener.beforeChunkCount); @@ -154,12 +160,9 @@ public void testAnnotationBasedChunkListenerForFaultTolerantTaskletStep() throws @Test public void testAnnotationBasedChunkListenerForJobStepBuilder() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); SimpleJob job = new SimpleJob("job"); job.setJobRepository(jobRepository); + JobStepBuilder builder = new StepBuilder("step") .repository(jobRepository) .transactionManager(transactionManager) @@ -174,29 +177,32 @@ public void testAnnotationBasedChunkListenerForJobStepBuilder() throws Exception } @Test - public void testItemListeners() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + public void testStepBuilderAssertsListeners() { + TaskletStepBuilder builder = new StepBuilder("step") + .repository(jobRepository) + .transactionManager(transactionManager) + .tasklet((contribution, chunkContext) -> null); - List items = new ArrayList() {{ - add("1"); - add("2"); - add("3"); - }}; + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, () -> builder.listener(new Object())); + assertThat( + exception.getMessage(), + startsWith("Object of type [java.lang.Object] is not a valid step listener.")); + } + @Test + public void testItemListeners() throws Exception { + List items = Arrays.asList("1", "2", "3"); ItemReader reader = new ListItemReader<>(items); - @SuppressWarnings("unchecked") SimpleStepBuilder builder = new StepBuilder("step") - .repository(jobRepository) - .transactionManager(transactionManager) - .chunk(3) - .reader(reader) - .processor(new PassThroughItemProcessor<>()) - .writer(new DummyItemWriter()) - .listener(new AnnotationBasedStepExecutionListener()); + .repository(jobRepository) + .transactionManager(transactionManager) + .chunk(3) + .reader(reader) + .processor(new PassThroughItemProcessor<>()) + .writer(new DummyItemWriter()) + .listener(new AnnotationBasedStepExecutionListener()); builder.build().execute(execution); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); @@ -214,29 +220,18 @@ public void testItemListeners() throws Exception { @Test public void testFunctions() throws Exception { - JobRepository jobRepository = new MapJobRepositoryFactoryBean().getObject(); - StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step"); - jobRepository.add(execution); - PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); - - List items = new ArrayList() {{ - add(1L); - add(2L); - add(3L); - }}; - + List items = Arrays.asList(1L, 2L, 3L); ItemReader reader = new ListItemReader<>(items); - ListItemWriter itemWriter = new ListItemWriter<>(); - @SuppressWarnings("unchecked") + SimpleStepBuilder builder = new StepBuilder("step") - .repository(jobRepository) - .transactionManager(transactionManager) - .chunk(3) - .reader(reader) - .processor((Function) s -> s.toString()) - .writer(itemWriter) - .listener(new AnnotationBasedStepExecutionListener()); + .repository(jobRepository) + .transactionManager(transactionManager) + .chunk(3) + .reader(reader) + .processor((Function) Object::toString) + .writer(itemWriter) + .listener(new AnnotationBasedStepExecutionListener()); builder.build().execute(execution); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java index 803bc3f150..28144060c9 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://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, @@ -18,6 +18,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; @@ -25,18 +27,24 @@ public class LoggingTasklet implements Tasklet { - protected static final Log logger = LogFactory.getLog(LoggingTasklet.class); + protected static final Log logger = LogFactory.getLog(LoggingTasklet.class); - private int id = 0; + private int id = 0; - public LoggingTasklet(int id) { - this.id = id; - } + public LoggingTasklet(int id) { + this.id = id; + } - @Nullable + @Nullable @Override - public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { - logger.info("tasklet executing: id=" + id); - return RepeatStatus.FINISHED; - } + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + logger.info("tasklet executing: id=" + id); + return RepeatStatus.FINISHED; + } + + @BeforeStep + public void storeJobExecution(StepExecution stepExecution) { + // DO NOTHING + } + }