OPEN - issue BATCH-673: Add new Java 5.0 features
http://jira.springframework.org/browse/BATCH-673 Added StepListenerFactoryBean to StepParser, along with updating the XSD to support listener methods.
This commit is contained in:
@@ -24,6 +24,9 @@ import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.flow.support.StateTransition;
|
||||
import org.springframework.batch.core.job.flow.support.state.EndState;
|
||||
import org.springframework.batch.core.job.flow.support.state.StepState;
|
||||
import org.springframework.batch.core.listener.JobExecutionListenerFactoryBean;
|
||||
import org.springframework.batch.core.listener.StepListenerFactoryBean;
|
||||
import org.springframework.batch.core.listener.StepListenerMetaData;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
@@ -32,6 +35,7 @@ import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -388,7 +392,7 @@ public class StepParser {
|
||||
DomUtils.getChildElementByTagName(element, "listeners");
|
||||
if (listenersElement != null) {
|
||||
List<BeanReference> listenerBeans = new ArrayList<BeanReference>();
|
||||
handleListenerElements(parserContext, listenersElement,
|
||||
handleStepListenerElements(parserContext, listenersElement,
|
||||
listenerBeans);
|
||||
ManagedList arguments = new ManagedList();
|
||||
arguments.addAll(listenerBeans);
|
||||
@@ -452,6 +456,63 @@ public class StepParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleStepListenerElements(ParserContext parserContext,
|
||||
Element element, List<BeanReference> beans) {
|
||||
List<Element> listenerElements =
|
||||
DomUtils.getChildElementsByTagName(element, "listener");
|
||||
if (listenerElements != null) {
|
||||
for (Element listenerElement : listenerElements) {
|
||||
BeanDefinitionBuilder listenerBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepListenerFactoryBean.class);
|
||||
String id = listenerElement.getAttribute("id");
|
||||
String listenerRef = listenerElement.getAttribute("ref");
|
||||
String className = listenerElement.getAttribute("class");
|
||||
if ((StringUtils.hasText(id) || StringUtils.hasText(className))
|
||||
&& StringUtils.hasText(listenerRef)) {
|
||||
NamedNodeMap attributeNodes = listenerElement.getAttributes();
|
||||
StringBuilder attributes = new StringBuilder();
|
||||
for (int i = 0; i < attributeNodes.getLength(); i++) {
|
||||
if (i > 0) {
|
||||
attributes.append(" ");
|
||||
}
|
||||
attributes.append(attributeNodes.item(i));
|
||||
}
|
||||
throw new BeanCreationException("Both 'id' or 'ref' plus 'class' specified; use 'class' with an optional 'id' or just 'ref' for <" +
|
||||
listenerElement.getTagName() + "> element with attributes: " + attributes);
|
||||
}
|
||||
if (StringUtils.hasText(listenerRef)) {
|
||||
listenerBuilder.addPropertyReference("delegate", listenerRef);
|
||||
}
|
||||
else if (StringUtils.hasText(className)) {
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null);
|
||||
String delegateId = parserContext.getReaderContext().generateBeanName(beanDef);
|
||||
parserContext.getRegistry().registerBeanDefinition(delegateId, beanDef);
|
||||
listenerBuilder.addPropertyReference("delegate", delegateId);
|
||||
}
|
||||
else {
|
||||
throw new BeanCreationException("Neither 'ref' or 'class' specified for <" + listenerElement.getTagName() + "> element");
|
||||
}
|
||||
|
||||
ManagedMap metaDataMap = new ManagedMap();
|
||||
for(StepListenerMetaData metaData: StepListenerMetaData.values()){
|
||||
String listenerMethod = listenerElement.getAttribute(metaData.getPropertyName());
|
||||
if(StringUtils.hasText(listenerMethod)){
|
||||
metaDataMap.put(metaData.getPropertyName(), listenerMethod);
|
||||
}
|
||||
}
|
||||
listenerBuilder.addPropertyValue("metaDataMap", metaDataMap);
|
||||
|
||||
AbstractBeanDefinition beanDef = listenerBuilder.getBeanDefinition();
|
||||
if (!StringUtils.hasText(id)) {
|
||||
id = parserContext.getReaderContext().generateBeanName(beanDef);
|
||||
}
|
||||
parserContext.getRegistry().registerBeanDefinition(id, beanDef);
|
||||
BeanReference bean = new RuntimeBeanReference(id);
|
||||
beans.add(bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleStreamsElement(Element element, RootBeanDefinition bd, ParserContext parserContext) {
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.springframework.batch.core.StepListener;
|
||||
import org.springframework.batch.core.configuration.util.MethodInvoker;
|
||||
import org.springframework.batch.core.configuration.util.MethodInvokerUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} implementation that builds a {@link StepListener} based on the
|
||||
@@ -54,31 +56,31 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
* @since 2.0
|
||||
* @see StepListenerMetaData
|
||||
*/
|
||||
public class StepListenerFactoryBean implements FactoryBean{
|
||||
public class StepListenerFactoryBean implements FactoryBean, InitializingBean{
|
||||
|
||||
private Object delegate;
|
||||
private Map<StepListenerMetaData, String> metaDataMap;
|
||||
private Map<String, String> metaDataMap;
|
||||
|
||||
public Object getObject() throws Exception {
|
||||
|
||||
Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
|
||||
if(metaDataMap == null){
|
||||
metaDataMap = new HashMap<StepListenerMetaData, String>();
|
||||
metaDataMap = new HashMap<String, String>();
|
||||
}
|
||||
//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)){
|
||||
if(!metaDataMap.containsKey(metaData.getPropertyName())){
|
||||
//put null so that the annotation and interface is checked
|
||||
metaDataMap.put(metaData, null);
|
||||
metaDataMap.put(metaData.getPropertyName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Class<? extends StepListener>> listenerInterfaces = new HashSet<Class<? extends StepListener>>();
|
||||
|
||||
//For every entry in th emap, try and find a method by interface, name, or annotation. If the same
|
||||
for(Entry<StepListenerMetaData, String> entry : metaDataMap.entrySet()){
|
||||
StepListenerMetaData metaData = entry.getKey();
|
||||
for(Entry<String, String> entry : metaDataMap.entrySet()){
|
||||
StepListenerMetaData metaData = StepListenerMetaData.fromPropertyName(entry.getKey());
|
||||
Set<MethodInvoker> invokers = new NullIgnoringSet<MethodInvoker>();
|
||||
invokers.add(getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes()));
|
||||
invokers.add(getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(),
|
||||
@@ -119,7 +121,7 @@ public class StepListenerFactoryBean implements FactoryBean{
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void setMetaDataMap(Map<StepListenerMetaData, String> metaDataMap) {
|
||||
public void setMetaDataMap(Map<String, String> metaDataMap) {
|
||||
this.metaDataMap = metaDataMap;
|
||||
}
|
||||
|
||||
@@ -139,4 +141,8 @@ public class StepListenerFactoryBean implements FactoryBean{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package org.springframework.batch.core.listener;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.core.ChunkListener;
|
||||
import org.springframework.batch.core.ItemProcessListener;
|
||||
@@ -52,34 +54,45 @@ import org.springframework.batch.core.annotation.OnWriteError;
|
||||
*/
|
||||
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, 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);
|
||||
BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class),
|
||||
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),
|
||||
BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class),
|
||||
AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class),
|
||||
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class),
|
||||
AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, Object.class),
|
||||
ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, Exception.class),
|
||||
BEFORE_PROCESS("beforeProcess", "before-process-method", BeforeProcess.class, ItemProcessListener.class, Object.class),
|
||||
AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, Object.class),
|
||||
ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class, Object.class, Exception.class),
|
||||
BEFORE_WRITE("beforeWrite", "before-write-method", BeforeWrite.class, ItemWriteListener.class, Object.class),
|
||||
AFTER_WRITE("afterWrite", "after-write-method", AfterWrite.class, ItemWriteListener.class, Object.class),
|
||||
ON_WRITE_ERROR("onWriteError", "on-write-error-method", OnWriteError.class, ItemWriteListener.class, Object.class, Exception.class),
|
||||
ON_SKIP_IN_READ("onSkipInRead", "on-skip-in-read-method", OnSkipInRead.class, SkipListener.class, Throwable.class),
|
||||
ON_SKIP_IN_PROCESS("onSkipInProcess", "on-skip-in-process-method", OnSkipInProcess.class, SkipListener.class, Object.class, Throwable.class),
|
||||
ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, Object.class, Throwable.class);
|
||||
|
||||
private final String methodName;
|
||||
private final String propertyName;
|
||||
private final Class<? extends Annotation> annotation;
|
||||
private final Class<? extends StepListener> listenerInterface;
|
||||
private final Class<?>[] paramTypes;
|
||||
private static final Map<String, StepListenerMetaData> propertyMap;
|
||||
|
||||
StepListenerMetaData(String methodName, Class<? extends Annotation> annotation, Class<? extends StepListener> listenerInterface, Class<?>... paramTypes) {
|
||||
StepListenerMetaData(String methodName, String propertyName, Class<? extends Annotation> annotation,
|
||||
Class<? extends StepListener> listenerInterface, Class<?>... paramTypes) {
|
||||
this.methodName = methodName;
|
||||
this.propertyName = propertyName;
|
||||
this.annotation = annotation;
|
||||
this.listenerInterface = listenerInterface;
|
||||
this.paramTypes = paramTypes;
|
||||
}
|
||||
|
||||
static{
|
||||
propertyMap = new HashMap<String, StepListenerMetaData>();
|
||||
for(StepListenerMetaData metaData : values()){
|
||||
propertyMap.put(metaData.getPropertyName(), metaData);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
@@ -96,4 +109,18 @@ public enum StepListenerMetaData {
|
||||
public Class<?>[] getParamTypes() {
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relevant meta data for the provided property name.
|
||||
*
|
||||
* @param propertyName
|
||||
* @return meta data with supplied property name, null if none exists.
|
||||
*/
|
||||
public static StepListenerMetaData fromPropertyName(String propertyName){
|
||||
return propertyMap.get(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="listener" type="listenerType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xsd:element name="listener" type="stepListenerType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
@@ -450,6 +450,28 @@
|
||||
<xsd:attribute name="ref" type="xsd:string"/>
|
||||
<xsd:attribute name="class" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="stepListenerType">
|
||||
<xsd:attribute name="id" type="xsd:ID"/>
|
||||
<xsd:attribute name="ref" type="xsd:string"/>
|
||||
<xsd:attribute name="class" type="xsd:string"/>
|
||||
<xsd:attribute name="before-step-method" type="xsd:string"/>
|
||||
<xsd:attribute name="after-step-method" type="xsd:string"/>
|
||||
<xsd:attribute name="before-chunk-method" type="xsd:string"/>
|
||||
<xsd:attribute name="after-chunk-method" type="xsd:string"/>
|
||||
<xsd:attribute name="before-read-method" type="xsd:string"/>
|
||||
<xsd:attribute name="after-read-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-read-error-method" type="xsd:string"/>
|
||||
<xsd:attribute name="before-process-method" type="xsd:string"/>
|
||||
<xsd:attribute name="after-process-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-process-error-method" type="xsd:string"/>
|
||||
<xsd:attribute name="before-write-method" type="xsd:string"/>
|
||||
<xsd:attribute name="after-write-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-write-error-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-skip-in-read-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-skip-in-process-method" type="xsd:string"/>
|
||||
<xsd:attribute name="on-skip-in-write-method" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="jobExecutionListenerType">
|
||||
<xsd:attribute name="before-method" type="xsd:string"></xsd:attribute>
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.batch.core.configuration.xml;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepExecutionListener;
|
||||
import org.springframework.batch.core.annotation.AfterRead;
|
||||
|
||||
public class TestListener extends AbstractTestComponent implements StepExecutionListener {
|
||||
|
||||
@@ -13,5 +14,14 @@ public class TestListener extends AbstractTestComponent implements StepExecution
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
executed = true;
|
||||
}
|
||||
|
||||
public void destroy(){
|
||||
|
||||
}
|
||||
|
||||
@AfterRead
|
||||
public void logItem(){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,9 +73,9 @@ public class StepListenerFactoryBeanTests {
|
||||
public void testStepAndChunk() throws Exception{
|
||||
|
||||
factoryBean.setDelegate(testClass);
|
||||
Map<StepListenerMetaData, String> metaDataMap = new HashMap<StepListenerMetaData, String>();;
|
||||
metaDataMap.put(AFTER_STEP, "destroy");
|
||||
metaDataMap.put(AFTER_CHUNK, "afterChunk");
|
||||
Map<String, String> metaDataMap = new HashMap<String, String>();;
|
||||
metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
|
||||
metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk");
|
||||
factoryBean.setMetaDataMap(metaDataMap);
|
||||
Object item = new Object();
|
||||
List<Object> items = new ArrayList<Object>();
|
||||
@@ -120,8 +120,8 @@ public class StepListenerFactoryBeanTests {
|
||||
//method name, that all three will be called
|
||||
ThreeStepExecutionListener delegate = new ThreeStepExecutionListener();
|
||||
factoryBean.setDelegate(delegate);
|
||||
Map<StepListenerMetaData, String> metaDataMap = new HashMap<StepListenerMetaData, String>();;
|
||||
metaDataMap.put(AFTER_STEP, "destroy");
|
||||
Map<String, String> metaDataMap = new HashMap<String, String>();;
|
||||
metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
|
||||
factoryBean.setMetaDataMap(metaDataMap);
|
||||
StepListener listener = (StepListener) factoryBean.getObject();
|
||||
((StepExecutionListener)listener).afterStep(stepExecution);
|
||||
@@ -132,8 +132,8 @@ public class StepListenerFactoryBeanTests {
|
||||
public void testAnnotatingInterfaceResultsInOneCall() throws Exception{
|
||||
MultipleAfterStep delegate = new MultipleAfterStep();
|
||||
factoryBean.setDelegate(delegate);
|
||||
Map<StepListenerMetaData, String> metaDataMap = new HashMap<StepListenerMetaData, String>();;
|
||||
metaDataMap.put(AFTER_STEP, "afterStep");
|
||||
Map<String, String> metaDataMap = new HashMap<String, String>();;
|
||||
metaDataMap.put(AFTER_STEP.getPropertyName(), "afterStep");
|
||||
factoryBean.setMetaDataMap(metaDataMap);
|
||||
StepListener listener = (StepListener) factoryBean.getObject();
|
||||
((StepExecutionListener)listener).afterStep(stepExecution);
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<item-task reader="reader" processor="processor" writer="writer"
|
||||
commit-interval="10">
|
||||
<listeners>
|
||||
<listener class="org.springframework.batch.core.configuration.xml.TestListener"/>
|
||||
<listener class="org.springframework.batch.core.configuration.xml.TestListener"
|
||||
after-step-method="destroy"/>
|
||||
<listener ref="listener"/>
|
||||
</listeners>
|
||||
<streams>
|
||||
@@ -28,5 +29,7 @@
|
||||
<beans:bean id="writer" class="org.springframework.batch.core.configuration.xml.TestWriter"/>
|
||||
|
||||
<beans:bean id="listener" class="org.springframework.batch.core.configuration.xml.TestListener"/>
|
||||
|
||||
<beans:bean id="listener2" class="org.springframework.batch.core.configuration.xml.TestListener"/>
|
||||
|
||||
</beans:beans>
|
||||
Reference in New Issue
Block a user