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:
lucasward
2008-11-24 06:43:39 +00:00
parent ae730065c7
commit 370b277e0f
7 changed files with 164 additions and 35 deletions

View File

@@ -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) {

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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(){
}
}

View File

@@ -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);

View File

@@ -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>