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 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 r = new ArrayList(); + for (Object listener : proxiedListeners) { + while (listener instanceof Advised) { + listener = ((Advised) listener).getTargetSource().getTarget(); + } + r.add(listener); + } + compositeListener = ReflectionTestUtils.getField(step, "chunkListener"); + composite = ReflectionTestUtils.getField(compositeListener, "listeners"); + proxiedListeners = (List) ReflectionTestUtils.getField(composite, "list"); + for (Object listener : proxiedListeners) { + while (listener instanceof Advised) { + listener = ((Advised) listener).getTargetSource().getTarget(); + } + r.add(listener); + } + try { + compositeListener = ReflectionTestUtils.getField( + ReflectionTestUtils.getField(ReflectionTestUtils.getField( + ReflectionTestUtils.getField(step, "tasklet"), "chunkProvider"), "listener"), + "itemReadListener"); + composite = ReflectionTestUtils.getField(compositeListener, "listeners"); + proxiedListeners = (List) ReflectionTestUtils.getField(composite, "list"); + for (Object listener : proxiedListeners) { + while (listener instanceof Advised) { + listener = ((Advised) listener).getTargetSource().getTarget(); + } + r.add(listener); + } + } + catch (IllegalArgumentException e) { + // ignore (not a chunk oriented step) + } + return r; + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests-context.xml new file mode 100644 index 000000000..7570744c9 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests-context.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file