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 a9ff578ad..606486926 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2018 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. @@ -15,12 +15,18 @@ */ package org.springframework.batch.core.step.builder; +import java.lang.reflect.Method; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import org.springframework.batch.core.ChunkListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.annotation.AfterChunk; +import org.springframework.batch.core.annotation.AfterChunkError; +import org.springframework.batch.core.annotation.BeforeChunk; +import org.springframework.batch.core.listener.StepListenerFactoryBean; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemStream; @@ -29,6 +35,7 @@ import org.springframework.batch.repeat.exception.DefaultExceptionHandler; import org.springframework.batch.repeat.exception.ExceptionHandler; import org.springframework.batch.repeat.support.RepeatTemplate; import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate; +import org.springframework.batch.support.ReflectionUtils; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.transaction.interceptor.TransactionAttribute; @@ -39,6 +46,7 @@ import org.springframework.transaction.interceptor.TransactionAttribute; * * @author Dave Syer * @author Michael Minella + * @author Mahmoud Ben Hassine * * @since 2.2 * @@ -136,6 +144,32 @@ StepBuilderHelper> { return this; } + /** + * Registers objects using the annotation based listener configuration. + * + * @param listener the object that has a method configured with listener annotation + * @return this for fluent chaining + */ + @Override + public B listener(Object listener) { + super.listener(listener); + + Set chunkListenerMethods = new HashSet<>(); + chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeChunk.class)); + 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()); + } + + @SuppressWarnings("unchecked") + B result = (B) this; + return result; + } + /** * Register a stream for callbacks that manage restart data. * 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 f56ae1559..cfe440fa0 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2014 the original author or authors. + * Copyright 2006-2018 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. @@ -32,9 +32,6 @@ import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.SkipListener; 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; import org.springframework.batch.core.annotation.OnSkipInProcess; import org.springframework.batch.core.annotation.OnSkipInRead; import org.springframework.batch.core.annotation.OnSkipInWrite; @@ -93,6 +90,7 @@ import org.springframework.util.Assert; * @author Dave Syer * @author Chris Schaefer * @author Michael Minella + * @author Mahmoud Ben Hassine * * @since 2.2 */ @@ -206,23 +204,12 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder { skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class)); skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class)); - Set chunkListenerMethods = new HashSet(); - chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeChunk.class)); - chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class)); - chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunkError.class)); - if(skipListenerMethods.size() > 0) { StepListenerFactoryBean factory = new StepListenerFactoryBean(); factory.setDelegate(listener); skipListeners.add((SkipListener) factory.getObject()); } - if(chunkListenerMethods.size() > 0) { - StepListenerFactoryBean factory = new StepListenerFactoryBean(); - factory.setDelegate(listener); - super.listener(new TerminateOnExceptionChunkListenerDelegate((ChunkListener) factory.getObject())); - } - @SuppressWarnings("unchecked") SimpleStepBuilder result = this; return result; 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 1cf6e2fb6..05db1b3a4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2018 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. @@ -27,6 +27,7 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.annotation.AfterChunk; +import org.springframework.batch.core.annotation.AfterChunkError; import org.springframework.batch.core.annotation.AfterProcess; import org.springframework.batch.core.annotation.AfterRead; import org.springframework.batch.core.annotation.AfterStep; @@ -36,7 +37,9 @@ import org.springframework.batch.core.annotation.BeforeProcess; import org.springframework.batch.core.annotation.BeforeRead; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.annotation.BeforeWrite; +import org.springframework.batch.core.configuration.xml.DummyItemReader; import org.springframework.batch.core.configuration.xml.DummyItemWriter; +import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; import org.springframework.batch.item.ItemReader; @@ -51,6 +54,7 @@ import static org.junit.Assert.assertEquals; /** * @author Dave Syer * @author Michael Minella + * @author Mahmoud Ben Hassine * */ @SuppressWarnings("serial") @@ -87,6 +91,85 @@ public class StepBuilderTests { assertEquals(1, InterfaceBasedStepExecutionListener.afterStepCount); assertEquals(1, AnnotationBasedStepExecutionListener.beforeStepCount); assertEquals(1, AnnotationBasedStepExecutionListener.afterStepCount); + assertEquals(1, AnnotationBasedStepExecutionListener.beforeChunkCount); + assertEquals(1, AnnotationBasedStepExecutionListener.afterChunkCount); + } + + @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); + } + + @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) + .chunk(5) + .reader(new DummyItemReader()) + .writer(new DummyItemWriter()) + .listener(new AnnotationBasedChunkListener()); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, AnnotationBasedChunkListener.beforeChunkCount); + assertEquals(1, AnnotationBasedChunkListener.afterChunkCount); + } + + @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) + .chunk(5) + .reader(new DummyItemReader()) + .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); + assertEquals(1, AnnotationBasedChunkListener.afterChunkCount); + } + + @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) + .job(job) + .listener(new AnnotationBasedChunkListener()); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + + // it makes no sense to register a ChunkListener on a step which is not of type tasklet, so it should not be invoked + assertEquals(0, AnnotationBasedChunkListener.beforeChunkCount); + assertEquals(0, AnnotationBasedChunkListener.afterChunkCount); } @Test @@ -258,4 +341,32 @@ public class StepBuilderTests { afterChunkCount++; } } + + public static class AnnotationBasedChunkListener { + + static int beforeChunkCount = 0; + static int afterChunkCount = 0; + static int afterChunkErrorCount = 0; + + public AnnotationBasedChunkListener() { + beforeChunkCount = 0; + afterChunkCount = 0; + afterChunkErrorCount = 0; + } + + @BeforeChunk + public void beforeChunk() { + beforeChunkCount++; + } + + @AfterChunk + public void afterChunk() { + afterChunkCount++; + } + + @AfterChunkError + public void afterChunkError() { + afterChunkErrorCount++; + } + } }