From e4b2a0bf7da3124a63d8ea33ce2c6b5bc2dbb7c2 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Thu, 13 Mar 2014 14:52:34 -0500 Subject: [PATCH] BATCH-2185: Updated to support annotation based listener configuration via javaconfig --- .../builder/FaultTolerantStepBuilder.java | 76 ++++++-- .../core/step/builder/SimpleStepBuilder.java | 55 +++++- .../core/step/builder/StepBuilderHelper.java | 40 +++- .../core/step/builder/StepBuilderTests.java | 178 +++++++++++++++++- .../batch/support/ReflectionUtils.java | 59 ++++++ .../batch/support/ReflectionUtilsTests.java | 96 ++++++++++ 6 files changed, 477 insertions(+), 27 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ReflectionUtilsTests.java 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 6a0932301..91fa15dcd 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-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 extends SimpleStepBuilder { 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 skipListenerMethods = new HashSet(); + 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 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; + } + + /** * Register a skip listener. * @@ -608,7 +658,7 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder { } /** - * 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) { 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 f3428ce10..3f1b637a4 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 @@ -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 extends AbstractTaskletStepBuilder itemListenerMethods = new HashSet(); + 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. * 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 157b2bc4f..7263c46f2 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 @@ -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> { @@ -76,6 +84,28 @@ public abstract class StepBuilderHelper> { 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 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()); + } + + @SuppressWarnings("unchecked") + B result = (B) this; + return result; + } + public B listener(StepExecutionListener listener) { properties.addStepExecutionListener(listener); @SuppressWarnings("unchecked") 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 616e47df0..385eebca5 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-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 items = new ArrayList() {{ + add("1"); + add("2"); + add("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()); + 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++; + } + } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java new file mode 100644 index 000000000..9bf0a2167 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java @@ -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 findMethod(Class clazz, Class annotationType) { + + Method [] declaredMethods = org.springframework.util.ReflectionUtils.getAllDeclaredMethods(clazz); + Set results = new HashSet(); + + for (Method curMethod : declaredMethods) { + Annotation annotation = AnnotationUtils.findAnnotation(curMethod, annotationType); + + if(annotation != null) { + results.add(curMethod); + } + } + + return results; + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ReflectionUtilsTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ReflectionUtilsTests.java new file mode 100644 index 000000000..00f3ece6e --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ReflectionUtilsTests.java @@ -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 methods = ReflectionUtils.findMethod(AnnotatedClass.class, Transactional.class); + assertEquals(1, methods.size()); + assertEquals("toString", methods.iterator().next().getName()); + } + + @Test + public void testFindNoAnnotatedMethod() { + Set methods = ReflectionUtils.findMethod(AnnotatedClass.class, Autowired.class); + assertEquals(0, methods.size()); + } + + @Test + public void testFindAnnotatedMethodHierarchy() { + Set methods = ReflectionUtils.findMethod(AnnotatedSubClass.class, Transactional.class); + assertEquals(2, methods.size()); + + boolean toStringFound = false; + boolean methodOneFound = false; + + Iterator 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"); + } + } +}