BATCH-1509: added parser and XSD changes

fixed a comment...
This commit is contained in:
Josh Long
2010-12-02 01:28:09 -08:00
committed by Dave Syer
parent 83ab17157f
commit fdd153db9f
15 changed files with 231 additions and 148 deletions

View File

@@ -40,6 +40,9 @@
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId><artifactId>h2</artifactId><version>1.2.132</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>

View File

@@ -17,7 +17,7 @@ package org.springframework.batch.core;
/**
* Batch domain interface representing the configuration of a step. As with the {@link Job}, a {@link Step} is meant to
* explicitly represent a the configuration of a step by a developer, but also the ability to execute the step.
* explicitly represent the configuration of a step by a developer, but also the ability to execute the step.
*
* @author Dave Syer
*
@@ -52,4 +52,4 @@ public interface Step {
*/
void execute(StepExecution stepExecution) throws JobInterruptedException;
}
}

View File

@@ -33,11 +33,11 @@ import org.w3c.dom.Element;
* {@link org.springframework.batch.core.Step} and goes on to (optionally) list
* a set of transitions from that step to others with &lt;next on="pattern"
* to="stepName"/&gt;. Used by the {@link JobParser}.
*
* @see JobParser
*
*
* @author Dave Syer
* @author Thomas Risberg
* @author Josh Long
* @see JobParser
* @since 2.0
*/
public abstract class AbstractStepParser {
@@ -60,6 +60,8 @@ public abstract class AbstractStepParser {
private static final String STEP_ATTR = "step";
private static final String STEP_ELE = STEP_ATTR;
private static final String PARTITIONER_ATTR = "partitioner";
private static final String HANDLER_ATTR = "handler";
@@ -75,10 +77,10 @@ public abstract class AbstractStepParser {
private static final String JOB_REPO_ATTR = "job-repository";
/**
* @param stepElement The &lt;step/&gt; element
* @param stepElement The &lt;step/&gt; element
* @param parserContext
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag. Use 'null' if unknown.
* from the enclosing tag. Use 'null' if unknown.
*/
protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobFactoryRef) {
@@ -100,7 +102,7 @@ public abstract class AbstractStepParser {
Element partitionElement = DomUtils.getChildElementByTagName(stepElement, PARTITION_ELE);
if (partitionElement != null) {
boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
parsePartition(stepElement, partitionElement, bd, parserContext, stepUnderspecified);
parsePartition(stepElement, partitionElement, bd, parserContext, stepUnderspecified, jobFactoryRef);
}
Element jobElement = DomUtils.getChildElementByTagName(stepElement, JOB_ELE);
@@ -137,8 +139,7 @@ public abstract class AbstractStepParser {
}
private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef ) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
@@ -146,17 +147,27 @@ public abstract class AbstractStepParser {
String partitionerRef = partitionElement.getAttribute(PARTITIONER_ATTR);
String handlerRef = partitionElement.getAttribute(HANDLER_ATTR);
if (!StringUtils.hasText(stepRef)) {
parserContext.getReaderContext().error("You must specify a step", partitionElement);
return;
}
if (!StringUtils.hasText(partitionerRef)) {
parserContext.getReaderContext().error("You must specify a partitioner", partitionElement);
return;
}
Element inlineStepElement = DomUtils.getChildElementByTagName(partitionElement, STEP_ELE);
if (inlineStepElement == null && !StringUtils.hasText(stepRef)) {
parserContext.getReaderContext().error("You must specify a step", partitionElement);
return;
}
MutablePropertyValues propertyValues = bd.getPropertyValues();
propertyValues.addPropertyValue("step", new RuntimeBeanReference(stepRef));
if (StringUtils.hasText(stepRef)) {
propertyValues.addPropertyValue("step", new RuntimeBeanReference(stepRef));
} else if( inlineStepElement!=null) {
AbstractBeanDefinition stepDefinition = parseStep(inlineStepElement, parserContext, jobFactoryRef);
propertyValues.addPropertyValue("step", stepDefinition );
}
propertyValues.addPropertyValue("partitioner", new RuntimeBeanReference(partitionerRef));
if (!StringUtils.hasText(handlerRef)) {
@@ -171,15 +182,13 @@ public abstract class AbstractStepParser {
propertyValues.addPropertyValue("gridSize", new TypedStringValue(gridSize));
}
}
}
else {
} else {
propertyValues.addPropertyValue("partitionHandler", new RuntimeBeanReference(handlerRef));
}
}
private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);
@@ -207,7 +216,7 @@ public abstract class AbstractStepParser {
private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd,
ParserContext parserContext, boolean stepUnderspecified) {
ParserContext parserContext, boolean stepUnderspecified) {
bd.setBeanClass(StepParserStepFactoryBean.class);
bd.setAttribute("isNamespaceStep", true);

View File

@@ -32,8 +32,7 @@ public class BeanDefinitionUtils {
* @return The {@link PropertyValue} for the property of the bean. Search
* parent hierarchy if necessary. Return null if none is found.
*/
public static PropertyValue getPropertyValue(String beanName, String propertyName,
ConfigurableListableBeanFactory beanFactory) {
public static PropertyValue getPropertyValue(String beanName, String propertyName, ConfigurableListableBeanFactory beanFactory) {
return beanFactory.getMergedBeanDefinition(beanName).getPropertyValues().getPropertyValue(propertyName);
}

View File

@@ -19,14 +19,14 @@ import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
*
*
* @author Dave Syer
*
*/
public class CoreNamespaceHandler extends NamespaceHandlerSupport {
/**
*
*
* @see NamespaceHandler#init()
*/
public void init() {

View File

@@ -16,10 +16,6 @@
package org.springframework.batch.core.configuration.xml;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import org.springframework.batch.classify.BinaryExceptionClassifier;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
@@ -66,18 +62,23 @@ 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 &lt;step/&gt; (and its inner &lt;tasklet/&gt;). Based on which properties
* are configured, the {@link #getObject()} method will delegate to the
* appropriate class for generating the {@link Step}.
*
*
* @author Dan Garrette
* @since 2.0
* @author Josh Long
* @see SimpleStepFactoryBean
* @see FaultTolerantStepFactoryBean
* @see TaskletStep
* @since 2.0
*/
class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
@@ -194,7 +195,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* Create a {@link Step} from the configuration provided.
*
*
* @see FactoryBean#getObject()
*/
public final Object getObject() throws Exception {
@@ -208,34 +209,28 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
configureSimple(fb);
configureFaultTolerant(fb);
return fb.getObject();
}
else {
} else {
SimpleStepFactoryBean<I, O> fb = new SimpleStepFactoryBean<I, O>();
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 <chunk/> element nor a 'ref' attribute referencing a Tasklet.");
}
@@ -274,10 +269,13 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
Assert.state(partitioner != null, "A Partitioner must be provided for a partition step");
Assert.state(step != null, "A Step must be provided for a partition step");
configureAbstractStep(ts);
PartitionHandler handler;
if (partitionHandler != null) {
handler = partitionHandler;
ts.setPartitionHandler(partitionHandler);
}
else {
} else {
TaskExecutorPartitionHandler partitionHandler = new TaskExecutorPartitionHandler();
partitionHandler.setStep(step);
if (taskExecutor == null) {
@@ -286,7 +284,21 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
partitionHandler.setGridSize(gridSize);
partitionHandler.setTaskExecutor(taskExecutor);
ts.setPartitionHandler(partitionHandler);
handler = partitionHandler;
}
// BATCH-1659
if (handler instanceof TaskExecutorPartitionHandler) {
try {
TaskExecutorPartitionHandler taskExecutorPartitionHandler = (TaskExecutorPartitionHandler) handler;
taskExecutorPartitionHandler.setStep(step);
taskExecutorPartitionHandler.afterPropertiesSet();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
SimpleStepExecutionSplitter splitter = new SimpleStepExecutionSplitter(jobRepository, step, partitioner);
ts.setStepExecutionSplitter(splitter);
}
@@ -475,18 +487,17 @@ class StepParserStepFactoryBean<I, O> 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
*/
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 + "'.");
@@ -499,7 +510,7 @@ class StepParserStepFactoryBean<I, O> 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
*/
@@ -538,7 +549,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -562,9 +573,6 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
// Job Attributes
// =========================================================
/**
* @param flow the flow to set
*/
public void setJob(Job job) {
this.job = job;
}
@@ -616,7 +624,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -633,7 +641,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* Public setter for {@link JobRepository}.
*
*
* @param jobRepository
*/
public void setJobRepository(JobRepository jobRepository) {
@@ -642,7 +650,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* The number of times that the step should be allowed to start
*
*
* @param startLimit
*/
public void setStartLimit(int startLimit) {
@@ -651,7 +659,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* A preconfigured {@link Tasklet} to use.
*
*
* @param tasklet
*/
public void setTasklet(Tasklet tasklet) {
@@ -680,7 +688,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -690,7 +698,7 @@ class StepParserStepFactoryBean<I, O> 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<Class<? extends Throwable>> noRollbackExceptionClasses) {
@@ -724,7 +732,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* A backoff policy to be applied to retry process.
*
*
* @param backOffPolicy the {@link BackOffPolicy} to set
*/
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
@@ -734,7 +742,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -752,7 +760,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -768,14 +776,14 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
* items than this fail without being skipped or recovered an exception will
* be thrown. This is to guard against inadvertent infinite loops generated
* by item identity problems.<br/>
*
* <p/>
* The default value should be high enough and more for most purposes. To
* 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}.<br/>
*
*
* @param cacheCapacity the cache capacity to set (greater than 0 else
* ignored)
* ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
@@ -786,7 +794,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -796,7 +804,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -807,7 +815,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -819,7 +827,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -830,7 +838,7 @@ class StepParserStepFactoryBean<I, O> 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 <code>retryLimit == 1</code> by default.
*
*
* @param retryLimit the retry limit to set, must be greater or equal to 1.
*/
public void setRetryLimit(int retryLimit) {
@@ -843,7 +851,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -853,7 +861,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -863,7 +871,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -875,7 +883,7 @@ class StepParserStepFactoryBean<I, O> 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) {
@@ -909,7 +917,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
/**
* Public setter for the {@link RetryListener}s.
*
*
* @param retryListeners the {@link RetryListener}s to set
*/
public void setRetryListeners(RetryListener... retryListeners) {
@@ -920,7 +928,7 @@ class StepParserStepFactoryBean<I, O> 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<Class<? extends Throwable>, Boolean> exceptionClasses) {
@@ -929,7 +937,7 @@ class StepParserStepFactoryBean<I, O> 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<Class<? extends Throwable>, Boolean> retryableExceptionClasses) {
@@ -940,7 +948,7 @@ class StepParserStepFactoryBean<I, O> 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) {

View File

@@ -143,9 +143,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
/*
* (non-Javadoc)
*
* @see
* org.springframework.batch.core.launch.JobOperator#getExecutions(java.
* lang.Long)
* @see org.springframework.batch.core.launch.JobOperator#getExecutions(java.lang.Long)
*/
public List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException {
JobInstance jobInstance = jobExplorer.getJobInstance(instanceId);
@@ -251,8 +249,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
* @see
* org.springframework.batch.core.launch.JobOperator#resume(java.lang.Long)
*/
public Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
NoSuchJobException, JobRestartException, JobParametersInvalidException {
public Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException {
logger.info("Checking status of job execution with id=" + executionId);

View File

@@ -79,8 +79,7 @@ public enum StepListenerMetaData implements ListenerMetaData {
private final Class<?>[] paramTypes;
private static final Map<String, StepListenerMetaData> propertyMap;
StepListenerMetaData(String methodName, String propertyName, 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;

View File

@@ -54,7 +54,7 @@ public class TaskExecutorPartitionHandler implements PartitionHandler, Initializ
private Step step;
public void afterPropertiesSet() throws Exception {
Assert.notNull(step, "A Step must be provided.");
// Assert.notNull(step, "A Step must be provided.");
}
/**

View File

@@ -26,8 +26,8 @@ import java.util.List;
* Encapsulation of a list of items to be processed and possibly a list of
* failed items to be skipped. To mark an item as skipped clients should iterate
* over the chunk using the {@link #iterator()} method, and if there is a
* failure call {@link ChunkIterator#remove(Exception)} on the iterator. The
* skipped items are then available through the chunk.
* failure call {@link org.springframework.batch.core.step.item.Chunk.ChunkIterator#remove()} on the iterator.
* The skipped items are then available through the chunk.
*
* @author Dave Syer
* @since 2.0
@@ -246,4 +246,4 @@ public class Chunk<W> implements Iterable<W> {
}
}
}

View File

@@ -453,6 +453,7 @@
<xsd:complexType name="partitionType">
<xsd:all>
<xsd:element name="step" type="stepType" minOccurs="0" maxOccurs="1" />
<xsd:element name="handler" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -25,75 +26,126 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.*;
import org.springframework.batch.core.partition.support.PartitionStep;
import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
* @author Josh Long
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PartitionStepParserTests {
@Autowired
@Qualifier("job1")
private Job job1;
public class PartitionStepParserTests implements ApplicationContextAware {
@Autowired
@Qualifier("job2")
private Job job2;
@Autowired
@Qualifier("job1")
private Job job1;
@Autowired
private JobRepository jobRepository;
@Autowired
@Qualifier("job2")
private Job job2;
@Autowired
private MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean;
@Before
public void setUp() {
mapJobRepositoryFactoryBean.clear();
}
@Autowired
@Qualifier("job3")
private Job job3;
@Test
public void testDefaultHandlerStep() throws Exception {
assertNotNull(job1);
JobExecution jobExecution = jobRepository.createJobExecution(job1.getName(), new JobParameters());
job1.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
List<String> stepNames = getStepNames(jobExecution);
assertEquals(3, stepNames.size());
assertEquals("[s1, step1:partition0, step1:partition1]", stepNames.toString());
}
@Autowired
private JobRepository jobRepository;
@Test
public void testHandlerRefStep() throws Exception {
assertNotNull(job2);
JobExecution jobExecution = jobRepository.createJobExecution(job2.getName(), new JobParameters());
job2.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
List<String> stepNames = getStepNames(jobExecution);
assertEquals(5, stepNames.size());
assertEquals("[s2, s3, step1:partition0, step1:partition1, step1:partition2]", stepNames.toString());
}
@Autowired
private MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean;
private List<String> getStepNames(JobExecution jobExecution) {
List<String> list = new ArrayList<String>();
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
list.add(stepExecution.getStepName());
}
Collections.sort(list);
return list;
}
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Before
public void setUp() {
mapJobRepositoryFactoryBean.clear();
}
@SuppressWarnings("unchecked")
private <T> T accessPrivateField(Object o, String fieldName) {
Field field = ReflectionUtils.findField(o.getClass(), fieldName);
boolean previouslyAccessibleValue = field.isAccessible();
field.setAccessible(true);
T val = (T) ReflectionUtils.getField(field, o);
field.setAccessible(previouslyAccessibleValue);
return val;
}
/**
* BATCH-1509 we now support the ability define steps inline for partitioned steps.
* this demonstates that the execution proceeds as expected and that the partitionhandler has a reference to the inline step definition
*/
@Test
public void testNestedPartitionStepStepReference() throws Throwable {
assertNotNull("the reference to the job3 configured in the XML file must not be null", job3);
JobExecution jobExecution = jobRepository.createJobExecution(job3.getName(), new JobParameters());
job3.execute(jobExecution);
for (StepExecution se : jobExecution.getStepExecutions()) {
String stepExecutionName = se.getStepName();
if (stepExecutionName.equalsIgnoreCase("j3s1")) { // the partitioned step
PartitionStep partitionStep = (PartitionStep) this.applicationContext.getBean(stepExecutionName);
// prove that the reference in the {@link TaskExecutorPartitionHandler} is the step configured inline
TaskExecutorPartitionHandler taskExecutorPartitionHandler = accessPrivateField(partitionStep, "partitionHandler");
TaskletStep taskletStep = accessPrivateField(taskExecutorPartitionHandler, "step");
assertNotNull("the taskletStep wasn't configured with a step. " +
"We're trusting that the factory ensured " +
"a reference was given.", taskletStep);
}
}
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
}
@Test
public void testDefaultHandlerStep() throws Exception {
assertNotNull(job1);
JobExecution jobExecution = jobRepository.createJobExecution(job1.getName(), new JobParameters());
job1.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
List<String> stepNames = getStepNames(jobExecution);
assertEquals(3, stepNames.size());
assertEquals("[s1, step1:partition0, step1:partition1]", stepNames.toString());
}
@Test
public void testHandlerRefStep() throws Exception {
assertNotNull(job2);
JobExecution jobExecution = jobRepository.createJobExecution(job2.getName(), new JobParameters());
job2.execute(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
List<String> stepNames = getStepNames(jobExecution);
assertEquals(5, stepNames.size());
assertEquals("[s2, s3, step1:partition0, step1:partition1, step1:partition2]", stepNames.toString());
}
private List<String> getStepNames(JobExecution jobExecution) {
List<String> list = new ArrayList<String>();
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
list.add(stepExecution.getStepName());
}
Collections.sort(list);
return list;
}
}

View File

@@ -81,11 +81,9 @@ public class StepListenerFactoryBeanTests {
@Test
@SuppressWarnings("unchecked")
public void testStepAndChunk() throws Exception {
TestListener testListener = new TestListener();
factoryBean.setDelegate(testListener);
Map<String, String> metaDataMap = new HashMap<String, String>();
;
metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk");
factoryBean.setMetaDataMap(metaDataMap);

View File

@@ -21,6 +21,15 @@
<step id="s3" parent="step2"/>
</job>
<job id="job3">
<step id="j3s1" >
<partition handler="handler1" partitioner="partitioner">
<step parent="step2"/>
</partition>
</step>
</job>
<bean id="handler"
class="org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler"
xmlns="http://www.springframework.org/schema/beans">
@@ -34,4 +43,12 @@
<beans:bean id="partitioner"
class="org.springframework.batch.core.partition.support.SimplePartitioner" />
<!--
for the inline case, demonstrates that we correctly ensure a reference to a step for both the default handler setup or a custom PartitionHandler
-->
<beans:bean id="handler1" class="org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler">
<!-- nb: we're not configuring a step! -->
<beans:property name="gridSize" value="10" />
</beans:bean>
</beans:beans>

View File

@@ -26,7 +26,7 @@ import java.util.TreeSet;
* A {@link Classifier} for a parameterised object type based on a map.
* Classifies objects according to their inheritance relation with the supplied
* type map. If the object to be classified is one of the keys of the provided
* map, or is a subclass of one of the keys, then the map entry vale for that
* map, or is a subclass of one of the keys, then the map entry value for that
* key is returned. Otherwise returns the default value which is null by
* default.
*