From 8bd116006cf37a749f600c76803f0d5e3c0788c8 Mon Sep 17 00:00:00 2001 From: dsyer Date: Wed, 25 Feb 2009 16:03:39 +0000 Subject: [PATCH] RESOLVED - issue BATCH-1102: Classes with "listener" annotations should be auto-registered Added convenience methods to *ListenerFactoryBean and then used in *StepFactoryBean --- archetypes/simple-cli/pom.xml | 2 +- .../src/main/resources/META-INF/MANIFEST.MF | Bin 1728 -> 12298 bytes .../batch/core/ExitStatus.java | 3 +- .../core/listener/JobListenerFactoryBean.java | 127 +++++++++++------ .../MethodInvokerMethodInterceptor.java | 54 +++++--- .../listener/StepListenerFactoryBean.java | 130 ++++++++++++------ .../core/step/item/SimpleStepFactoryBean.java | 63 ++++++--- .../listener/JobListenerFactoryBeanTests.java | 103 ++++++++++---- .../StepListenerFactoryBeanTests.java | 97 +++++++++---- .../src/main/resources/jobs/skipSampleJob.xml | 1 - .../sample/common/CustomItemReaderTests.java | 15 +- .../sample/common/StagingItemReaderTests.java | 2 +- .../support/AbstractRowMapperTests.java | 29 ++-- 13 files changed, 427 insertions(+), 199 deletions(-) diff --git a/archetypes/simple-cli/pom.xml b/archetypes/simple-cli/pom.xml index 29f9c251f..0709ee244 100644 --- a/archetypes/simple-cli/pom.xml +++ b/archetypes/simple-cli/pom.xml @@ -138,9 +138,9 @@ com.springsource.bundlor com.springsource.bundlor.maven + 1.0.0.BUILD-SNAPSHOT ${project.basedir}/src/main/resources/META-INF/MANIFEST.MF - ${project.basedir}/template.mf diff --git a/archetypes/simple-cli/src/main/resources/META-INF/MANIFEST.MF b/archetypes/simple-cli/src/main/resources/META-INF/MANIFEST.MF index 4bc8285711aaf658d76214b41a1f41102d5e3fdb..bdd712f9e68a2ef60c65f7d3da3030d470d77cee 100644 GIT binary patch literal 12298 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%0A<~o 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 M(1&jp-?#k#0W}$K;Q#;t literal 1728 zcmb7^!H(K65QguO@(xyRD-ny`mP0`t0Ik%m$`*;LmmaDi8G^xyQ`@2N_8kWjARA!! z6e;$6p803ypF$>XGn62n(WvCrnY7Foi^V51eAK$=1pv+p89 zHqy}w?LNmkVj{tq9PFykxbL}hu|<8N&m`^&QwF*N7bm0*$NN5u&A$2(hu`9jOQpxV zf4X0MZ=w+_#^t?H4)iLU!WJ{%dr5PpZ8(Ey0|_O&hAr&4`71k0CmhPVJW^?@GQ))$ z(4(%-VetErf9Bl|d*WSJF?$FG8jX^LbutW9?!M59kt#_@kqZ{dMl9ku+(S^lZc5>n znpFI!^oB2-f|QI#zE+E~@G^%E`&7pgC*e23jm4Y`i|H*05s1^8!#hGR;)p}(Bantm zh^*54i-&u>S7(1+MXHB5B-}}nNM#mxmYX7XZG1P>aq8|gbt(nEX!R2fQfr39X|%ct zi{X4`u7QZtoS7zQ9WSD?_qU29&Xo~t#l>L3s6t2BfD)&*-aExwO&zBGlC}pRamrn} zR`MN#T3XyGu919k|0&n&qbR$xAqY9nI!nfBXyvg+bFF24_#5S9y5N?Q77n zWYoNzqqfGsMH8o@-?YV}pS61yEj@ASy>GpA+_=`WY6Pg4#JP8+8j+xG$f-xp9nYon HJ>UBe1x`vU diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java b/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java index 19f9b8de0..29e55b888 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java @@ -17,12 +17,11 @@ package org.springframework.batch.core; import java.io.Serializable; -import org.springframework.batch.repeat.RepeatOperations; import org.springframework.util.StringUtils; /** * Value object used to carry information about the status of a - * {@link RepeatOperations}. + * job or step execution. * * ExitStatus is immutable and therefore thread-safe. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java index 10c81476f..e2bdbfd4b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java @@ -35,80 +35,88 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** - * {@link FactoryBean} implementation that builds a {@link JobExecutionListener} based on the - * various lifecycle methods or annotations that are provided. There are three possible ways of having - * a method called as part of a {@link JobExecutionListener} lifecyle: + * {@link FactoryBean} implementation that builds a {@link JobExecutionListener} + * based on the various lifecycle methods or annotations that are provided. + * There are three possible ways of having a method called as part of a + * {@link JobExecutionListener} lifecyle: * *
    - *
  • Interface implementation: By implementing JobExecutionListener, methods on said - * interface will be called. - *
  • Annotations: Annotating a method will result in registration. - *
  • String name of the method to be called, which is tied to {@link JobListenerMetaData} in the - * metaDatMap. - *
+ *
  • Interface implementation: By implementing JobExecutionListener, methods + * on said interface will be called. + *
  • Annotations: Annotating a method will result in registration. + *
  • String name of the method to be called, which is tied to + * {@link JobListenerMetaData} in the metaDatMap. + * * - * It should be noted that methods obtained by name or annotation that don't match the listener method - * signatures to which they belong, will cause errors. However, it is acceptable to have no parameters at all. - * If the same method is marked in more than one way. (i.e. the method name is given and it's annotated) the - * method will only be called once. However, if the same class has multiple methods tied to a particular - * listener, each method will be called. + * It should be noted that methods obtained by name or annotation that don't + * match the listener method signatures to which they belong, will cause errors. + * However, it is acceptable to have no parameters at all. If the same method is + * marked in more than one way. (i.e. the method name is given and it's + * annotated) the method will only be called once. However, if the same class + * has multiple methods tied to a particular listener, each method will be + * called. * * @author Lucas Ward * @since 2.0 * @see JobListenerMetaData */ -public class JobListenerFactoryBean implements FactoryBean, InitializingBean{ +public class JobListenerFactoryBean implements FactoryBean, InitializingBean { private Object delegate; + private Map metaDataMap; - + public void setDelegate(Object delegate) { this.delegate = delegate; } - + public void setMetaDataMap(Map metaDataMap) { this.metaDataMap = metaDataMap; } - - public Object getObject() throws Exception { + + public Object getObject() { Map> invokerMap = new HashMap>(); - if(metaDataMap == null){ + if (metaDataMap == null) { metaDataMap = new HashMap(); } - //Because all annotations and interfaces should be checked for, make sure that each meta data - //entry is represented. - for(JobListenerMetaData metaData : JobListenerMetaData.values()){ - if(!metaDataMap.containsKey(metaData.getPropertyName())){ - //put null so that the annotation and interface is checked + // Because all annotations and interfaces should be checked for, make + // sure that each meta data + // entry is represented. + for (JobListenerMetaData metaData : JobListenerMetaData.values()) { + if (!metaDataMap.containsKey(metaData.getPropertyName())) { + // put null so that the annotation and interface is checked metaDataMap.put(metaData.getPropertyName(), null); } } - - //For every entry in th emap, try and find a method by interface, name, or annotation. If the same - for(Entry entry : metaDataMap.entrySet()){ + + // For every entry in the map, try and find a method by interface, name, + // or annotation. If the same + for (Entry entry : metaDataMap.entrySet()) { JobListenerMetaData metaData = JobListenerMetaData.fromPropertyName(entry.getKey()); Set invokers = new NullIgnoringSet(); invokers.add(getMethodInvokerByName(entry.getValue(), delegate, JobExecution.class)); - invokers.add(getMethodInvokerForInterface(JobExecutionListener.class, metaData.getMethodName(), - delegate, JobExecution.class)); + invokers.add(getMethodInvokerForInterface(JobExecutionListener.class, metaData.getMethodName(), delegate, + JobExecution.class)); invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate)); - if(!invokers.isEmpty()){ + if (!invokers.isEmpty()) { invokerMap.put(metaData.getMethodName(), invokers); } } - - //create a proxy listener for only the interfaces that have methods to be called + + // create a proxy listener for only the interfaces that have methods to + // be called ProxyFactory proxyFactory = new ProxyFactory(); - proxyFactory.setInterfaces(new Class[]{JobExecutionListener.class}); + proxyFactory.setTarget(delegate); + proxyFactory.setInterfaces(new Class[] { JobExecutionListener.class }); proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap))); return proxyFactory.getProxy(); } - - private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class... params){ - if(methodName != null){ + + private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class... params) { + if (methodName != null) { return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params); } - else{ + else { return null; } } @@ -125,19 +133,52 @@ public class JobListenerFactoryBean implements FactoryBean, InitializingBean{ public void afterPropertiesSet() throws Exception { Assert.notNull(delegate, "Delegate listener must not be null"); } - + + /** + * Convenience method to wrap any object and expose the appropriate + * {@link JobExecutionListener} interfaces. + * @param delegate a delegate object + * @return a JobListener instance constructed from the delegate + */ + public static JobExecutionListener getListener(Object delegate) { + JobListenerFactoryBean factory = new JobListenerFactoryBean(); + factory.setDelegate(delegate); + return (JobExecutionListener) factory.getObject(); + } + + /** + * Convenience method to check whether the given object is or can be made + * into a {@link JobExecutionListener}. + * @param delegate the object to check + * @return true if the delegate is an instance of + * {@link JobExecutionListener}, or contains the marker annotations + */ + public static boolean isListener(Object delegate) { + if (delegate instanceof JobExecutionListener) { + return true; + } + for (JobListenerMetaData metaData : JobListenerMetaData.values()) { + Set invokers = new NullIgnoringSet(); + invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate)); + if (!invokers.isEmpty()) { + return true; + } + } + return false; + } + /* * Extension of HashSet that ignores nulls, rather than putting them into * the set. */ - private static class NullIgnoringSet extends HashSet{ - + private static class NullIgnoringSet extends HashSet { + @Override public boolean add(E e) { - if(e == null){ + if (e == null) { return false; } - else{ + else { return super.add(e); } }; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java index 9c756fac6..08c20ce20 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java @@ -25,48 +25,68 @@ import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.util.MethodInvoker; /** - * {@link MethodInterceptor} that, given a map of method names and {@link MethodInvoker}s, - * will execute all methods tied to a particular method name, with the provided - * arguments. The only possible return value that is handled is of type ExitStatus, since - * the only StepListener implementation that isn't void is - * {@link StepExecutionListener#afterStep(org.springframework.batch.core.StepExecution)}, which - * returns ExitStatus. + * {@link MethodInterceptor} that, given a map of method names and + * {@link MethodInvoker}s, will execute all methods tied to a particular method + * name, with the provided arguments. The only possible return value that is + * handled is of type ExitStatus, since the only StepListener implementation + * that isn't void is + * {@link StepExecutionListener#afterStep(org.springframework.batch.core.StepExecution)} + * , which returns ExitStatus. * * @author Lucas Ward * @since 2.0 * @see MethodInvoker */ -public class MethodInvokerMethodInterceptor implements MethodInterceptor{ +public class MethodInvokerMethodInterceptor implements MethodInterceptor { private final Map> invokerMap; public MethodInvokerMethodInterceptor(Map> invokerMap) { this.invokerMap = invokerMap; } - + public Object invoke(MethodInvocation invocation) throws Throwable { - + String methodName = invocation.getMethod().getName(); Set invokers = invokerMap.get(methodName); - - if(invokers == null){ + + if (invokers == null) { return null; } ExitStatus status = null; - for(MethodInvoker invoker : invokers){ + for (MethodInvoker invoker : invokers) { Object retVal = invoker.invokeMethod(invocation.getArguments()); - if(retVal instanceof ExitStatus){ - if(status != null){ + if (retVal instanceof ExitStatus) { + if (status != null) { status = status.and((ExitStatus) retVal); } - else{ + else { status = (ExitStatus) retVal; } } } - + return status; } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MethodInvokerMethodInterceptor)) { + return false; + } + MethodInvokerMethodInterceptor other = (MethodInvokerMethodInterceptor) obj; + return invokerMap.equals(other.invokerMap); + } - + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return invokerMap.hashCode(); + } + } 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 724170c9a..e3b242443 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 @@ -34,76 +34,87 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** - * {@link FactoryBean} implementation that builds a {@link StepListener} based on the - * various lifecycle methods or annotations that are provided. There are three possible ways of having - * a method called as part of a {@link StepListener} lifecyle: + * {@link FactoryBean} implementation that builds a {@link StepListener} based + * on the various lifecycle methods or annotations that are provided. There are + * three possible ways of having a method called as part of a + * {@link StepListener} lifecyle: * *
      - *
    • Interface implementation: By implementing any of the subclasses of StepListener, methods on said - * interface will be called - *
    • Annotations: Annotating a method will result in registration. - *
    • String name of the method to be called, which is tied to {@link StepListenerMetaData} in the - * metaDatMap. - *
    + *
  • Interface implementation: By implementing any of the subclasses of + * StepListener, methods on said interface will be called + *
  • Annotations: Annotating a method will result in registration. + *
  • String name of the method to be called, which is tied to + * {@link StepListenerMetaData} in the metaDatMap. + * * - * It should be noted that methods obtained by name or annotation that don't match the StepListener method - * signatures to which they belong, will cause errors. However, it is acceptable to have no parameters at all. - * If the same method is marked in more than one way. (i.e. the method name is given and it's annotated) the - * method will only be called once. However, if the same class has multiple methods tied to a particular - * listener, each method will be called. + * It should be noted that methods obtained by name or annotation that don't + * match the StepListener method signatures to which they belong, will cause + * errors. However, it is acceptable to have no parameters at all. If the same + * method is marked in more than one way. (i.e. the method name is given and + * it's annotated) the method will only be called once. However, if the same + * class has multiple methods tied to a particular listener, each method will be + * called. * * @author Lucas Ward * @since 2.0 * @see StepListenerMetaData */ -public class StepListenerFactoryBean implements FactoryBean, InitializingBean{ +public class StepListenerFactoryBean implements FactoryBean, InitializingBean { private Object delegate; + private Map metaDataMap; - - public Object getObject() throws Exception { - + + public Object getObject() { + Map> invokerMap = new HashMap>(); - if(metaDataMap == null){ + if (metaDataMap == null) { metaDataMap = new HashMap(); } - //Because all annotations and interfaces should be checked for, make sure that each meta data - //entry is represented. - for(StepListenerMetaData metaData : StepListenerMetaData.values()){ - if(!metaDataMap.containsKey(metaData.getPropertyName())){ - //put null so that the annotation and interface is checked + // Because all annotations and interfaces should be checked for, make + // sure that each meta data + // entry is represented. + for (StepListenerMetaData metaData : StepListenerMetaData.values()) { + if (!metaDataMap.containsKey(metaData.getPropertyName())) { + // put null so that the annotation and interface is checked metaDataMap.put(metaData.getPropertyName(), null); } } - + Set> listenerInterfaces = new HashSet>(); - - //For every entry in the map, try and find a method by interface, name, or annotation. If the same - for(Entry entry : metaDataMap.entrySet()){ + + // For every entry in the map, try and find a method by interface, name, + // or annotation. If the same + for (Entry entry : metaDataMap.entrySet()) { StepListenerMetaData metaData = StepListenerMetaData.fromPropertyName(entry.getKey()); Set invokers = new NullIgnoringSet(); invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes())); - invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), + invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate, metaData.getParamTypes())); invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate)); - if(!invokers.isEmpty()){ + if (!invokers.isEmpty()) { invokerMap.put(metaData.getMethodName(), invokers); listenerInterfaces.add(metaData.getListenerInterface()); } } - - //create a proxy listener for only the interfaces that have methods to be called + + if (listenerInterfaces.isEmpty()) { + listenerInterfaces.add(StepListener.class); + } + + // create a proxy listener for only the interfaces that have methods to + // be called ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setInterfaces(listenerInterfaces.toArray(new Class[0])); proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap))); return proxyFactory.getProxy(); } - - private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class... params){ - if(methodName != null){ + + private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class... params) { + if (methodName != null) { return MethodInvokerUtils.createMethodInvokerByName(candidate, methodName, false, params); } - else{ + else { return null; } } @@ -116,27 +127,60 @@ public class StepListenerFactoryBean implements FactoryBean, InitializingBean{ public boolean isSingleton() { return false; } - + public void setDelegate(Object delegate) { this.delegate = delegate; } - + public void setMetaDataMap(Map metaDataMap) { this.metaDataMap = metaDataMap; } - + + /** + * Convenience method to wrap any object and expose the appropriate + * {@link StepListener} interfaces. + * @param delegate a delegate object + * @return a StepListener instance constructed from the delegate + */ + public static StepListener getListener(Object delegate) { + StepListenerFactoryBean factory = new StepListenerFactoryBean(); + factory.setDelegate(delegate); + return (StepListener) factory.getObject(); + } + + /** + * Convenience method to check whether the given object is or can be made + * into a {@link StepListener}. + * @param delegate the object to check + * @return true if the delegate is an instance of any of the + * {@link StepListener} interfaces, or contains the marker annotations + */ + public static boolean isListener(Object delegate) { + if (delegate instanceof StepListener) { + return true; + } + for (StepListenerMetaData metaData : StepListenerMetaData.values()) { + Set invokers = new NullIgnoringSet(); + invokers.add(getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate)); + if (!invokers.isEmpty()) { + return true; + } + } + return false; + } + /* * Extension of HashSet that ignores nulls, rather than putting them into * the set. */ - private static class NullIgnoringSet extends HashSet{ - + private static class NullIgnoringSet extends HashSet { + @Override public boolean add(E e) { - if(e == null){ + if (e == null) { return false; } - else{ + else { return super.add(e); } }; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleStepFactoryBean.java index 51a3bb2f4..1b83bdf40 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleStepFactoryBean.java @@ -29,6 +29,7 @@ import org.springframework.batch.core.SkipListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.StepListenerFactoryBean; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemProcessor; @@ -121,10 +122,21 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { super(); } + /** + * Flag to signal that the reader is transactional (usually a JMS consumer) + * so that items are re-presented after a rollback. The default is false and + * readers are assumed to be forward-only. + * + * @param isReaderTransactionalQueue the value of the flag + */ public void setIsReaderTransactionalQueue(boolean isReaderTransactionalQueue) { this.isReaderTransactionalQueue = isReaderTransactionalQueue; } + /** + * Convenience method for subclasses. + * @return true if the flag is set (default false) + */ protected boolean isReaderTransactionalQueue() { return isReaderTransactionalQueue; } @@ -142,7 +154,7 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { } /** - * Public getter for the String. + * Public getter for the name of the step. * @return the name */ public String getName() { @@ -150,7 +162,7 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { } /** - * Public setter for the startLimit. + * Public setter for the start limit for the step. * * @param startLimit the startLimit to set */ @@ -159,7 +171,8 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { } /** - * Public setter for the shouldAllowStartIfComplete. + * Public setter for the flag to indicate that the step should be replayed + * on a restart, even if successful the first time. * * @param allowStartIfComplete the shouldAllowStartIfComplete to set */ @@ -168,21 +181,21 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { } /** - * @param itemReader the itemReader to set + * @param itemReader the {@link ItemReader} to set */ public void setItemReader(ItemReader itemReader) { this.itemReader = itemReader; } /** - * @param itemWriter the itemWriter to set + * @param itemWriter the {@link ItemWriter} to set */ public void setItemWriter(ItemWriter itemWriter) { this.itemWriter = itemWriter; } /** - * @param itemProcessor the itemProcessor to set + * @param itemProcessor the {@link ItemProcessor} to set */ public void setItemProcessor(ItemProcessor itemProcessor) { this.itemProcessor = itemProcessor; @@ -516,11 +529,14 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { if (itemHandler instanceof ItemStream) { step.registerStream((ItemStream) itemHandler); } - if (itemHandler instanceof StepExecutionListener) { - step.registerStepExecutionListener((StepExecutionListener) itemHandler); - } - if (itemHandler instanceof ChunkListener) { - chunkListeners.add((StepListener) itemHandler); + if (StepListenerFactoryBean.isListener(itemHandler)) { + StepListener listener = StepListenerFactoryBean.getListener(itemHandler); + if (listener instanceof StepExecutionListener) { + step.registerStepExecutionListener((StepExecutionListener) listener); + } + if (listener instanceof ChunkListener) { + chunkListeners.add((StepListener) listener); + } } } @@ -551,17 +567,20 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { // auto-register reader, processor and writer for (Object itemHandler : new Object[] { getItemReader(), getItemWriter(), getItemProcessor() }) { - if (itemHandler instanceof SkipListener) { - chunkProvider.registerListener((StepListener) itemHandler); - chunkProcessor.registerListener((StepListener) itemHandler); - // already registered with both so avoid double-registering - continue; - } - if (itemHandler instanceof ItemReadListener) { - chunkProvider.registerListener((StepListener) itemHandler); - } - if (itemHandler instanceof ItemProcessListener || itemHandler instanceof ItemWriteListener) { - chunkProcessor.registerListener((StepListener) itemHandler); + if (StepListenerFactoryBean.isListener(itemHandler)) { + StepListener listener = StepListenerFactoryBean.getListener(itemHandler); + if (listener instanceof SkipListener) { + chunkProvider.registerListener(listener); + chunkProcessor.registerListener(listener); + // already registered with both so avoid double-registering + continue; + } + if (listener instanceof ItemReadListener) { + chunkProvider.registerListener(listener); + } + if (listener instanceof ItemProcessListener || listener instanceof ItemWriteListener) { + chunkProcessor.registerListener(listener); + } } } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java index 19b451f8d..d4b7ea297 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java @@ -15,7 +15,11 @@ */ package org.springframework.batch.core.listener; -import static org.junit.Assert.*; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import java.util.HashSet; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -26,19 +30,19 @@ import org.springframework.batch.core.annotation.BeforeJob; /** * @author Lucas Ward - * + * */ public class JobListenerFactoryBeanTests { JobListenerFactoryBean factoryBean; - + @Before - public void setUp(){ + public void setUp() { factoryBean = new JobListenerFactoryBean(); } - + @Test - public void testWithInterface() throws Exception{ + public void testWithInterface() throws Exception { JobListenerWithInterface delegate = new JobListenerWithInterface(); factoryBean.setDelegate(delegate); JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject(); @@ -48,9 +52,9 @@ public class JobListenerFactoryBeanTests { assertTrue(delegate.beforeJobCalled); assertTrue(delegate.afterJobCalled); } - + @Test - public void testWithAnnotations() throws Exception{ + public void testWithAnnotations() throws Exception { AnnotatedTestClass delegate = new AnnotatedTestClass(); factoryBean.setDelegate(delegate); JobExecutionListener listener = (JobExecutionListener) factoryBean.getObject(); @@ -60,34 +64,81 @@ public class JobListenerFactoryBeanTests { assertTrue(delegate.beforeJobCalled); assertTrue(delegate.afterJobCalled); } - - private class JobListenerWithInterface implements JobExecutionListener{ + + @Test + public void testFactoryMethod() throws Exception { + JobListenerWithInterface delegate = new JobListenerWithInterface(); + Object listener = JobListenerFactoryBean.getListener(delegate); + assertTrue(listener instanceof JobExecutionListener); + ((JobExecutionListener) listener).afterJob(new JobExecution(11L)); + assertTrue(delegate.afterJobCalled); + } + + @Test + public void testUseInHashSet() throws Exception { + JobListenerWithInterface delegate = new JobListenerWithInterface(); + Object listener = JobListenerFactoryBean.getListener(delegate); + Object other = JobListenerFactoryBean.getListener(delegate); + assertTrue(listener instanceof JobExecutionListener); + Set listeners = new HashSet(); + listeners.add((JobExecutionListener) listener); + listeners.add((JobExecutionListener) other); + assertTrue(listeners.contains(listener)); + assertEquals(1, listeners.size()); + } + + @Test + public void testAnnotationsIsListener() throws Exception { + assertTrue(JobListenerFactoryBean.isListener(new Object() { + @SuppressWarnings("unused") + @BeforeJob + public void foo(JobExecution execution) { + } + })); + } + + @Test + public void testInterfaceIsListener() throws Exception { + assertTrue(JobListenerFactoryBean.isListener(new JobListenerWithInterface())); + } + + @Test + public void testEqualityOfProxies() throws Exception { + JobListenerWithInterface delegate = new JobListenerWithInterface(); + Object listener1 = JobListenerFactoryBean.getListener(delegate); + Object listener2 = JobListenerFactoryBean.getListener(delegate); + assertEquals(listener1, listener2); + } + + private class JobListenerWithInterface implements JobExecutionListener { boolean beforeJobCalled = false; + boolean afterJobCalled = false; - + public void afterJob(JobExecution jobExecution) { - beforeJobCalled = true; + afterJobCalled = true; } public void beforeJob(JobExecution jobExecution) { - afterJobCalled = true; - } - - } - - private class AnnotatedTestClass { - - boolean beforeJobCalled = false; - boolean afterJobCalled = false; - - @BeforeJob - public void before(){ beforeJobCalled = true; } - + + } + + private class AnnotatedTestClass { + + boolean beforeJobCalled = false; + + boolean afterJobCalled = false; + + @BeforeJob + public void before() { + beforeJobCalled = true; + } + @AfterJob - public void after(){ + public void after() { afterJobCalled = true; } } 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 0889bd571..ad90ac0e8 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 @@ -17,6 +17,7 @@ package org.springframework.batch.core.listener; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_CHUNK; import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_STEP; @@ -57,22 +58,22 @@ import org.springframework.util.Assert; */ public class StepListenerFactoryBeanTests { - StepListenerFactoryBean factoryBean; - TestClass testClass; - JobExecution jobExecution = new JobExecution(11L); - StepExecution stepExecution = new StepExecution("testStep", jobExecution); + private StepListenerFactoryBean factoryBean; + private TestListener testListener; + private JobExecution jobExecution = new JobExecution(11L); + private StepExecution stepExecution = new StepExecution("testStep", jobExecution); @Before public void setUp(){ factoryBean = new StepListenerFactoryBean(); - testClass = new TestClass(); + testListener = new TestListener(); } @Test @SuppressWarnings("unchecked") public void testStepAndChunk() throws Exception{ - factoryBean.setDelegate(testClass); + factoryBean.setDelegate(testListener); Map metaDataMap = new HashMap();; metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy"); metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk"); @@ -97,21 +98,21 @@ public class StepListenerFactoryBeanTests { ((SkipListener)listener).onSkipInRead(new Throwable()); ((SkipListener)listener).onSkipInProcess(item, new Throwable()); ((SkipListener)listener).onSkipInWrite(item, new Throwable()); - assertTrue(testClass.beforeStepCalled); - assertTrue(testClass.beforeChunkCalled); - assertTrue(testClass.afterChunkCalled); - assertTrue(testClass.beforeReadCalled); - assertTrue(testClass.afterReadCalled); - assertTrue(testClass.onReadErrorCalled); - assertTrue(testClass.beforeProcessCalled); - assertTrue(testClass.afterProcessCalled); - assertTrue(testClass.onProcessErrorCalled); - assertTrue(testClass.beforeWriteCalled); - assertTrue(testClass.afterWriteCalled); - assertTrue(testClass.onWriteErrorCalled); - assertTrue(testClass.onSkipInReadCalled); - assertTrue(testClass.onSkipInProcessCalled); - assertTrue(testClass.onSkipInWriteCalled); + assertTrue(testListener.beforeStepCalled); + assertTrue(testListener.beforeChunkCalled); + assertTrue(testListener.afterChunkCalled); + assertTrue(testListener.beforeReadCalled); + assertTrue(testListener.afterReadCalled); + assertTrue(testListener.onReadErrorCalled); + assertTrue(testListener.beforeProcessCalled); + assertTrue(testListener.afterProcessCalled); + assertTrue(testListener.onProcessErrorCalled); + assertTrue(testListener.beforeWriteCalled); + assertTrue(testListener.afterWriteCalled); + assertTrue(testListener.onWriteErrorCalled); + assertTrue(testListener.onSkipInReadCalled); + assertTrue(testListener.onSkipInProcessCalled); + assertTrue(testListener.onSkipInWriteCalled); } @Test @@ -132,7 +133,7 @@ public class StepListenerFactoryBeanTests { public void testAnnotatingInterfaceResultsInOneCall() throws Exception{ MultipleAfterStep delegate = new MultipleAfterStep(); factoryBean.setDelegate(delegate); - Map metaDataMap = new HashMap();; + Map metaDataMap = new HashMap(); metaDataMap.put(AFTER_STEP.getPropertyName(), "afterStep"); factoryBean.setMetaDataMap(metaDataMap); StepListener listener = (StepListener) factoryBean.getObject(); @@ -140,7 +141,55 @@ public class StepListenerFactoryBeanTests { assertEquals(1, delegate.callcount); } - private class MultipleAfterStep implements StepExecutionListener{ + @Test + public void testVanillaInterface() throws Exception{ + MultipleAfterStep delegate = new MultipleAfterStep(); + factoryBean.setDelegate(delegate); + Object listener = factoryBean.getObject(); + assertTrue(listener instanceof StepExecutionListener); + ((StepExecutionListener)listener).beforeStep(stepExecution); + assertEquals(1, delegate.callcount); + } + + @Test + public void testFactoryMethod() throws Exception{ + MultipleAfterStep delegate = new MultipleAfterStep(); + Object listener = StepListenerFactoryBean.getListener(delegate); + assertTrue(listener instanceof StepExecutionListener); + assertFalse(listener instanceof ChunkListener); + ((StepExecutionListener)listener).beforeStep(stepExecution); + assertEquals(1, delegate.callcount); + } + + @Test + public void testInterfaceIsListener() throws Exception { + assertTrue(StepListenerFactoryBean.isListener(new ThreeStepExecutionListener())); + } + + @Test + public void testAnnotationsIsListener() throws Exception { + assertTrue(StepListenerFactoryBean.isListener(new Object() { + @SuppressWarnings("unused") + @BeforeStep + public void foo(StepExecution execution) { + } + })); + } + + @Test + public void testMixedIsListener() throws Exception { + assertTrue(StepListenerFactoryBean.isListener(new MultipleAfterStep())); + } + + @Test + public void testNonListener() throws Exception{ + Object delegate = new Object(); + factoryBean.setDelegate(delegate); + StepListener listener = (StepListener) factoryBean.getObject(); + assertTrue(listener instanceof StepListener); + } + + private class MultipleAfterStep implements StepExecutionListener { int callcount = 0; @@ -183,7 +232,7 @@ public class StepListenerFactoryBeanTests { } - private class TestClass implements SkipListener{ + private class TestListener implements SkipListener{ boolean beforeStepCalled = false; boolean afterStepCalled = false; diff --git a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml index 65848a676..02e24166a 100644 --- a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml +++ b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml @@ -39,7 +39,6 @@ - diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/CustomItemReaderTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/CustomItemReaderTests.java index 9cfa40739..612c55bcb 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/CustomItemReaderTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/CustomItemReaderTests.java @@ -15,11 +15,14 @@ */ package org.springframework.batch.sample.common; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.util.ArrayList; import java.util.List; -import junit.framework.TestCase; - +import org.junit.Before; +import org.junit.Test; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; @@ -34,15 +37,15 @@ import org.springframework.batch.item.UnexpectedInputException; * @author Lucas Ward * */ -public class CustomItemReaderTests extends TestCase { +public class CustomItemReaderTests { ItemReader itemReader; /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { List items = new ArrayList(); items.add("1"); @@ -52,6 +55,7 @@ public class CustomItemReaderTests extends TestCase { itemReader = new CustomItemReader(items); } + @Test public void testRead() throws Exception{ assertEquals("1", itemReader.read()); @@ -60,6 +64,7 @@ public class CustomItemReaderTests extends TestCase { assertNull(itemReader.read()); } + @Test public void testRestart() throws Exception{ ExecutionContext executionContext = new ExecutionContext(); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/StagingItemReaderTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/StagingItemReaderTests.java index 28dc8871f..39d0d0961 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/StagingItemReaderTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/common/StagingItemReaderTests.java @@ -41,7 +41,7 @@ public class StagingItemReaderTests { @Autowired private StagingItemReader reader; - private Long jobId = 11L; + private Long jobId = 113L; @Autowired public void setDataSource(DataSource dataSource) { diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/support/AbstractRowMapperTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/support/AbstractRowMapperTests.java index 6ce1fde3f..5ffa94b60 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/support/AbstractRowMapperTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/support/AbstractRowMapperTests.java @@ -1,12 +1,13 @@ package org.springframework.batch.sample.support; -import static org.easymock.EasyMock.*; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.replay; +import static org.junit.Assert.assertEquals; import java.sql.ResultSet; import java.sql.SQLException; -import junit.framework.TestCase; - +import org.junit.Test; import org.springframework.jdbc.core.RowMapper; /** @@ -14,38 +15,38 @@ import org.springframework.jdbc.core.RowMapper; * * @author Robert Kasanicky */ -public abstract class AbstractRowMapperTests extends TestCase { +public abstract class AbstractRowMapperTests { - //row number should be irrelevant + // row number should be irrelevant private static final int IGNORED_ROW_NUMBER = 0; - - //mock result set + + // mock result set private ResultSet rs = createMock(ResultSet.class); /** - * @return Expected result of mapping the mock ResultSet by - * the mapper being tested. + * @return Expected result of mapping the mock ResultSet by the + * mapper being tested. */ abstract protected Object expectedDomainObject(); - + /** * @return RowMapper implementation that is being tested. */ abstract protected RowMapper rowMapper(); - + /* * Define the behaviour of mock ResultSet. */ abstract protected void setUpResultSetMock(ResultSet rs) throws SQLException; - - + /* * Regular usage scenario. */ + @Test public void testRegularUse() throws SQLException { setUpResultSetMock(rs); replay(rs); - + assertEquals(expectedDomainObject(), rowMapper().mapRow(rs, IGNORED_ROW_NUMBER)); } }