diff --git a/spring-batch-core/.settings/com.springsource.sts.config.flow.prefs b/spring-batch-core/.settings/com.springsource.sts.config.flow.prefs
index dd1e279a5..b0ce728e1 100644
--- a/spring-batch-core/.settings/com.springsource.sts.config.flow.prefs
+++ b/spring-batch-core/.settings/com.springsource.sts.config.flow.prefs
@@ -1,6 +1,7 @@
-#Thu Jul 08 16:57:57 BST 2010
+#Mon Jan 31 13:34:48 GMT 2011
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobExecutionListenerMethodAttributeParserTests-context.xml=\n\n\n\n\n\n
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryDefaultParserTests-context.xml=\n
+//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests-context.xml=\n\n\n\n\n\n
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerMethodAttributeParserTests-context.xml=\n\n\n\n\n\n
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepWithPojoListenerJobParserTests-context.xml=\n\n\n\n\n\n
//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/batch\:/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests-context.xml=\n\n\n\n\n\n\n\n\n\n
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
index eb4d7aa5d..b38ecc29d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java
@@ -15,6 +15,7 @@
*/
package org.springframework.batch.core.configuration.xml;
+import org.springframework.batch.core.listener.StepListenerMetaData;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -76,6 +77,8 @@ public abstract class AbstractStepParser {
private static final String JOB_REPO_ATTR = "job-repository";
+ private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.stepExecutionListenerMetaData());
+
/**
* @param stepElement The <step/> element
* @param parserContext
@@ -135,6 +138,8 @@ public abstract class AbstractStepParser {
bd.setDescription(description.getTextContent());
}
+ stepListenerParser.handleListenersElement(stepElement, bd, parserContext);
+
return bd;
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java
index cdb0d5324..80dad028e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java
@@ -17,6 +17,7 @@ package org.springframework.batch.core.configuration.xml;
import java.util.List;
+import org.springframework.batch.core.listener.StepListenerMetaData;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -58,6 +59,8 @@ public class ChunkElementParser {
private static final String ITEM_WRITER_ADAPTER_CLASS = "org.springframework.batch.item.adapter.ItemWriterAdapter";
+ private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.itemListenerMetaData());
+
/**
* @param element
* @param parserContext
@@ -141,6 +144,8 @@ public class ChunkElementParser {
handleRetryListenersElement(element, propertyValues, parserContext, bd);
handleStreamsElement(element, propertyValues, parserContext);
+
+ stepListenerParser.handleListenersElement(element, bd, parserContext);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepListenerParser.java
index c63a33e22..c85bbcc5d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepListenerParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepListenerParser.java
@@ -15,10 +15,19 @@
*/
package org.springframework.batch.core.configuration.xml;
+import java.util.List;
+
import org.springframework.batch.core.listener.AbstractListenerFactoryBean;
import org.springframework.batch.core.listener.ListenerMetaData;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
import org.springframework.batch.core.listener.StepListenerMetaData;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Element;
/**
* Parser for a step listener element. Builds a {@link StepListenerFactoryBean}
@@ -29,13 +38,54 @@ import org.springframework.batch.core.listener.StepListenerMetaData;
* @see AbstractListenerParser
*/
public class StepListenerParser extends AbstractListenerParser {
+
+ private static final String LISTENERS_ELE = "listeners";
+
+ private static final String MERGE_ATTR = "merge";
+
+ private final ListenerMetaData[] listenerMetaData;
+
+ public StepListenerParser() {
+ this(StepListenerMetaData.values());
+ }
+
+ public StepListenerParser(ListenerMetaData[] listenerMetaData) {
+ this.listenerMetaData = listenerMetaData;
+ }
protected Class extends AbstractListenerFactoryBean> getBeanClass() {
return StepListenerFactoryBean.class;
}
protected ListenerMetaData[] getMetaDataValues() {
- return StepListenerMetaData.values();
+ return listenerMetaData;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void handleListenersElement(Element stepElement, BeanDefinition beanDefinition,
+ ParserContext parserContext) {
+ MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
+ List listenersElements = DomUtils.getChildElementsByTagName(stepElement, LISTENERS_ELE);
+ if (listenersElements.size() == 1) {
+ Element listenersElement = listenersElements.get(0);
+ CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(),
+ parserContext.extractSource(stepElement));
+ parserContext.pushContainingComponent(compositeDef);
+ ManagedList listenerBeans = new ManagedList();
+ if (propertyValues.contains("listeners")) {
+ listenerBeans = (ManagedList) propertyValues.getPropertyValue("listeners").getValue();
+ }
+ listenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR)
+ && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR)));
+ List listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener");
+ if (listenerElements != null) {
+ for (Element listenerElement : listenerElements) {
+ listenerBeans.add(parse(listenerElement, parserContext));
+ }
+ }
+ propertyValues.addPropertyValue("listeners", listenerBeans);
+ parserContext.popAndRegisterContainingComponent();
+ }
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
index a8c9c674d..33909794e 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java
@@ -16,7 +16,14 @@
package org.springframework.batch.core.configuration.xml;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
import org.springframework.batch.classify.BinaryExceptionClassifier;
+import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
@@ -62,17 +69,13 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.util.Assert;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-
/**
* This {@link FactoryBean} is used by the batch namespace parser to create
* {@link Step} objects. Stores all of the properties that are configurable on
* the <step/> (and its inner <tasklet/>). Based on which properties
* are configured, the {@link #getObject()} method will delegate to the
* appropriate class for generating the {@link Step}.
- *
+ *
* @author Dan Garrette
* @author Josh Long
* @see SimpleStepFactoryBean
@@ -195,7 +198,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Create a {@link Step} from the configuration provided.
- *
+ *
* @see FactoryBean#getObject()
*/
public final Object getObject() throws Exception {
@@ -209,28 +212,34 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
configureSimple(fb);
configureFaultTolerant(fb);
return fb.getObject();
- } else {
+ }
+ else {
SimpleStepFactoryBean fb = new SimpleStepFactoryBean();
configureSimple(fb);
return fb.getObject();
}
- } else if (tasklet != null) {
+ }
+ else if (tasklet != null) {
TaskletStep ts = new TaskletStep();
configureTaskletStep(ts);
return ts;
- } else if (flow != null) {
+ }
+ else if (flow != null) {
FlowStep ts = new FlowStep();
configureFlowStep(ts);
return ts;
- } else if (job != null) {
+ }
+ else if (job != null) {
JobStep ts = new JobStep();
configureJobStep(ts);
return ts;
- } else if (step != null) {
+ }
+ else if (step != null) {
PartitionStep ts = new PartitionStep();
configurePartitionStep(ts);
return ts;
- } else {
+ }
+ else {
throw new IllegalStateException("Step [" + name
+ "] has neither a element nor a 'ref' attribute referencing a Tasklet.");
}
@@ -256,12 +265,13 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
ts.setStartLimit(startLimit);
}
if (listeners != null) {
- int i = 0;
- StepExecutionListener[] newListeners = new StepExecutionListener[listeners.length];
+ List newListeners = new ArrayList();
for (StepListener listener : listeners) {
- newListeners[i++] = (StepExecutionListener) listener;
+ if (listener instanceof StepExecutionListener) {
+ newListeners.add((StepExecutionListener) listener);
+ }
}
- ts.setStepExecutionListeners(newListeners);
+ ts.setStepExecutionListeners(newListeners.toArray(new StepExecutionListener[0]));
}
}
@@ -275,7 +285,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
if (partitionHandler != null) {
handler = partitionHandler;
ts.setPartitionHandler(partitionHandler);
- } else {
+ }
+ else {
TaskExecutorPartitionHandler partitionHandler = new TaskExecutorPartitionHandler();
partitionHandler.setStep(step);
if (taskExecutor == null) {
@@ -287,14 +298,14 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
handler = partitionHandler;
}
-
- // BATCH-1659
+ // BATCH-1659
if (handler instanceof TaskExecutorPartitionHandler) {
try {
TaskExecutorPartitionHandler taskExecutorPartitionHandler = (TaskExecutorPartitionHandler) handler;
taskExecutorPartitionHandler.setStep(step);
taskExecutorPartitionHandler.afterPropertiesSet();
- } catch (Exception e) {
+ }
+ catch (Exception e) {
throw new RuntimeException(e);
}
}
@@ -408,6 +419,15 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
@SuppressWarnings("serial")
private void configureTaskletStep(TaskletStep ts) {
configureAbstractStep(ts);
+ if (listeners != null) {
+ List newListeners = new ArrayList();
+ for (StepListener listener : listeners) {
+ if (listener instanceof ChunkListener) {
+ newListeners.add((ChunkListener) listener);
+ }
+ }
+ ts.setChunkListeners(newListeners.toArray(new ChunkListener[0]));
+ }
if (tasklet != null) {
ts.setTasklet(tasklet);
}
@@ -487,17 +507,17 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* Check if a field is present then a second is also. If the
* twoWayDependency flag is set then the opposite must also be true: if the
* second value is present, the first must also be.
- *
- * @param dependentName the name of the first field
- * @param dependentValue the value of the first field
- * @param name the name of the other field (which should be absent if the
- * first is present)
- * @param value the value of the other field
+ *
+ * @param dependentName the name of the first field
+ * @param dependentValue the value of the first field
+ * @param name the name of the other field (which should be absent if the
+ * first is present)
+ * @param value the value of the other field
* @param twoWayDependency true if both depend on each other
- * @throws IllegalArgumentException if eiether condition is violated
+ * @throws IllegalArgumentException if either condition is violated
*/
private void validateDependency(String dependentName, Object dependentValue, String name, Object value,
- boolean twoWayDependency) {
+ boolean twoWayDependency) {
if (isPresent(dependentValue) && !isPresent(value)) {
throw new IllegalArgumentException("The field '" + dependentName + "' is not permitted on the step ["
+ this.name + "] because there is no '" + name + "'.");
@@ -510,7 +530,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Is the object non-null (or if an Integer, non-zero)?
- *
+ *
* @param o an object
* @return true if the object has a value
*/
@@ -549,7 +569,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Set the bean name property, which will become the name of the
* {@link Step} when it is created.
- *
+ *
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
public void setBeanName(String name) {
@@ -624,7 +644,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* 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
*/
public void setAllowStartIfComplete(boolean allowStartIfComplete) {
@@ -641,7 +661,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Public setter for {@link JobRepository}.
- *
+ *
* @param jobRepository
*/
public void setJobRepository(JobRepository jobRepository) {
@@ -650,7 +670,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* The number of times that the step should be allowed to start
- *
+ *
* @param startLimit
*/
public void setStartLimit(int startLimit) {
@@ -659,7 +679,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* A preconfigured {@link Tasklet} to use.
- *
+ *
* @param tasklet
*/
public void setTasklet(Tasklet tasklet) {
@@ -688,7 +708,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* The listeners to inject into the {@link Step}. Any instance of
* {@link StepListener} can be used, and will then receive callbacks at the
* appropriate stage in the step.
- *
+ *
* @param listeners an array of listeners
*/
public void setListeners(StepListener[] listeners) {
@@ -698,7 +718,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Exception classes that may not cause a rollback if encountered in the
* right place.
- *
+ *
* @param noRollbackExceptionClasses the noRollbackExceptionClasses to set
*/
public void setNoRollbackExceptionClasses(Collection> noRollbackExceptionClasses) {
@@ -732,7 +752,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* A backoff policy to be applied to retry process.
- *
+ *
* @param backOffPolicy the {@link BackOffPolicy} to set
*/
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
@@ -742,7 +762,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* A retry policy to apply when exceptions occur. If this is specified then
* the retry limit and retryable exceptions will be ignored.
- *
+ *
* @param retryPolicy the {@link RetryPolicy} to set
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
@@ -760,7 +780,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* A key generator that can be used to compare items with previously
* recorded items in a retry. Only used if the reader is a transactional
* queue.
- *
+ *
* @param keyGenerator the {@link KeyGenerator} to set
*/
public void setKeyGenerator(KeyGenerator keyGenerator) {
@@ -781,9 +801,9 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* breach the limit in a single-threaded step typically you have to have
* this many failures in a single transaction. Defaults to the value in the
* {@link MapRetryContextCache}.
- *
+ *
* @param cacheCapacity the cache capacity to set (greater than 0 else
- * ignored)
+ * ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
@@ -794,7 +814,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* level. A transaction will be committed when this policy decides to
* complete. Defaults to a {@link SimpleCompletionPolicy} with chunk size
* equal to the commitInterval property.
- *
+ *
* @param chunkCompletionPolicy the chunkCompletionPolicy to set
*/
public void setChunkCompletionPolicy(CompletionPolicy chunkCompletionPolicy) {
@@ -804,7 +824,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Set the commit interval. Either set this or the chunkCompletionPolicy but
* not both.
- *
+ *
* @param commitInterval 1 by default
*/
public void setCommitInterval(int commitInterval) {
@@ -815,7 +835,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* 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) {
@@ -827,7 +847,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* should be called for every item in every transaction. If false then we
* can cache the processor results between transactions in the case of a
* rollback.
- *
+ *
* @param processorTransactional the value to set
*/
public void setProcessorTransactional(Boolean processorTransactional) {
@@ -838,7 +858,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* Public setter for the retry limit. Each item can be retried up to this
* limit. Note this limit includes the initial attempt to process the item,
* therefore retryLimit == 1 by default.
- *
+ *
* @param retryLimit the retry limit to set, must be greater or equal to 1.
*/
public void setRetryLimit(int retryLimit) {
@@ -851,7 +871,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* skipped and no exception propagated until the limit is reached. If it is
* zero then all exceptions will be propagated from the chunk and cause the
* step to abort.
- *
+ *
* @param skipLimit the value to set. Default is 0 (never skip).
*/
public void setSkipLimit(int skipLimit) {
@@ -861,7 +881,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Public setter for a skip policy. If this value is set then the skip limit
* and skippable exceptions are ignored.
- *
+ *
* @param skipPolicy the {@link SkipPolicy} to set
*/
public void setSkipPolicy(SkipPolicy skipPolicy) {
@@ -871,7 +891,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Public setter for the {@link TaskExecutor}. If this is set, then it will
* be used to execute the chunk processing inside the {@link Step}.
- *
+ *
* @param taskExecutor the taskExecutor to set
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
@@ -883,7 +903,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* queued for concurrent processing to prevent thread pools from being
* overwhelmed. Defaults to
* {@link TaskExecutorRepeatTemplate#DEFAULT_THROTTLE_LIMIT}.
- *
+ *
* @param throttleLimit the throttle limit to set.
*/
public void setThrottleLimit(Integer throttleLimit) {
@@ -917,7 +937,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Public setter for the {@link RetryListener}s.
- *
+ *
* @param retryListeners the {@link RetryListener}s to set
*/
public void setRetryListeners(RetryListener... retryListeners) {
@@ -928,7 +948,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* Public setter for exception classes that when raised won't crash the job
* but will result in transaction rollback and the item which handling
* caused the exception will be skipped.
- *
+ *
* @param exceptionClasses
*/
public void setSkippableExceptionClasses(Map, Boolean> exceptionClasses) {
@@ -937,7 +957,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
/**
* Public setter for exception classes that will retry the item when raised.
- *
+ *
* @param retryableExceptionClasses the retryableExceptionClasses to set
*/
public void setRetryableExceptionClasses(Map, Boolean> retryableExceptionClasses) {
@@ -948,7 +968,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
* The streams to inject into the {@link Step}. Any instance of
* {@link ItemStream} can be used, and will then receive callbacks at the
* appropriate stage in the step.
- *
+ *
* @param streams an array of listeners
*/
public void setStreams(ItemStream[] streams) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
index 00494f900..25b8e3971 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java
@@ -24,7 +24,6 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
-import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
@@ -43,6 +42,11 @@ import org.w3c.dom.Element;
*/
public class TaskletParser {
+ /**
+ *
+ */
+ private static final String TRANSACTION_MANAGER_ATTR = "transaction-manager";
+
private static final String TASKLET_REF_ATTR = "ref";
private static final String TASKLET_METHOD_ATTR = "method";
@@ -57,12 +61,11 @@ public class TaskletParser {
private static final String TX_ATTRIBUTES_ELE = "transaction-attributes";
- private static final String LISTENERS_ELE = "listeners";
-
private static final String MERGE_ATTR = "merge";
private static final ChunkElementParser chunkElementParser = new ChunkElementParser();
+ // TODO: BATCH-1689, make this StepListenerParser.taskletListenerMetaData()
private static final StepListenerParser stepListenerParser = new StepListenerParser();
public void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd,
@@ -83,17 +86,18 @@ public class TaskletParser {
validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef,
chunkElements, beanElements, refElements);
- if (chunkElements.size() == 1) {
+ if (!chunkElements.isEmpty()) {
chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified);
}
else {
BeanMetadataElement bme = null;
if (StringUtils.hasText(taskletRef)) {
- bme = new RuntimeBeanReference(taskletRef);
+ bme = new RuntimeBeanReference(taskletRef);
}
else if (beanElements.size() == 1) {
Element beanElement = beanElements.get(0);
- BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement(beanElement, bd);
+ BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement(
+ beanElement, bd);
parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder);
bme = beanDefinitionHolder;
}
@@ -181,7 +185,7 @@ public class TaskletParser {
MutablePropertyValues propertyValues = bd.getPropertyValues();
handleTaskletAttributes(taskletElement, propertyValues);
handleTransactionAttributesElement(taskletElement, propertyValues);
- handleListenersElement(taskletElement, propertyValues, parserContext);
+ stepListenerParser.handleListenersElement(taskletElement, bd, parserContext);
handleExceptionElement(taskletElement, parserContext, propertyValues, "no-rollback-exception-classes",
"noRollbackExceptionClasses");
bd.setRole(BeanDefinition.ROLE_SUPPORT);
@@ -237,7 +241,7 @@ public class TaskletParser {
}
private void handleTaskletAttributes(Element taskletElement, MutablePropertyValues propertyValues) {
- String transactionManagerRef = taskletElement.getAttribute("transaction-manager");
+ String transactionManagerRef = taskletElement.getAttribute(TRANSACTION_MANAGER_ATTR);
if (StringUtils.hasText(transactionManagerRef)) {
propertyValues.addPropertyValue("transactionManager", new RuntimeBeanReference(transactionManagerRef));
}
@@ -260,27 +264,4 @@ public class TaskletParser {
}
}
- @SuppressWarnings("unchecked")
- private void handleListenersElement(Element stepElement, MutablePropertyValues propertyValues,
- ParserContext parserContext) {
- List listenersElements = DomUtils.getChildElementsByTagName(stepElement, LISTENERS_ELE);
- if (listenersElements.size() == 1) {
- Element listenersElement = listenersElements.get(0);
- CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(),
- parserContext.extractSource(stepElement));
- parserContext.pushContainingComponent(compositeDef);
- ManagedList listenerBeans = new ManagedList();
- listenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR)
- && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR)));
- List listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener");
- if (listenerElements != null) {
- for (Element listenerElement : listenerElements) {
- listenerBeans.add(stepListenerParser.parse(listenerElement, parserContext));
- }
- }
- propertyValues.addPropertyValue("listeners", listenerBeans);
- parserContext.popAndRegisterContainingComponent();
- }
- }
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
index 3ce781154..3bd6bb8c1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java
@@ -21,8 +21,8 @@ import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvo
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
@@ -88,23 +88,56 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
// For every entry in the map, try and find a method by interface, name,
// or annotation. If the same
Map> invokerMap = new HashMap>();
+ boolean synthetic = false;
for (Entry entry : metaDataMap.entrySet()) {
+
final ListenerMetaData metaData = this.getMetaDataFromPropertyName(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(metaData.getAnnotation(), delegate, metaData.getParamTypes()));
+ Set invokers = new HashSet();
+
+ MethodInvoker invoker;
+
+ invoker = getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate,
+ metaData.getParamTypes());
+ if (invoker != null) {
+ invokers.add(invoker);
+ }
+
+ invoker = getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes());
+ if (invoker != null) {
+ invokers.add(invoker);
+ synthetic = true;
+ }
+
+ invoker = getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes());
+ if (invoker != null) {
+ invokers.add(invoker);
+ synthetic = true;
+ }
+
if (!invokers.isEmpty()) {
invokerMap.put(metaData.getMethodName(), invokers);
listenerInterfaces.add(metaData.getListenerInterface());
}
+
}
if (listenerInterfaces.isEmpty()) {
listenerInterfaces.add(this.getDefaultListenerClass());
}
+ if (!synthetic) {
+ int count = 0;
+ for (Class> listenerInterface : listenerInterfaces) {
+ if (listenerInterface.isInstance(delegate)) {
+ count++;
+ }
+ }
+ // All listeners can be supplied by the delegate itself
+ if (count == listenerInterfaces.size()) {
+ return delegate;
+ }
+ }
+
boolean ordered = false;
if (delegate instanceof Ordered) {
ordered = true;
@@ -153,23 +186,6 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
this.metaDataMap = metaDataMap;
}
- /*
- * Extension of HashSet that ignores nulls, rather than putting them into
- * the set.
- */
- protected static class NullIgnoringSet extends HashSet {
-
- @Override
- public boolean add(E e) {
- if (e == null) {
- return false;
- }
- else {
- return super.add(e);
- }
- };
- }
-
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "Delegate must not be null");
}
@@ -191,7 +207,8 @@ public abstract class AbstractListenerFactoryBean implements FactoryBean, Initia
}
if (target instanceof Advised) {
TargetSource targetSource = ((Advised) target).getTargetSource();
- if (targetSource!=null && targetSource.getTargetClass()!=null && listenerType.isAssignableFrom(targetSource.getTargetClass())) {
+ if (targetSource != null && targetSource.getTargetClass() != null
+ && listenerType.isAssignableFrom(targetSource.getTargetClass())) {
return true;
}
}
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
index 518a1f9df..753d7d21f 100644
--- 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
@@ -123,4 +123,17 @@ public enum StepListenerMetaData implements ListenerMetaData {
public static StepListenerMetaData fromPropertyName(String propertyName){
return propertyMap.get(propertyName);
}
+
+ public static ListenerMetaData[] itemListenerMetaData() {
+ return new ListenerMetaData[] {BEFORE_WRITE, AFTER_WRITE, ON_WRITE_ERROR, BEFORE_PROCESS, AFTER_PROCESS, ON_PROCESS_ERROR, BEFORE_READ, AFTER_READ, ON_READ_ERROR, ON_SKIP_IN_WRITE, ON_SKIP_IN_PROCESS, ON_SKIP_IN_READ};
+ }
+
+ public static ListenerMetaData[] stepExecutionListenerMetaData() {
+ return new ListenerMetaData[] {BEFORE_STEP, AFTER_STEP};
+ }
+
+ public static ListenerMetaData[] taskletListenerMetaData() {
+ return new ListenerMetaData[] {BEFORE_CHUNK, AFTER_CHUNK};
+ }
+
}
diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
index 090753d96..dd32ab261 100644
--- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
+++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
@@ -447,6 +447,7 @@
+
@@ -805,6 +806,7 @@
+
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java
new file mode 100644
index 000000000..7ed82022e
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java
@@ -0,0 +1,128 @@
+/*
+ * 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.configuration.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.aop.framework.Advised;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.listener.ChunkListenerSupport;
+import org.springframework.batch.core.listener.ItemListenerSupport;
+import org.springframework.batch.core.listener.StepExecutionListenerSupport;
+import org.springframework.batch.core.step.tasklet.TaskletStep;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ * @author Dan Garrette
+ * @since 2.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class StepListenerInStepParserTests {
+
+ @Autowired
+ private BeanFactory beanFactory;
+
+ @Test
+ public void testListenersAtStepLevel() throws Exception {
+ Step step = (Step) beanFactory.getBean("s1");
+ List> list = getListeners(step);
+ assertEquals(1, list.size());
+ assertTrue(list.get(0) instanceof StepExecutionListenerSupport);
+ }
+
+ @Test
+ // TODO: BATCH-1689 (expected=BeanCreationException.class)
+ public void testListenersAtStepLevelWrongType() throws Exception {
+ Step step = (Step) beanFactory.getBean("s2");
+ List> list = getListeners(step);
+ assertEquals(1, list.size());
+ assertTrue(list.get(0) instanceof ChunkListenerSupport);
+ }
+
+ @Test
+ public void testListenersAtTaskletAndStepLevels() throws Exception {
+ Step step = (Step) beanFactory.getBean("s3");
+ List> list = getListeners(step);
+ assertEquals(2, list.size());
+ assertTrue(list.get(0) instanceof StepExecutionListenerSupport);
+ assertTrue(list.get(1) instanceof ChunkListenerSupport);
+ }
+
+ @Test
+ public void testListenersAtChunkAndStepLevels() throws Exception {
+ Step step = (Step) beanFactory.getBean("s4");
+ List> list = getListeners(step);
+ assertEquals(2, list.size());
+ assertTrue(list.get(0) instanceof StepExecutionListenerSupport);
+ assertTrue(list.get(1) instanceof ItemListenerSupport);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List> getListeners(Step step) throws Exception {
+ assertTrue(step instanceof TaskletStep);
+
+ Object compositeListener = ReflectionTestUtils.getField(step, "stepExecutionListener");
+ Object composite = ReflectionTestUtils.getField(compositeListener, "list");
+ List proxiedListeners = (List) ReflectionTestUtils.getField(
+ composite, "list");
+ List