From 337ff6709e0f6fe94a95efd369e037e9e5301aec Mon Sep 17 00:00:00 2001 From: lucasward Date: Fri, 21 Nov 2008 07:05:35 +0000 Subject: [PATCH] OPEN - issue BATCH-673: Add new Java 5.0 features http://jira.springframework.org/browse/BATCH-673 First draft of StepListener annotation support. --- .../batch/core/annotation/AfterChunk.java | 33 +++ .../batch/core/annotation/AfterProcess.java | 20 ++ .../batch/core/annotation/AfterRead.java | 35 +++ .../batch/core/annotation/AfterWrite.java | 20 ++ .../batch/core/annotation/BeforeChunk.java | 33 +++ .../batch/core/annotation/BeforeProcess.java | 35 +++ .../batch/core/annotation/BeforeRead.java | 35 +++ .../batch/core/annotation/BeforeWrite.java | 20 ++ .../batch/core/annotation/OnProcessError.java | 20 ++ .../batch/core/annotation/OnReadError.java | 35 +++ .../core/annotation/OnSkipInProcess.java | 21 ++ .../batch/core/annotation/OnSkipInRead.java | 21 ++ .../batch/core/annotation/OnSkipInWrite.java | 21 ++ .../batch/core/annotation/OnWriteError.java | 20 ++ .../util/SimpleMethodInvoker.java | 35 +++ .../StepExecutionListenerAdapter.java | 130 +++++++++++ .../listener/StepListenerFactoryBean.java | 147 ++++++++++++ .../core/listener/StepListenerMetaData.java | 99 ++++++++ .../StepListenerMethodInterceptor.java | 55 +++++ .../StepListenerFactoryBeanTests.java | 218 ++++++++++++++++++ 20 files changed, 1053 insertions(+) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerAdapter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMethodInterceptor.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java new file mode 100644 index 000000000..0ebc1ef81 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method to be called after a chunk is executed. + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AfterChunk { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java new file mode 100644 index 000000000..bfdeccdf5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemProcessor; + +/** + * Marks a method to be called after an item is passed to an {@link ItemProcessor} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AfterProcess { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java new file mode 100644 index 000000000..173aa97ea --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemReader; + +/** + * Marks a method to be called after an item is read from an {@link ItemReader} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AfterRead { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java new file mode 100644 index 000000000..2277f1e70 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemWriter; + +/** + * Marks a method to be called after an item is passed to an {@link ItemWriter} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface AfterWrite { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java new file mode 100644 index 000000000..817a5ba31 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method to be called before a chunk is executed. + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BeforeChunk { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java new file mode 100644 index 000000000..c378c6f64 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemProcessor; + +/** + * Marks a method to be called before an item is passed to an {@link ItemProcessor} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BeforeProcess { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java new file mode 100644 index 000000000..342f74e31 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemReader; + +/** + * Marks a method to be called before an item is read from an {@link ItemReader} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BeforeRead { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java new file mode 100644 index 000000000..3caa3099c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemWriter; + +/** + * Marks a method to be called before an item is passed to an {@link ItemWriter} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BeforeWrite { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java new file mode 100644 index 000000000..fbf00fd9d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemProcessor; + +/** + * Marks a method to be called if an exception is thrown by an {@link ItemProcessor} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnProcessError { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java new file mode 100644 index 000000000..2ecc696f5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2008 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.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemReader; + +/** + * Marks a method to be called if an exception is thrown by an {@link ItemReader} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnReadError { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java new file mode 100644 index 000000000..434c907f6 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java @@ -0,0 +1,21 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemProcessor; + +/** + * Marks a method to be called when an item is skipped due to an exception thrown in the + * {@link ItemProcessor} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnSkipInProcess { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java new file mode 100644 index 000000000..991d4f87f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java @@ -0,0 +1,21 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemReader; + +/** + * Marks a method to be called when an item is skipped due to an exception thrown in the + * {@link ItemReader} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnSkipInRead { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java new file mode 100644 index 000000000..74f8ac506 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java @@ -0,0 +1,21 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemWriter; + +/** + * Marks a method to be called when an item is skipped due to an exception thrown in the + * {@link ItemWriter} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnSkipInWrite { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java new file mode 100644 index 000000000..5fa33872a --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java @@ -0,0 +1,20 @@ +package org.springframework.batch.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.item.ItemWriter; + +/** + * Marks a method to be called if an exception is thrown by an {@link ItemWriter} + * + * @author Lucas Ward + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface OnWriteError { + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java index 6f6a6f68e..48e5c0248 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java @@ -33,6 +33,7 @@ package org.springframework.batch.core.configuration.util; import java.lang.reflect.Method; +import org.apache.commons.lang.builder.HashCodeBuilder; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -49,6 +50,22 @@ public class SimpleMethodInvoker implements MethodInvoker { private final Object object; private Method method; + public static MethodInvoker createMethodInvokerByName(Object object, String methodName, boolean paramsRequired, Class... paramTypes){ + Assert.notNull(object, "Object to invoke must not be null"); + Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes); + //if no method was found for the given parameters, and the parameters aren't required + if(method == null && !paramsRequired){ + //try with no params + method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[]{}); + } + if(method == null){ + return null; + } + else{ + return new SimpleMethodInvoker(object, method); + } + } + public SimpleMethodInvoker(Object object, Method method) { Assert.notNull(object, "Object to invoke must not be null"); Assert.notNull(method, "Method to invoke must not be null"); @@ -93,4 +110,22 @@ public class SimpleMethodInvoker implements MethodInvoker { object + "] with arguments: [" + args + "]"); } } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof SimpleMethodInvoker)){ + return false; + } + + if(obj == this){ + return true; + } + SimpleMethodInvoker rhs = (SimpleMethodInvoker) obj; + return (rhs.method == this.method) && (rhs.object == this.object); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(25, 37).append(object).append(method).toHashCode(); + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerAdapter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerAdapter.java new file mode 100644 index 000000000..e08da2021 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerAdapter.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2008 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.core.listener; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.ItemProcessListener; +import org.springframework.batch.core.ItemReadListener; +import org.springframework.batch.core.ItemWriteListener; +import org.springframework.batch.core.SkipListener; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.configuration.util.MethodInvoker; + +/** + * @author Lucas Ward + * + */ +public class StepExecutionListenerAdapter implements StepExecutionListener, ChunkListener, + ItemReadListener, ItemWriteListener, ItemProcessListener, SkipListener{ + + private Set beforeStepMethodInvokers = new HashSet(); + private Set afterMethodInvokers = new HashSet(); + + public void setAfterMethodInvokers(Set afterMethodInvokers) { + this.afterMethodInvokers = afterMethodInvokers; + } + + public void setBeforeMethodInvokers(Set beforeMethodInvokers) { + this.beforeStepMethodInvokers = beforeMethodInvokers; + } + + public ExitStatus afterStep(StepExecution stepExecution) { + return null; + } + + public void beforeStep(StepExecution stepExecution) { + for(MethodInvoker invoker : beforeStepMethodInvokers){ + invoker.invokeMethod(stepExecution); + } + } + + public void afterChunk() { + // TODO Auto-generated method stub + + } + + public void beforeChunk() { + // TODO Auto-generated method stub + + } + + public void afterRead(T item) { + // TODO Auto-generated method stub + + } + + public void beforeRead() { + // TODO Auto-generated method stub + + } + + public void onReadError(Exception ex) { + // TODO Auto-generated method stub + + } + + public void afterWrite(List items) { + // TODO Auto-generated method stub + + } + + public void beforeWrite(List items) { + // TODO Auto-generated method stub + + } + + public void onWriteError(Exception exception, List items) { + // TODO Auto-generated method stub + + } + + public void afterProcess(T item, S result) { + // TODO Auto-generated method stub + + } + + public void beforeProcess(T item) { + // TODO Auto-generated method stub + + } + + public void onProcessError(T item, Exception e) { + // TODO Auto-generated method stub + + } + + public void onSkipInProcess(T item, Throwable t) { + // TODO Auto-generated method stub + + } + + public void onSkipInRead(Throwable t) { + // TODO Auto-generated method stub + + } + + public void onSkipInWrite(S item, Throwable t) { + // TODO Auto-generated method stub + + } + +} 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 new file mode 100644 index 000000000..39b7cfdcc --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2008 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.core.listener; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.configuration.util.AnnotationMethodResolver; +import org.springframework.batch.core.configuration.util.MethodInvoker; +import org.springframework.batch.core.configuration.util.MethodResolver; +import org.springframework.batch.core.configuration.util.SimpleMethodInvoker; +import org.springframework.beans.factory.FactoryBean; + +/** + * {@link FactoryBean} implementation that builds a {@link StepListener} based on the + * various lifecycle methods or annotations that are provided. + * + * @author Lucas Ward + * + */ +public class StepListenerFactoryBean implements FactoryBean{ + + private Object delegate; + private Map metaDataMap; + + public Object getObject() throws Exception { + + Map> invokerMap = new HashMap>(); + if(metaDataMap == null){ + metaDataMap = new HashMap(); + } + for(StepListenerMetaData metaData : StepListenerMetaData.values()){ + if(!metaDataMap.containsKey(metaData)){ + //put null so that the annotation and interface is checked + metaDataMap.put(metaData, null); + } + } + Set> listenerInterfaces = new HashSet>(); + + for(Entry entry : metaDataMap.entrySet()){ + StepListenerMetaData metaData = entry.getKey(); + Set invokers = new NullIgnoringSet(); + invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes())); + invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), + delegate, metaData.getParamTypes())); + invokers.add(getMethodInvokerByAnnotation(delegate, metaData.getAnnotation())); + if(!invokers.isEmpty()){ + invokerMap.put(metaData.getMethodName(), invokers); + listenerInterfaces.add(metaData.getListenerInterface()); + } + } + + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.setInterfaces(listenerInterfaces.toArray(new Class[0])); + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new StepListenerMethodInterceptor(invokerMap))); + return proxyFactory.getProxy(); + } + + private MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class... params){ + if(methodName != null){ + return SimpleMethodInvoker.createMethodInvokerByName(candidate, methodName, false, params); + } + else{ + return null; + } + } + + private MethodInvoker getMethodInvokerForInterface(Class iFace, String methodName, + Object candidate, Class... params){ + + if(candidate.getClass().isAssignableFrom(iFace)){ + return SimpleMethodInvoker.createMethodInvokerByName(candidate, methodName, true, params); + } + else{ + return null; + } + } + + private MethodInvoker getMethodInvokerByAnnotation(Object candidate, Class annotation){ + + MethodResolver resolver = new AnnotationMethodResolver(annotation); + Method method = resolver.findMethod(candidate); + + if(method != null){ + return new SimpleMethodInvoker(candidate, method); + } + else{ + return null; + } + } + + + @SuppressWarnings("unchecked") + public Class getObjectType() { + return StepListener.class; + } + + public boolean isSingleton() { + return false; + } + + public void setDelegate(Object delegate) { + this.delegate = delegate; + } + + public void setMetaDataMap(Map metaDataMap) { + this.metaDataMap = metaDataMap; + } + + /* + * Extension of HashSet that ignores nulls, rather than putting them into + * the set. + */ + private class NullIgnoringSet extends HashSet{ + + @Override + public boolean add(E e) { + if(e == null){ + return false; + } + else{ + return super.add(e); + } + }; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java new file mode 100644 index 000000000..722d3c041 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2008 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.core.listener; + +import java.lang.annotation.Annotation; + +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.SkipListener; +import org.springframework.batch.core.StepExecution; +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.AfterProcess; +import org.springframework.batch.core.annotation.AfterRead; +import org.springframework.batch.core.annotation.AfterStep; +import org.springframework.batch.core.annotation.AfterWrite; +import org.springframework.batch.core.annotation.BeforeChunk; +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.annotation.OnProcessError; +import org.springframework.batch.core.annotation.OnReadError; +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.annotation.OnWriteError; + +/** + * Enumeration for {@link StepListener} meta data, which ties together the names + * of methods, their interfaces, annotation, and expected arguments. + * + * @author Lucas Ward + * @since 2.0 + * @see StepListenerFactoryBean + */ +public enum StepListenerMetaData { + + BEFORE_STEP("beforeStep", BeforeStep.class, StepExecutionListener.class, StepExecution.class), + AFTER_STEP("afterStep", AfterStep.class, StepExecutionListener.class, StepExecution.class), + BEFORE_CHUNK("beforeChunk", BeforeChunk.class, ChunkListener.class), + AFTER_CHUNK("afterChunk", AfterChunk.class, ChunkListener.class), + BEFORE_READ("beforeRead", BeforeRead.class, ItemReadListener.class), + AFTER_READ("afterRead", AfterRead.class, ItemReadListener.class, Object.class), + ON_READ_ERROR("onReadError", OnReadError.class, ItemReadListener.class, Exception.class), + BEFORE_PROCESS("beforeProcess", BeforeProcess.class, ItemProcessListener.class, Object.class), + AFTER_PROCESS("afterProcess", AfterProcess.class, ItemProcessListener.class, Object.class), + ON_PROCESS_ERROR("onProcessError", OnProcessError.class, ItemProcessListener.class, Object.class, Exception.class), + BEFORE_WRITE("beforeWrite", BeforeWrite.class, ItemWriteListener.class, Object.class), + AFTER_WRITE("afterWrite", AfterWrite.class, ItemWriteListener.class, Object.class), + ON_WRITE_ERROR("onWriteError", OnWriteError.class, ItemWriteListener.class, Object.class, Exception.class), + ON_SKIP_IN_READ("onSkipInRead", OnSkipInRead.class, SkipListener.class, Object.class, Throwable.class), + ON_SKIP_IN_PROCESS("onSkipInProcess", OnSkipInProcess.class, SkipListener.class, Object.class, Throwable.class), + ON_SKIP_IN_WRITE("onSkipInWrite", OnSkipInWrite.class, SkipListener.class, Object.class, Throwable.class); + + private final String methodName; + private final Class annotation; + private final Class listenerInterface; + private final Class[] paramTypes; + + StepListenerMetaData(String methodName, Class annotation, Class listenerInterface, Class... paramTypes) { + this.methodName = methodName; + this.annotation = annotation; + this.listenerInterface = listenerInterface; + this.paramTypes = paramTypes; + } + + public String getMethodName() { + return methodName; + } + + public Class getAnnotation() { + return annotation; + } + + public Class getListenerInterface() { + return listenerInterface; + } + + public Class[] getParamTypes() { + return paramTypes; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMethodInterceptor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMethodInterceptor.java new file mode 100644 index 000000000..8fb83de62 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMethodInterceptor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2008 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.core.listener; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.batch.core.configuration.util.MethodInvoker; + +/** + * @author Lucas Ward + * + */ +public class StepListenerMethodInterceptor implements MethodInterceptor{ + + private final Map> invokerMap; + + public StepListenerMethodInterceptor(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){ + return null; + } + + for(MethodInvoker invoker : invokers){ + invoker.invokeMethod(invocation.getArguments()); + } + + return null; + } + + +} 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 new file mode 100644 index 000000000..2c8013fa1 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2008 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.core.listener; + +import static junit.framework.Assert.assertTrue; +import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_CHUNK; +import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_STEP; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +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.JobExecution; +import org.springframework.batch.core.SkipListener; +import org.springframework.batch.core.StepExecution; +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.BeforeChunk; +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.annotation.OnProcessError; +import org.springframework.batch.core.annotation.OnReadError; +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.annotation.OnWriteError; +import org.springframework.util.Assert; + +/** + * @author Lucas Ward + * + */ +public class StepListenerFactoryBeanTests { + + StepListenerFactoryBean factoryBean; + TestClass testClass; + JobExecution jobExecution = new JobExecution(11L); + StepExecution stepExecution = new StepExecution("testStep", jobExecution); + + @Before + public void setUp(){ + factoryBean = new StepListenerFactoryBean(); + testClass = new TestClass(); + } + + @Test + @SuppressWarnings("unchecked") + public void testStepAndChunk() throws Exception{ + + factoryBean.setDelegate(testClass); + Map metaDataMap = new HashMap();; + metaDataMap.put(AFTER_STEP, "destroy"); + metaDataMap.put(AFTER_CHUNK, "afterChunk"); + factoryBean.setMetaDataMap(metaDataMap); + Object item = new Object(); + List items = new ArrayList(); + items.add(item); + StepListener listener = (StepListener) factoryBean.getObject(); + ((StepExecutionListener)listener).beforeStep(stepExecution); + ((StepExecutionListener)listener).afterStep(stepExecution); + ((ChunkListener)listener).beforeChunk(); + ((ChunkListener)listener).afterChunk(); + ((ItemReadListener)listener).beforeRead(); + //((ItemReadListener)listener).afterRead(item); + ((ItemReadListener)listener).onReadError(new Exception()); + ((ItemProcessListener)listener).beforeProcess(item); + ((ItemProcessListener)listener).afterProcess(item, item); + ((ItemProcessListener)listener).onProcessError(item, new Exception()); + ((ItemWriteListener)listener).beforeWrite(items); + ((ItemWriteListener)listener).afterWrite(items); + ((ItemWriteListener)listener).onWriteError(new Exception(), items); + ((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); + } + + + + private class TestClass implements SkipListener{ + + boolean beforeStepCalled = false; + boolean afterStepCalled = false; + boolean beforeChunkCalled = false; + boolean afterChunkCalled = false; + boolean beforeReadCalled = false; + boolean afterReadCalled = false; + boolean onReadErrorCalled = false; + boolean beforeProcessCalled = false; + boolean afterProcessCalled = false; + boolean onProcessErrorCalled = false; + boolean beforeWriteCalled = false; + boolean afterWriteCalled = false; + boolean onWriteErrorCalled = false; + boolean onSkipInReadCalled = false; + boolean onSkipInProcessCalled = false; + boolean onSkipInWriteCalled = false; + + @BeforeStep + public void initStep(){ + beforeStepCalled = true; + } + + public void destroy(){ + afterStepCalled = true; + } + + @BeforeChunk + public void before(){ + beforeChunkCalled = true; + } + + public void afterChunk(){ + afterChunkCalled = true; + } + + @BeforeRead + public void beforeReadMethod(){ + beforeReadCalled = true; + } + + @AfterRead + public void afterReadMethod(Object item){ + Assert.notNull(item); + afterReadCalled = true; + } + + @OnReadError + public void onErrorInRead(){ + onReadErrorCalled = true; + } + + @BeforeProcess + public void beforeProcess(){ + beforeProcessCalled = true; + } + + @AfterProcess + public void afterProcess(){ + afterProcessCalled = true; + } + + @OnProcessError + public void processError(){ + onProcessErrorCalled = true; + } + + @BeforeWrite + public void beforeWrite(){ + beforeWriteCalled = true; + } + + @AfterWrite + public void afterWrite(){ + afterWriteCalled = true; + } + + @OnWriteError + public void writeError(){ + onWriteErrorCalled = true; + } + + @OnSkipInProcess + public void onSkipInProcess(Object item, Throwable t) { + onSkipInProcessCalled = true; + } + + @OnSkipInRead + public void onSkipInRead(Throwable t) { + onSkipInReadCalled = true; + } + + @OnSkipInWrite + public void onSkipInWrite(Object item, Throwable t) { + onSkipInWriteCalled = true; + } + + } +}