BATCH-2185: Updated to support annotation based listener configuration via javaconfig

This commit is contained in:
Michael Minella
2014-03-13 14:52:34 -05:00
parent bc29c1ba14
commit e4b2a0bf7d
6 changed files with 477 additions and 27 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2013 the original author or authors.
* Copyright 2006-2014 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,22 +15,17 @@
*/
package org.springframework.batch.core.step.builder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.batch.operations.BatchRuntimeException;
import org.springframework.batch.core.ChunkListener;
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;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.FatalStepExecutionException;
@@ -59,6 +54,7 @@ import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.support.ReflectionUtils;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.classify.Classifier;
import org.springframework.classify.SubclassClassifier;
@@ -77,12 +73,27 @@ import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import javax.batch.operations.BatchRuntimeException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A step builder for fully fault tolerant chunk-oriented item processing steps. Extends {@link SimpleStepBuilder} with
* additional properties for retry and skip of failed items.
*
* @author Dave Syer
<<<<<<< HEAD
* @author Chris Schaefer
=======
* @author Michael Minella
>>>>>>> 2c62f20... BATCH-2185: Updated to support annotation based listener configuration via javaconfig
*
* @since 2.2
*/
@@ -180,6 +191,45 @@ public class FaultTolerantStepBuilder<I, O> extends SimpleStepBuilder<I, O> {
return tasklet;
}
/**
* 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
@SuppressWarnings("unchecked")
public SimpleStepBuilder listener(Object listener) {
super.listener(listener);
Set<Method> skipListenerMethods = new HashSet<Method>();
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInRead.class));
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class));
skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class));
Set<Method> chunkListenerMethods = new HashSet<Method>();
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;
}
/**
* Register a skip listener.
*
@@ -608,7 +658,7 @@ public class FaultTolerantStepBuilder<I, O> extends SimpleStepBuilder<I, O> {
}
/**
* Wrap the provided {@link RetryPolicy} so that it never retries explicitly non-retryable
* Wrap the provided {@link org.springframework.retry.RetryPolicy} so that it never retries explicitly non-retryable
* exceptions.
*/
private RetryPolicy getFatalExceptionAwareProxy(RetryPolicy retryPolicy) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2013 the original author or authors.
* Copyright 2006-2014 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,16 +15,21 @@
*/
package org.springframework.batch.core.step.builder;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.ItemProcessListener;
import org.springframework.batch.core.ItemReadListener;
import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.annotation.AfterProcess;
import org.springframework.batch.core.annotation.AfterRead;
import org.springframework.batch.core.annotation.AfterWrite;
import org.springframework.batch.core.annotation.BeforeProcess;
import org.springframework.batch.core.annotation.BeforeRead;
import org.springframework.batch.core.annotation.BeforeWrite;
import org.springframework.batch.core.annotation.OnProcessError;
import org.springframework.batch.core.annotation.OnReadError;
import org.springframework.batch.core.annotation.OnWriteError;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
import org.springframework.batch.core.step.item.ChunkOrientedTasklet;
import org.springframework.batch.core.step.item.SimpleChunkProcessor;
@@ -39,8 +44,15 @@ import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.support.ReflectionUtils;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Step builder for simple item processing (chunk oriented) steps. Items are read and cached in chunks, and then
* processed (transformed) and written (optionally either the processor or the writer can be omitted) all in the same
@@ -230,6 +242,39 @@ public class SimpleStepBuilder<I, O> extends AbstractTaskletStepBuilder<SimpleSt
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 SimpleStepBuilder listener(Object listener) {
super.listener(listener);
Set<Method> itemListenerMethods = new HashSet<Method>();
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeRead.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterRead.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeProcess.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterProcess.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeWrite.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterWrite.class));
itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnReadError.class));
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());
}
@SuppressWarnings("unchecked")
SimpleStepBuilder result = this;
return result;
}
/**
* Register an item reader listener.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2011 the original author or authors.
* Copyright 2006-2014 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,24 +15,32 @@
*/
package org.springframework.batch.core.step.builder;
import java.util.ArrayList;
import java.util.List;
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.annotation.AfterStep;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.support.ReflectionUtils;
import org.springframework.transaction.PlatformTransactionManager;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A base class and utility for other step builders providing access to common properties like job repository and
* transaction manager.
*
* @author Dave Syer
*
* @author Michael Minella
*
* @since 2.2
*/
public abstract class StepBuilderHelper<B extends StepBuilderHelper<B>> {
@@ -76,6 +84,28 @@ public abstract class StepBuilderHelper<B extends StepBuilderHelper<B>> {
return result;
}
/**
* 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
*/
public B listener(Object listener) {
Set<Method> stepExecutionListenerMethods = new HashSet<Method>();
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());
}
@SuppressWarnings("unchecked")
B result = (B) this;
return result;
}
public B listener(StepExecutionListener listener) {
properties.addStepExecutionListener(listener);
@SuppressWarnings("unchecked")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
@@ -16,19 +16,28 @@
package org.springframework.batch.core.step.builder;
import org.junit.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.*;
import org.springframework.batch.core.annotation.*;
import org.springframework.batch.core.configuration.xml.DummyItemWriter;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.item.support.PassThroughItemProcessor;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
* @author Michael Minella
*
*/
public class StepBuilderTests {
@@ -49,6 +58,167 @@ public class StepBuilderTests {
}
});
builder.build().execute(execution);
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
}
@Test
public void testListeners() throws Exception {
JobRepository jobRepository = new MapJobRepositoryFactoryBean().getJobRepository();
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)
.listener(new InterfaceBasedStepExecutionListener())
.listener(new AnnotationBasedStepExecutionListener())
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext)
throws Exception {
return null;
}
});
builder.build().execute(execution);
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
assertEquals(1, InterfaceBasedStepExecutionListener.beforeStepCount);
assertEquals(1, InterfaceBasedStepExecutionListener.afterStepCount);
assertEquals(1, AnnotationBasedStepExecutionListener.beforeStepCount);
assertEquals(1, AnnotationBasedStepExecutionListener.afterStepCount);
}
@Test
public void testItemListeners() throws Exception {
JobRepository jobRepository = new MapJobRepositoryFactoryBean().getJobRepository();
StepExecution execution = jobRepository.createJobExecution("foo", new JobParameters()).createStepExecution("step");
jobRepository.add(execution);
PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
List<String> items = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
ItemReader<String> reader = new ListItemReader<String>(items);
@SuppressWarnings("unchecked")
SimpleStepBuilder<String, String> builder = new StepBuilder("step")
.repository(jobRepository)
.transactionManager(transactionManager)
.<String, String>chunk(3)
.reader(reader)
.processor(new PassThroughItemProcessor<String>())
.writer(new DummyItemWriter())
.listener(new AnnotationBasedStepExecutionListener());
builder.build().execute(execution);
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
assertEquals(1, AnnotationBasedStepExecutionListener.beforeStepCount);
assertEquals(1, AnnotationBasedStepExecutionListener.afterStepCount);
assertEquals(4, AnnotationBasedStepExecutionListener.beforeReadCount);
assertEquals(3, AnnotationBasedStepExecutionListener.afterReadCount);
assertEquals(3, AnnotationBasedStepExecutionListener.beforeProcessCount);
assertEquals(3, AnnotationBasedStepExecutionListener.afterProcessCount);
assertEquals(1, AnnotationBasedStepExecutionListener.beforeWriteCount);
assertEquals(1, AnnotationBasedStepExecutionListener.afterWriteCount);
assertEquals(2, AnnotationBasedStepExecutionListener.beforeChunkCount);
assertEquals(2, AnnotationBasedStepExecutionListener.afterChunkCount);
}
public static class InterfaceBasedStepExecutionListener implements StepExecutionListener {
static int beforeStepCount = 0;
static int afterStepCount = 0;
@Override
public void beforeStep(StepExecution stepExecution) {
beforeStepCount++;
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
afterStepCount++;
return stepExecution.getExitStatus();
}
}
@SuppressWarnings("unused")
public static class AnnotationBasedStepExecutionListener {
static int beforeStepCount = 0;
static int afterStepCount = 0;
static int beforeReadCount = 0;
static int afterReadCount = 0;
static int beforeProcessCount = 0;
static int afterProcessCount = 0;
static int beforeWriteCount = 0;
static int afterWriteCount = 0;
static int beforeChunkCount = 0;
static int afterChunkCount = 0;
public AnnotationBasedStepExecutionListener() {
beforeStepCount = 0;
afterStepCount = 0;
beforeReadCount = 0;
afterReadCount = 0;
beforeProcessCount = 0;
afterProcessCount = 0;
beforeWriteCount = 0;
afterWriteCount = 0;
beforeChunkCount = 0;
afterChunkCount = 0;
}
@BeforeStep
public void beforeStep() {
beforeStepCount++;
}
@AfterStep
public ExitStatus afterStep(StepExecution stepExecution) {
afterStepCount++;
return stepExecution.getExitStatus();
}
@BeforeRead
public void beforeRead() {
beforeReadCount++;
}
@AfterRead
public void afterRead() {
afterReadCount++;
}
@BeforeProcess
public void beforeProcess() {
beforeProcessCount++;
}
@AfterProcess
public void afterProcess() {
afterProcessCount++;
}
@BeforeWrite
public void beforeWrite() {
beforeWriteCount++;
}
@AfterWrite
public void setAfterWrite() {
afterWriteCount++;
}
@BeforeChunk
public void beforeChunk() {
beforeChunkCount++;
}
@AfterChunk
public void afterChunk() {
afterChunkCount++;
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.support;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
/**
* Provides reflection based utilities for Spring Batch that are not available
* via Spring Core
*
* @author Michael Minella
* @since 2.2.6
*/
public class ReflectionUtils {
private ReflectionUtils() {}
/**
* Returns a {@link java.util.Set} of {@link java.lang.reflect.Method} instances that
* are annotated with the annotation provided.
*
* @param clazz The class to search for a method with the given annotation type
* @param annotationType The type of annotation to look for
* @return a set of {@link java.lang.reflect.Method} instances if any are found, an empty set if not.
*/
public static final Set<Method> findMethod(Class clazz, Class<? extends Annotation> annotationType) {
Method [] declaredMethods = org.springframework.util.ReflectionUtils.getAllDeclaredMethods(clazz);
Set<Method> results = new HashSet<Method>();
for (Method curMethod : declaredMethods) {
Annotation annotation = AnnotationUtils.findAnnotation(curMethod, annotationType);
if(annotation != null) {
results.add(curMethod);
}
}
return results;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.support;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Michael Minella
* @since 2.2.6
*/
public class ReflectionUtilsTests {
@Test
public void testFindAnnotatedMethod() {
Set<Method> methods = ReflectionUtils.findMethod(AnnotatedClass.class, Transactional.class);
assertEquals(1, methods.size());
assertEquals("toString", methods.iterator().next().getName());
}
@Test
public void testFindNoAnnotatedMethod() {
Set<Method> methods = ReflectionUtils.findMethod(AnnotatedClass.class, Autowired.class);
assertEquals(0, methods.size());
}
@Test
public void testFindAnnotatedMethodHierarchy() {
Set<Method> methods = ReflectionUtils.findMethod(AnnotatedSubClass.class, Transactional.class);
assertEquals(2, methods.size());
boolean toStringFound = false;
boolean methodOneFound = false;
Iterator<Method> iterator = methods.iterator();
String name = iterator.next().getName();
if(name.equals("toString")) {
toStringFound = true;
} else if(name.equals("methodOne")) {
methodOneFound = true;
}
name = iterator.next().getName();
if(name.equals("toString")) {
toStringFound = true;
} else if(name.equals("methodOne")) {
methodOneFound = true;
}
assertTrue(toStringFound && methodOneFound);
}
public static class AnnotatedClass {
public void methodOne() {
System.err.println("This is method 1");
}
@Transactional
public String toString() {
return "AnnoatedClass";
}
}
public static class AnnotatedSubClass extends AnnotatedClass {
@Transactional
public void methodOne() {
System.err.println("This is method 1 in the sub class");
}
}
}