diff --git a/archetypes/simple-cli/src/main/resources/META-INF/spring/module-context.xml b/archetypes/simple-cli/src/main/resources/META-INF/spring/module-context.xml
index 7e2e8b24f..81050a616 100644
--- a/archetypes/simple-cli/src/main/resources/META-INF/spring/module-context.xml
+++ b/archetypes/simple-cli/src/main/resources/META-INF/spring/module-context.xml
@@ -5,9 +5,12 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
Example job to get you started. It provides a
- skeleton for a typical batch application.
+ skeleton for
+ a typical batch application.
+
-
+
@@ -17,13 +20,12 @@
-
-
-
-
+
+
+
+
diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java
index 64fd0a17c..ffc9d58e3 100644
--- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java
+++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java
@@ -23,7 +23,7 @@ import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java
index 2ba066f78..c8a290b75 100644
--- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java
+++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java
@@ -26,7 +26,7 @@ import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanRollbackTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanRollbackTests.java
index 033547deb..cfd47fa1a 100644
--- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanRollbackTests.java
+++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanRollbackTests.java
@@ -22,7 +22,7 @@ import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
-import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanTests.java
index 58ca3a330..6044d1f13 100644
--- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanTests.java
+++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/step/MapRepositoryFaultTolerantStepFactoryBeanTests.java
@@ -20,7 +20,7 @@ import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
-import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
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 1600f8ba9..d62486d2a 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,37 +16,41 @@
package org.springframework.batch.core.configuration.xml;
-import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Set;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.aop.framework.Advised;
import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.ItemProcessListener;
+import org.springframework.batch.core.ItemReadListener;
+import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.core.Job;
+import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.job.flow.Flow;
-import org.springframework.batch.core.job.flow.FlowStep;
import org.springframework.batch.core.launch.JobLauncher;
-import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.partition.PartitionHandler;
-import org.springframework.batch.core.partition.support.PartitionStep;
import org.springframework.batch.core.partition.support.Partitioner;
-import org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter;
import org.springframework.batch.core.partition.support.StepExecutionAggregator;
-import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler;
import org.springframework.batch.core.repository.JobRepository;
-import org.springframework.batch.core.step.AbstractStep;
-import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder;
+import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder;
+import org.springframework.batch.core.step.builder.FlowStepBuilder;
+import org.springframework.batch.core.step.builder.JobStepBuilder;
+import org.springframework.batch.core.step.builder.PartitionStepBuilder;
+import org.springframework.batch.core.step.builder.SimpleStepBuilder;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.batch.core.step.builder.StepBuilderHelper;
+import org.springframework.batch.core.step.builder.TaskletStepBuilder;
+import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
+import org.springframework.batch.core.step.factory.SimpleStepFactoryBean;
import org.springframework.batch.core.step.item.KeyGenerator;
-import org.springframework.batch.core.step.item.SimpleStepFactoryBean;
import org.springframework.batch.core.step.job.JobParametersExtractor;
-import org.springframework.batch.core.step.job.JobStep;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.tasklet.TaskletStep;
@@ -60,7 +64,6 @@ import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.classify.BinaryExceptionClassifier;
-import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.retry.RetryListener;
import org.springframework.retry.RetryPolicy;
@@ -74,11 +77,9 @@ import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.util.Assert;
/**
- * 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}.
+ * 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
@@ -89,8 +90,6 @@ import org.springframework.util.Assert;
*/
class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
- private static final Log logger = LogFactory.getLog(StepParserStepFactoryBean.class);
-
//
// Step Attributes
//
@@ -109,6 +108,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
private PlatformTransactionManager transactionManager;
+ private Set stepExecutionListeners = new LinkedHashSet();
+
//
// Flow Elements
//
@@ -139,8 +140,6 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
//
// Tasklet Elements
//
- private StepListener[] listeners;
-
private Collection> noRollbackExceptionClasses;
private Integer transactionTimeout;
@@ -149,10 +148,12 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
private Isolation isolation;
+ private Set chunkListeners = new LinkedHashSet();
+
//
// Chunk Attributes
//
- private Integer cacheCapacity;
+ private int cacheCapacity = 0;
private CompletionPolicy chunkCompletionPolicy;
@@ -162,7 +163,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
private Boolean processorTransactional;
- private Integer retryLimit;
+ private int retryLimit = 0;
private BackOffPolicy backOffPolicy;
@@ -191,12 +192,20 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
//
private RetryListener[] retryListeners;
- private Map, Boolean> skippableExceptionClasses;
+ private Map, Boolean> skippableExceptionClasses = new HashMap, Boolean>();
- private Map, Boolean> retryableExceptionClasses;
+ private Map, Boolean> retryableExceptionClasses = new HashMap, Boolean>();
private ItemStream[] streams;
+ private Set> readListeners = new LinkedHashSet>();
+
+ private Set> writeListeners = new LinkedHashSet>();
+
+ private Set> processListeners = new LinkedHashSet>();
+
+ private Set> skipListeners = new LinkedHashSet>();
+
//
// Additional
//
@@ -204,6 +213,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
private StepExecutionAggregator stepExecutionAggregator;
+ private StepListener[] listeners;
+
/**
* Create a {@link Step} from the configuration provided.
*
@@ -216,36 +227,23 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
validateFaultTolerantSettings();
if (isFaultTolerant()) {
- FaultTolerantStepFactoryBean fb = new FaultTolerantStepFactoryBean();
- configureSimple(fb);
- configureFaultTolerant(fb);
- return fb.getObject();
+ return createFaultTolerantStep();
}
else {
- SimpleStepFactoryBean fb = new SimpleStepFactoryBean();
- configureSimple(fb);
- return fb.getObject();
+ return createSimpleStep();
}
}
else if (tasklet != null) {
- TaskletStep ts = new TaskletStep();
- configureTaskletStep(ts);
- return ts;
+ return createTaskletStep();
}
else if (flow != null) {
- FlowStep ts = new FlowStep();
- configureFlowStep(ts);
- return ts;
+ return createFlowStep();
}
else if (job != null) {
- JobStep ts = new JobStep();
- configureJobStep(ts);
- return ts;
+ return createJobStep();
}
else {
- PartitionStep ts = new PartitionStep();
- configurePartitionStep(ts);
- return ts;
+ return createPartitionStep();
}
}
@@ -255,215 +253,166 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
return hasChunkElement || tasklet != null;
}
- private void configureAbstractStep(AbstractStep ts) {
- if (name != null) {
- ts.setName(name);
- }
+ private void enhanceCommonStep(StepBuilderHelper> builder) {
if (allowStartIfComplete != null) {
- ts.setAllowStartIfComplete(allowStartIfComplete);
- }
- if (jobRepository != null) {
- ts.setJobRepository(jobRepository);
+ builder.allowStartIfComplete(allowStartIfComplete);
}
if (startLimit != null) {
- ts.setStartLimit(startLimit);
+ builder.startLimit(startLimit);
}
- if (listeners != null) {
- List newListeners = new ArrayList();
- for (StepListener listener : listeners) {
- if (listener instanceof StepExecutionListener) {
- newListeners.add((StepExecutionListener) listener);
- }
- }
- ts.setStepExecutionListeners(newListeners.toArray(new StepExecutionListener[0]));
+ builder.repository(jobRepository);
+ builder.transactionManager(transactionManager);
+ for (StepExecutionListener listener : stepExecutionListeners) {
+ builder.listener(listener);
}
}
- private void configurePartitionStep(PartitionStep ts) {
- Assert.state(partitioner != null, "A Partitioner must be provided for a partition step");
- configureAbstractStep(ts);
+ private Step createPartitionStep() {
- if (partitionHandler != null) {
- ts.setPartitionHandler(partitionHandler);
+ PartitionStepBuilder builder;
+ if (partitioner != null) {
+ builder = new StepBuilder(name).partitioner(step != null ? step.getName() : name, partitioner).step(step);
}
else {
- TaskExecutorPartitionHandler partitionHandler = new TaskExecutorPartitionHandler();
- partitionHandler.setStep(step);
- if (taskExecutor == null) {
- taskExecutor = new SyncTaskExecutor();
- }
- partitionHandler.setGridSize(gridSize);
- partitionHandler.setTaskExecutor(taskExecutor);
- ts.setPartitionHandler(partitionHandler);
+ builder = new StepBuilder(name).partitioner(step);
+ }
+ enhanceCommonStep(builder);
+
+ if (partitionHandler != null) {
+ builder.partitionHandler(partitionHandler);
+ }
+ else {
+ builder.gridSize(gridSize);
+ builder.taskExecutor(taskExecutor);
}
- boolean allowStartIfComplete = this.allowStartIfComplete != null ? this.allowStartIfComplete : false;
- String name = this.name;
- if (step != null) {
- try {
- allowStartIfComplete = step.isAllowStartIfComplete();
- name = step.getName();
- }
- catch (Exception e) {
- logger.info("Ignored exception from step asking for name and allowStartIfComplete flag. "
- + "Using default from enclosing PartitionStep (" + name + "," + allowStartIfComplete + ").");
- }
- }
- SimpleStepExecutionSplitter splitter = new SimpleStepExecutionSplitter(jobRepository, allowStartIfComplete,
- name, partitioner);
- ts.setStepExecutionSplitter(splitter);
- if (stepExecutionAggregator != null) {
- ts.setStepExecutionAggregator(stepExecutionAggregator);
- }
+ builder.aggregator(stepExecutionAggregator);
+
+ return builder.build();
+
}
- private Object extractTarget(Object target, Class> type) {
- if (target instanceof Advised) {
- Object source;
- try {
- source = ((Advised) target).getTargetSource().getTarget();
- }
- catch (Exception e) {
- throw new IllegalStateException("Could not extract target from proxy", e);
- }
- if (source instanceof Advised) {
- source = extractTarget(source, type);
- }
- if (type.isAssignableFrom(source.getClass())) {
- target = source;
- }
- }
- return target;
- }
+ private Step createFaultTolerantStep() {
- private void configureSimple(SimpleStepFactoryBean fb) {
- if (name != null) {
- fb.setBeanName(name);
- }
- if (allowStartIfComplete != null) {
- fb.setAllowStartIfComplete(allowStartIfComplete);
- }
- if (jobRepository != null) {
- fb.setJobRepository(jobRepository);
- }
- if (startLimit != null) {
- fb.setStartLimit(startLimit);
- }
- if (transactionManager != null) {
- fb.setTransactionManager(transactionManager);
- }
- if (listeners != null) {
- fb.setListeners(listeners);
- }
- if (transactionTimeout != null) {
- fb.setTransactionTimeout(transactionTimeout);
- }
- if (propagation != null) {
- fb.setPropagation(propagation);
- }
- if (isolation != null) {
- fb.setIsolation(isolation);
- }
+ FaultTolerantStepBuilder builder = new FaultTolerantStepBuilder(new StepBuilder(name));
- if (chunkCompletionPolicy != null) {
- fb.setChunkCompletionPolicy(chunkCompletionPolicy);
- }
if (commitInterval != null) {
- fb.setCommitInterval(commitInterval);
+ builder.chunk(commitInterval);
}
- if (taskExecutor != null) {
- fb.setTaskExecutor(taskExecutor);
- }
- if (throttleLimit != null) {
- fb.setThrottleLimit(throttleLimit);
- }
- if (itemReader != null) {
- fb.setItemReader(itemReader);
- }
- if (itemProcessor != null) {
- fb.setItemProcessor(itemProcessor);
- }
- if (itemWriter != null) {
- fb.setItemWriter(itemWriter);
+ enhanceTaskletStepBuilder(builder);
+
+ builder.reader(itemReader);
+ builder.writer(itemWriter);
+ builder.processor(itemProcessor);
+
+ if (processorTransactional != null && !processorTransactional) {
+ builder.processorNonTransactional();
}
- if (streams != null) {
- fb.setStreams(streams);
+ for (SkipListener listener : skipListeners) {
+ builder.listener(listener);
}
- }
- private void configureFaultTolerant(FaultTolerantStepFactoryBean fb) {
- if (cacheCapacity != null) {
- fb.setCacheCapacity(cacheCapacity);
- }
- if (readerTransactionalQueue != null) {
- fb.setIsReaderTransactionalQueue(readerTransactionalQueue);
- }
- if (processorTransactional != null) {
- fb.setProcessorTransactional(processorTransactional);
- }
- if (retryLimit != null) {
- fb.setRetryLimit(retryLimit);
- }
- if (skipLimit != null) {
- fb.setSkipLimit(skipLimit);
- }
+ registerItemListeners(builder);
+
if (skipPolicy != null) {
- fb.setSkipPolicy(skipPolicy);
+ builder.skipPolicy(skipPolicy);
}
- if (backOffPolicy != null) {
- fb.setBackOffPolicy(backOffPolicy);
- }
- if (retryPolicy != null) {
- fb.setRetryPolicy(retryPolicy);
- }
- if (retryContextCache != null) {
- fb.setRetryContextCache(retryContextCache);
- }
- if (keyGenerator != null) {
- fb.setKeyGenerator(keyGenerator);
+ else if (skipLimit!=null) {
+ builder.skipLimit(skipLimit);
+ for (Class extends Throwable> type : skippableExceptionClasses.keySet()) {
+ if (skippableExceptionClasses.get(type)) {
+ builder.skip(type);
+ }
+ else {
+ builder.noSkip(type);
+ }
+ }
}
if (retryListeners != null) {
- fb.setRetryListeners(retryListeners);
+ for (RetryListener listener : retryListeners) {
+ builder.listener(listener);
+ }
}
- if (skippableExceptionClasses != null) {
- fb.setSkippableExceptionClasses(skippableExceptionClasses);
+
+ if (retryContextCache == null && cacheCapacity > 0) {
+ retryContextCache = new MapRetryContextCache(cacheCapacity);
}
- if (retryableExceptionClasses != null) {
- fb.setRetryableExceptionClasses(retryableExceptionClasses);
+ builder.retryContextCache(retryContextCache);
+ builder.keyGenerator(keyGenerator);
+ if (retryPolicy != null) {
+ builder.retryPolicy(retryPolicy);
}
+ else {
+ builder.retryLimit(retryLimit);
+ builder.backOffPolicy(backOffPolicy);
+ for (Class extends Throwable> type : retryableExceptionClasses.keySet()) {
+ if (retryableExceptionClasses.get(type)) {
+ builder.retry(type);
+ }
+ else {
+ builder.noRetry(type);
+ }
+ }
+ }
+
if (noRollbackExceptionClasses != null) {
- fb.setNoRollbackExceptionClasses(noRollbackExceptionClasses);
+ for (Class extends Throwable> type : noRollbackExceptionClasses) {
+ builder.noRollback(type);
+ }
+ }
+
+ return builder.build();
+
+ }
+
+ private void registerItemListeners(SimpleStepBuilder builder) {
+ for (ItemReadListener listener : readListeners) {
+ builder.listener(listener);
+ }
+ for (ItemWriteListener listener : writeListeners) {
+ builder.listener(listener);
+ }
+ for (ItemProcessListener listener : processListeners) {
+ builder.listener(listener);
}
}
- @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]));
+ @SuppressWarnings("unchecked")
+ private Step createSimpleStep() {
+ @SuppressWarnings("rawtypes")
+ SimpleStepBuilder builder = new SimpleStepBuilder(new StepBuilder(name));
+ if (commitInterval != null) {
+ builder.chunk(commitInterval);
}
- if (tasklet != null) {
- ts.setTasklet(tasklet);
+ enhanceTaskletStepBuilder(builder);
+ registerItemListeners(builder);
+ builder.reader(itemReader);
+ builder.writer(itemWriter);
+ builder.processor(itemProcessor);
+ builder.completionPolicy(chunkCompletionPolicy);
+ return builder.build();
+ }
+
+ private TaskletStep createTaskletStep() {
+ TaskletStepBuilder builder = new StepBuilder(name).tasklet(tasklet);
+ enhanceTaskletStepBuilder(builder);
+ return builder.build();
+ }
+
+ private void enhanceTaskletStepBuilder(AbstractTaskletStepBuilder> builder) {
+
+ enhanceCommonStep(builder);
+ for (ChunkListener listener : chunkListeners) {
+ builder.listener(listener);
+
}
- if (taskExecutor != null) {
- TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate();
- repeatTemplate.setTaskExecutor(taskExecutor);
- if (throttleLimit != null) {
- repeatTemplate.setThrottleLimit(throttleLimit);
- }
- ts.setStepOperations(repeatTemplate);
- }
- if (transactionManager != null) {
- ts.setTransactionManager(transactionManager);
+ builder.taskExecutor(taskExecutor);
+ if (throttleLimit != null) {
+ builder.throttleLimit(throttleLimit);
}
+ builder.transactionManager(transactionManager);
if (transactionTimeout != null || propagation != null || isolation != null
|| noRollbackExceptionClasses != null) {
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
@@ -479,39 +428,35 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
Collection> exceptions = noRollbackExceptionClasses == null ? new HashSet>()
: noRollbackExceptionClasses;
final BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(exceptions, false);
- ts.setTransactionAttribute(new DefaultTransactionAttribute(attribute) {
+ builder.transactionAttribute(new DefaultTransactionAttribute(attribute) {
@Override
public boolean rollbackOn(Throwable ex) {
return classifier.classify(ex);
}
});
}
+ if (streams != null) {
+ for (ItemStream stream : streams) {
+ builder.stream(stream);
+ }
+ }
+
}
- @SuppressWarnings("serial")
- private void configureFlowStep(FlowStep ts) {
- configureAbstractStep(ts);
- if (flow != null) {
- ts.setFlow(flow);
- }
+ private Step createFlowStep() {
+ FlowStepBuilder builder = new StepBuilder(name).flow(flow);
+ enhanceCommonStep(builder);
+ return builder.build();
}
- @SuppressWarnings("serial")
- private void configureJobStep(JobStep ts) throws Exception {
- configureAbstractStep(ts);
- if (job != null) {
- ts.setJob(job);
- }
- if (jobParametersExtractor != null) {
- ts.setJobParametersExtractor(jobParametersExtractor);
- }
- if (jobLauncher == null) {
- SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
- jobLauncher.setJobRepository(jobRepository);
- jobLauncher.afterPropertiesSet();
- this.jobLauncher = jobLauncher;
- }
- ts.setJobLauncher(jobLauncher);
+ private Step createJobStep() throws Exception {
+
+ JobStepBuilder builder = new StepBuilder(name).job(job);
+ enhanceCommonStep(builder);
+ builder.parametersExtractor(jobParametersExtractor);
+ builder.launcher(jobLauncher);
+ return builder.build();
+
}
private void validateFaultTolerantSettings() {
@@ -526,14 +471,12 @@ 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.
+ * 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 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 either condition is violated
@@ -560,6 +503,12 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
if (o instanceof Integer) {
return isPositive((Integer) o);
}
+ if (o instanceof Collection) {
+ return !((Collection>) o).isEmpty();
+ }
+ if (o instanceof Map) {
+ return !((Map, ?>) o).isEmpty();
+ }
return o != null;
}
@@ -589,8 +538,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
// =========================================================
/**
- * Set the bean name property, which will become the name of the
- * {@link Step} when it is created.
+ * 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)
*/
@@ -678,8 +626,8 @@ 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.
+ * 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
*/
@@ -741,19 +689,47 @@ 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.
+ * 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) {
- this.listeners = listeners;
+ this.listeners = listeners; // useful for testing
+ for (StepListener listener : listeners) {
+ if (listener instanceof SkipListener) {
+ @SuppressWarnings("unchecked")
+ SkipListener skipListener = (SkipListener) listener;
+ skipListeners.add(skipListener);
+ }
+ if (listener instanceof StepExecutionListener) {
+ StepExecutionListener stepExecutionListener = (StepExecutionListener) listener;
+ stepExecutionListeners.add(stepExecutionListener);
+ }
+ if (listener instanceof ChunkListener) {
+ ChunkListener chunkListener = (ChunkListener) listener;
+ chunkListeners.add(chunkListener);
+ }
+ if (listener instanceof ItemReadListener) {
+ @SuppressWarnings("unchecked")
+ ItemReadListener readListener = (ItemReadListener) listener;
+ readListeners.add(readListener);
+ }
+ if (listener instanceof ItemWriteListener) {
+ @SuppressWarnings("unchecked")
+ ItemWriteListener writeListener = (ItemWriteListener) listener;
+ writeListeners.add(writeListener);
+ }
+ if (listener instanceof ItemProcessListener) {
+ @SuppressWarnings("unchecked")
+ ItemProcessListener processListener = (ItemProcessListener) listener;
+ processListeners.add(processListener);
+ }
+ }
}
/**
- * Exception classes that may not cause a rollback if encountered in the
- * right place.
+ * Exception classes that may not cause a rollback if encountered in the right place.
*
* @param noRollbackExceptionClasses the noRollbackExceptionClasses to set
*/
@@ -796,8 +772,8 @@ 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.
+ * 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
*/
@@ -813,9 +789,8 @@ 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.
+ * 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
*/
@@ -828,28 +803,24 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
// =========================================================
/**
- * Public setter for the capacity of the cache in the retry policy. If more
- * items than this fail without being skipped or recovered an exception will
- * be thrown. This is to guard against inadvertent infinite loops generated
+ * Public setter for the capacity of the cache in the retry policy. If more 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.
*
- * 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
+ * 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}.
*
- * @param cacheCapacity the cache capacity to set (greater than 0 else
- * ignored)
+ * @param cacheCapacity the cache capacity to set (greater than 0 else ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
}
/**
- * Public setter for the {@link CompletionPolicy} applying to the chunk
- * 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.
+ * Public setter for the {@link CompletionPolicy} applying to the chunk 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
*/
@@ -858,8 +829,7 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
}
/**
- * Set the commit interval. Either set this or the chunkCompletionPolicy but
- * not both.
+ * Set the commit interval. Either set this or the chunkCompletionPolicy but not both.
*
* @param commitInterval 1 by default
*/
@@ -868,9 +838,8 @@ 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.
+ * 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
*/
@@ -879,10 +848,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
}
/**
- * Flag to signal that the processor is transactional, in which case it
- * 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.
+ * Flag to signal that the processor is transactional, in which case it 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
*/
@@ -891,9 +858,8 @@ 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.
+ * 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.
*/
@@ -902,11 +868,9 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
}
/**
- * Public setter for a limit that determines skip policy. If this value is
- * positive then an exception in chunk processing will cause the item to be
- * 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.
+ * Public setter for a limit that determines skip policy. If this value is positive then an exception in chunk
+ * processing will cause the item to be 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).
*/
@@ -915,8 +879,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.
+ * 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
*/
@@ -925,8 +888,8 @@ 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}.
+ * 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
*/
@@ -935,10 +898,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware {
}
/**
- * Public setter for the throttle limit. This limits the number of tasks
- * queued for concurrent processing to prevent thread pools from being
- * overwhelmed. Defaults to
- * {@link TaskExecutorRepeatTemplate#DEFAULT_THROTTLE_LIMIT}.
+ * Public setter for the throttle limit. This limits the number of tasks 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.
*/
@@ -981,9 +942,8 @@ 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.
+ * 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
*/
@@ -1001,9 +961,8 @@ 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.
+ * 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
*/
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
index 466a1555c..071f39577 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java
@@ -55,8 +55,11 @@ public class FlowStep extends AbstractStep {
*/
@Override
public void afterPropertiesSet() throws Exception {
- super.afterPropertiesSet();
Assert.state(flow != null, "A Flow must be provided");
+ if (getName()==null) {
+ setName(flow.getName());
+ }
+ super.afterPropertiesSet();
}
/**
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
index c8006c1cb..79e78e189 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java
@@ -39,8 +39,8 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
- * A {@link Step} implementation that provides common behavior to subclasses,
- * including registering and calling listeners.
+ * A {@link Step} implementation that provides common behavior to subclasses, including registering and calling
+ * listeners.
*
* @author Dave Syer
* @author Ben Hale
@@ -68,7 +68,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
public void afterPropertiesSet() throws Exception {
- Assert.notNull(jobRepository, "JobRepository is mandatory");
+ Assert.state(name != null, "A Step must have a name");
+ Assert.state(jobRepository != null, "JobRepository is mandatory");
}
public String getName() {
@@ -76,8 +77,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Set the name property. Always overrides the default value if this object
- * is a Spring bean.
+ * Set the name property. Always overrides the default value if this object is a Spring bean.
*
* @see #setBeanName(java.lang.String)
*/
@@ -86,11 +86,9 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Set the name property if it is not already set. Because of the order of
- * the callbacks in a Spring container the name property will be set first
- * if it is present. Care is needed with bean definition inheritance - if a
- * parent bean has a name, then its children need an explicit name as well,
- * otherwise they will not be unique.
+ * Set the name property if it is not already set. Because of the order of the callbacks in a Spring container the
+ * name property will be set first if it is present. Care is needed with bean definition inheritance - if a parent
+ * bean has a name, then its children need an explicit name as well, otherwise they will not be unique.
*
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
@@ -118,8 +116,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Public setter for flag that determines whether the step should start
- * again if it is already complete. Defaults to false.
+ * Public setter for flag that determines whether the step should start again if it is already complete. Defaults to
+ * false.
*
* @param allowStartIfComplete the value of the flag to set
*/
@@ -137,9 +135,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Extension point for subclasses to execute business logic. Subclasses
- * should set the {@link ExitStatus} on the {@link StepExecution} before
- * returning.
+ * Extension point for subclasses to execute business logic. Subclasses should set the {@link ExitStatus} on the
+ * {@link StepExecution} before returning.
*
* @param stepExecution the current step context
* @throws Exception
@@ -147,9 +144,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
protected abstract void doExecute(StepExecution stepExecution) throws Exception;
/**
- * Extension point for subclasses to provide callbacks to their
- * collaborators at the beginning of a step, to open or acquire resources.
- * Does nothing by default.
+ * Extension point for subclasses to provide callbacks to their collaborators at the beginning of a step, to open or
+ * acquire resources. Does nothing by default.
*
* @param ctx the {@link ExecutionContext} to use
* @throws Exception
@@ -158,9 +154,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Extension point for subclasses to provide callbacks to their
- * collaborators at the end of a step (right at the end of the finally
- * block), to close or release resources. Does nothing by default.
+ * Extension point for subclasses to provide callbacks to their collaborators at the end of a step (right at the end
+ * of the finally block), to close or release resources. Does nothing by default.
*
* @param ctx the {@link ExecutionContext} to use
* @throws Exception
@@ -169,9 +164,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Template method for step execution logic - calls abstract methods for
- * resource initialization ({@link #open(ExecutionContext)}), execution
- * logic ({@link #doExecute(StepExecution)}) and resource closing (
+ * Template method for step execution logic - calls abstract methods for resource initialization (
+ * {@link #open(ExecutionContext)}), execution logic ({@link #doExecute(StepExecution)}) and resource closing (
* {@link #close(ExecutionContext)}).
*/
public final void execute(StepExecution stepExecution) throws JobInterruptedException,
@@ -287,8 +281,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Register a step listener for callbacks at the appropriate stages in a
- * step execution.
+ * Register a step listener for callbacks at the appropriate stages in a step execution.
*
* @param listener a {@link StepExecutionListener}
*/
@@ -332,8 +325,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
/**
- * Default mapping from throwable to {@link ExitStatus}. Clients can modify
- * the exit code using a {@link StepExecutionListener}.
+ * Default mapping from throwable to {@link ExitStatus}. Clients can modify the exit code using a
+ * {@link StepExecutionListener}.
*
* @param ex the cause of the failure
* @return an {@link ExitStatus}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java
new file mode 100644
index 000000000..f840b0ff5
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java
@@ -0,0 +1,136 @@
+package org.springframework.batch.core.step.builder;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.core.step.tasklet.TaskletStep;
+import org.springframework.batch.item.ItemStream;
+import org.springframework.batch.repeat.RepeatOperations;
+import org.springframework.batch.repeat.exception.DefaultExceptionHandler;
+import org.springframework.batch.repeat.exception.ExceptionHandler;
+import org.springframework.batch.repeat.support.RepeatTemplate;
+import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.transaction.interceptor.TransactionAttribute;
+
+public abstract class AbstractTaskletStepBuilder> extends
+ StepBuilderHelper> {
+
+ private Set listeners = new LinkedHashSet();
+
+ private RepeatOperations stepOperations;
+
+ private TransactionAttribute transactionAttribute;
+
+ private Set streams = new LinkedHashSet();
+
+ private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+
+ private int throttleLimit = TaskExecutorRepeatTemplate.DEFAULT_THROTTLE_LIMIT;
+
+ private TaskExecutor taskExecutor;
+
+ public AbstractTaskletStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ protected abstract Tasklet createTasklet();
+
+ public TaskletStep build() {
+
+ TaskletStep step = new TaskletStep(getName());
+
+ super.enhance(step);
+
+ step.setChunkListeners(listeners.toArray(new ChunkListener[0]));
+
+ if (transactionAttribute != null) {
+ step.setTransactionAttribute(transactionAttribute);
+ }
+
+ if (stepOperations == null) {
+
+ stepOperations = new RepeatTemplate();
+
+ if (taskExecutor != null) {
+ TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate();
+ repeatTemplate.setTaskExecutor(taskExecutor);
+ repeatTemplate.setThrottleLimit(throttleLimit);
+ stepOperations = repeatTemplate;
+ }
+
+ ((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler);
+
+ }
+ step.setStepOperations(stepOperations);
+ step.setTasklet(createTasklet());
+
+ step.setStreams(getStreams());
+
+ try {
+ step.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+
+ return step;
+
+ }
+
+ public AbstractTaskletStepBuilder listener(ChunkListener listener) {
+ listeners.add(listener);
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder stream(ItemStream stream) {
+ streams.add(stream);
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder taskExecutor(TaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder throttleLimit(int throttleLimit) {
+ this.throttleLimit = throttleLimit;
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder exceptionHandler(ExceptionHandler exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder stepOperations(RepeatOperations repeatTemplate) {
+ this.stepOperations = repeatTemplate;
+ return this;
+ }
+
+ public AbstractTaskletStepBuilder transactionAttribute(TransactionAttribute transactionAttribute) {
+ this.transactionAttribute = transactionAttribute;
+ return this;
+ }
+
+ protected ItemStream[] getStreams() {
+ return streams.toArray(new ItemStream[0]);
+ }
+
+ protected RepeatOperations getStepOperations() {
+ return stepOperations;
+ }
+
+ protected ExceptionHandler getExceptionHandler() {
+ return exceptionHandler;
+ }
+
+ protected boolean concurrent() {
+ boolean concurrent = taskExecutor != null && !(taskExecutor instanceof SyncTaskExecutor);
+ return concurrent;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
old mode 100755
new mode 100644
similarity index 53%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
index 1b276d603..4f46342eb
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2007 the original author or authors.
+ * Copyright 2006-2011 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.
@@ -13,24 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package org.springframework.batch.core.step.item;
+package org.springframework.batch.core.step.builder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-import org.springframework.classify.BinaryExceptionClassifier;
-import org.springframework.classify.Classifier;
-import org.springframework.classify.SubclassClassifier;
import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.JobInterruptedException;
-import org.springframework.batch.core.Step;
+import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.listener.StepListenerFactoryBean;
import org.springframework.batch.core.step.FatalStepExecutionException;
+import org.springframework.batch.core.step.item.BatchRetryTemplate;
+import org.springframework.batch.core.step.item.ChunkMonitor;
+import org.springframework.batch.core.step.item.ChunkOrientedTasklet;
+import org.springframework.batch.core.step.item.FaultTolerantChunkProcessor;
+import org.springframework.batch.core.step.item.FaultTolerantChunkProvider;
+import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException;
+import org.springframework.batch.core.step.item.KeyGenerator;
+import org.springframework.batch.core.step.item.SimpleRetryExceptionHandler;
import org.springframework.batch.core.step.skip.CompositeSkipPolicy;
import org.springframework.batch.core.step.skip.ExceptionClassifierSkipPolicy;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
@@ -40,11 +47,14 @@ import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.skip.SkipPolicyFailedException;
-import org.springframework.batch.core.step.tasklet.TaskletStep;
+import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.support.RepeatTemplate;
+import org.springframework.classify.BinaryExceptionClassifier;
+import org.springframework.classify.Classifier;
+import org.springframework.classify.SubclassClassifier;
import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.RetryException;
import org.springframework.retry.RetryListener;
@@ -52,60 +62,29 @@ import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.policy.CompositeRetryPolicy;
import org.springframework.retry.policy.ExceptionClassifierRetryPolicy;
-import org.springframework.retry.policy.MapRetryContextCache;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.policy.RetryContextCache;
import org.springframework.retry.policy.SimpleRetryPolicy;
-import org.springframework.core.task.SyncTaskExecutor;
-import org.springframework.core.task.TaskExecutor;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
/**
- * Factory bean for step that provides options for configuring skip behaviour.
- * User can set {@link #setSkipLimit(int)} to set how many exceptions of
- * {@link #setSkippableExceptionClasses(Collection)} types are tolerated.
- * {@link #setFatalExceptionClasses(Collection)} will cause immediate
- * termination of job - they are treated as higher priority than
- * {@link #setSkippableExceptionClasses(Collection)}, so the two lists don't
- * need to be exclusive.
- *
- * Skippable exceptions on write will by default cause transaction rollback - to
- * avoid rollback for specific exception class include it in the transaction
- * attribute as "no rollback for".
- *
- * @see SimpleStepFactoryBean
- *
* @author Dave Syer
- * @author Robert Kasanicky
- * @author Morten Andersen-Gott
*
*/
-public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean {
+public class FaultTolerantStepBuilder extends SimpleStepBuilder {
- private Map, Boolean> skippableExceptionClasses = new HashMap, Boolean>();
+ private ChunkMonitor chunkMonitor = new ChunkMonitor();
- private Collection> nonSkippableExceptionClasses = new HashSet>();
-
- private Collection> noRollbackExceptionClasses = new HashSet>();
-
- private Map, Boolean> retryableExceptionClasses = new HashMap, Boolean>();
-
- private Collection> nonRetryableExceptionClasses = new HashSet>();
-
- private int cacheCapacity = 0;
+ private boolean streamIsReader;
private int retryLimit = 0;
- private int skipLimit = 0;
-
- private SkipPolicy skipPolicy;
-
private BackOffPolicy backOffPolicy;
- private RetryListener[] retryListeners;
+ private Set retryListeners = new LinkedHashSet();
private RetryPolicy retryPolicy;
@@ -113,182 +92,235 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean> noRollbackExceptionClasses = new LinkedHashSet>();
+
+ private Map, Boolean> skippableExceptionClasses = new HashMap, Boolean>();
+
+ private Collection> nonSkippableExceptionClasses = new HashSet>();
+
+ private Map, Boolean> retryableExceptionClasses = new HashMap, Boolean>();
+
+ private Collection> nonRetryableExceptionClasses = new HashSet>();
+
+ private Set> skipListeners = new LinkedHashSet>();
+
+ private int skipLimit = 0;
+
+ private SkipPolicy skipPolicy;
private boolean processorTransactional = true;
- /**
- * The {@link KeyGenerator} to use to identify failed items across rollback.
- * Not used in the case of the
- * {@link #setIsReaderTransactionalQueue(boolean) transactional queue flag}
- * being false (the default).
- *
- * @param keyGenerator
- * the {@link KeyGenerator} to set
- */
- public void setKeyGenerator(KeyGenerator keyGenerator) {
+ public FaultTolerantStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ @Override
+ protected Tasklet createTasklet() {
+ Assert.state(getReader() != null, "ItemReader must be provided");
+ Assert.state(getProcessor() != null || getWriter() != null, "ItemWriter or ItemProcessor must be provided");
+ addSpecialExceptions();
+ registerSkipListeners();
+ FaultTolerantChunkProvider chunkProvider = createChunkProvider();
+ FaultTolerantChunkProcessor chunkProcessor = createChunkProcessor();
+ ChunkOrientedTasklet tasklet = new ChunkOrientedTasklet(chunkProvider, chunkProcessor);
+ tasklet.setBuffering(!isReaderTransactionalQueue());
+ return tasklet;
+ }
+
+ public FaultTolerantStepBuilder listener(SkipListener super I, ? super O> listener) {
+ skipListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public FaultTolerantStepBuilder listener(ChunkListener listener) {
+ super.listener(new TerminateOnExceptionChunkListenerDelegate(listener));
+ return this;
+ }
+
+ @Override
+ public AbstractTaskletStepBuilder> transactionAttribute(
+ TransactionAttribute transactionAttribute) {
+ return super.transactionAttribute(getTransactionAttribute(transactionAttribute));
+ }
+
+ public FaultTolerantStepBuilder listener(RetryListener listener) {
+ retryListeners.add(listener);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder keyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
+ return this;
}
-
- /**
- * Setter for the retry policy. If this is specified the other retry
- * properties are ignored (retryLimit, backOffPolicy,
- * retryableExceptionClasses).
- *
- * @param retryPolicy
- * a stateless {@link RetryPolicy}
- */
- public void setRetryPolicy(RetryPolicy retryPolicy) {
- this.retryPolicy = retryPolicy;
- }
-
- /**
- * 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) {
+
+ public FaultTolerantStepBuilder retryLimit(int retryLimit) {
this.retryLimit = retryLimit;
+ return this;
}
-
- /**
- * Public setter for the capacity of the cache in the retry policy. If more
- * 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.
- *
- * 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}.
- *
- * This property is ignored if the
- * {@link #setRetryContextCache(RetryContextCache)} is set directly.
- *
- * @param cacheCapacity
- * the cache capacity to set (greater than 0 else ignored)
- */
- public void setCacheCapacity(int cacheCapacity) {
- this.cacheCapacity = cacheCapacity;
+
+ public FaultTolerantStepBuilder retryPolicy(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ return this;
}
-
- /**
- * Override the default retry context cache for retry of chunk processing.
- * If this property is set then {@link #setCacheCapacity(int)} is ignored.
- *
- * @param retryContextCache
- * the {@link RetryContextCache} to set
- */
- public void setRetryContextCache(RetryContextCache retryContextCache) {
- this.retryContextCache = retryContextCache;
- }
-
- /**
- * Public setter for the retryable exceptions classifier map (from throwable
- * class to boolean, true is retryable).
- *
- * @param retryableExceptionClasses
- * the retryableExceptionClasses to set
- */
- public void setRetryableExceptionClasses(Map, Boolean> retryableExceptionClasses) {
- this.retryableExceptionClasses = retryableExceptionClasses;
- }
-
- /**
- * Public setter for the {@link BackOffPolicy}.
- *
- * @param backOffPolicy
- * the {@link BackOffPolicy} to set
- */
- public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
+
+ public FaultTolerantStepBuilder backOffPolicy(BackOffPolicy backOffPolicy) {
this.backOffPolicy = backOffPolicy;
+ return this;
}
-
- /**
- * Public setter for the {@link RetryListener}s.
- *
- * @param retryListeners
- * the {@link RetryListener}s to set
- */
- public void setRetryListeners(RetryListener... retryListeners) {
- this.retryListeners = retryListeners;
+
+ public FaultTolerantStepBuilder retryContextCache(RetryContextCache retryContextCache) {
+ this.retryContextCache = retryContextCache;
+ return this;
}
-
- /**
- * A limit that determines skip policy. If this value is positive then an
- * exception in chunk processing will cause the item to be 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) {
+
+ public FaultTolerantStepBuilder skipLimit(int skipLimit) {
this.skipLimit = skipLimit;
+ return this;
}
-
- /**
- * A {@link SkipPolicy} that determines the outcome of an exception when
- * processing an item. Overrides the {@link #setSkipLimit(int) skipLimit}.
- * The {@link #setSkippableExceptionClasses(Map) skippableExceptionClasses}
- * are also ignored if this is set.
- *
- * @param skipPolicy
- * the {@link SkipPolicy} to set
- */
- public void setSkipPolicy(SkipPolicy skipPolicy) {
+
+ public FaultTolerantStepBuilder skipPolicy(SkipPolicy skipPolicy) {
this.skipPolicy = skipPolicy;
+ return this;
+ }
+
+ public FaultTolerantStepBuilder noRollback(Class extends Throwable> type) {
+ noRollbackExceptionClasses.add(type);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder noRetry(Class extends Throwable> type) {
+ retryableExceptionClasses.put(type, false);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder retry(Class extends Throwable> type) {
+ retryableExceptionClasses.put(type, true);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder noSkip(Class extends Throwable> type) {
+ skippableExceptionClasses.put(type, false);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder skip(Class extends Throwable> type) {
+ skippableExceptionClasses.put(type, true);
+ return this;
+ }
+
+ public FaultTolerantStepBuilder processorNonTransactional() {
+ this.processorTransactional = false;
+ return this;
+ }
+
+ @Override
+ public AbstractTaskletStepBuilder> stream(ItemStream stream) {
+ if (stream instanceof ItemReader>) {
+ if (!streamIsReader) {
+ streamIsReader = true;
+ super.stream(chunkMonitor);
+ }
+ // In cases where multiple nested item readers are registered,
+ // they all want to get the open() and close() callbacks.
+ chunkMonitor.registerItemStream(stream);
+ } else {
+ super.stream(stream);
+ }
+ return this;
+ }
+
+ private FaultTolerantChunkProvider createChunkProvider() {
+
+ SkipPolicy readSkipPolicy = createSkipPolicy();
+ readSkipPolicy = getFatalExceptionAwareProxy(readSkipPolicy);
+ FaultTolerantChunkProvider chunkProvider = new FaultTolerantChunkProvider(getReader(),
+ createChunkOperations());
+ chunkProvider.setMaxSkipsOnRead(Math.max(getChunkSize(), FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ));
+ chunkProvider.setSkipPolicy(readSkipPolicy);
+ chunkProvider.setRollbackClassifier(getRollbackClassifier());
+ ArrayList listeners = new ArrayList(getItemListeners());
+ listeners.addAll(skipListeners);
+ chunkProvider.setListeners(listeners);
+
+ return chunkProvider;
+
+ }
+
+ private FaultTolerantChunkProcessor createChunkProcessor() {
+
+ BatchRetryTemplate batchRetryTemplate = createRetryOperations();
+
+ FaultTolerantChunkProcessor chunkProcessor = new FaultTolerantChunkProcessor(getProcessor(),
+ getWriter(), batchRetryTemplate);
+ chunkProcessor.setBuffering(!isReaderTransactionalQueue());
+ chunkProcessor.setProcessorTransactional(processorTransactional);
+
+ SkipPolicy writeSkipPolicy = createSkipPolicy();
+ writeSkipPolicy = getFatalExceptionAwareProxy(writeSkipPolicy);
+ chunkProcessor.setWriteSkipPolicy(writeSkipPolicy);
+ chunkProcessor.setProcessSkipPolicy(writeSkipPolicy);
+ chunkProcessor.setRollbackClassifier(getRollbackClassifier());
+ chunkProcessor.setKeyGenerator(keyGenerator);
+ detectStreamInReader();
+
+ ArrayList listeners = new ArrayList(getItemListeners());
+ listeners.addAll(skipListeners);
+ chunkProcessor.setListeners(listeners);
+ chunkProcessor.setChunkMonitor(chunkMonitor);
+
+ return chunkProcessor;
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addSpecialExceptions() {
+ addNonSkippableExceptionIfMissing(SkipLimitExceededException.class, NonSkippableReadException.class,
+ SkipListenerFailedException.class, SkipPolicyFailedException.class, RetryException.class,
+ JobInterruptedException.class, Error.class);
+ addNonRetryableExceptionIfMissing(SkipLimitExceededException.class, NonSkippableReadException.class,
+ TransactionException.class, FatalStepExecutionException.class, SkipListenerFailedException.class,
+ SkipPolicyFailedException.class, RetryException.class, JobInterruptedException.class, Error.class);
+ }
+
+ private void detectStreamInReader() {
+ if (streamIsReader) {
+ if (!concurrent()) {
+ chunkMonitor.setItemReader(getReader());
+ }
+ else {
+ logger.warn("Asynchronous TaskExecutor detected with ItemStream reader. This is probably an error, "
+ + "and may lead to incorrect restart data being stored.");
+ }
+ }
}
/**
- * Exception classes that when raised won't crash the job but will result in
- * the item which handling caused the exception being skipped. Any exception
- * which is marked for "no rollback" is also skippable, but not vice versa.
- * Remember to set the {@link #setSkipLimit(int) skip limit} as well.
- *
- * Defaults to all no exception.
+ * Register explicitly set item listeners and auto-register reader, processor and writer if applicable
+ */
+ private void registerSkipListeners() {
+
+ // auto-register reader, processor and writer
+ for (Object itemHandler : new Object[] { getReader(), getWriter(), getProcessor() }) {
+
+ if (StepListenerFactoryBean.isListener(itemHandler)) {
+ StepListener listener = StepListenerFactoryBean.getListener(itemHandler);
+ if (listener instanceof SkipListener, ?>) {
+ @SuppressWarnings("unchecked")
+ SkipListener super I, ? super O> skipListener = (SkipListener super I, ? super O>) listener;
+ skipListeners.add(skipListener);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Convenience method to get an exception classifier based on the provided transaction attributes.
*
- * @param exceptionClasses
- * defaults to Exception
+ * @return an exception classifier: maps to true if an exception should cause rollback
*/
- public void setSkippableExceptionClasses(Map, Boolean> exceptionClasses) {
- this.skippableExceptionClasses = exceptionClasses;
- }
-
- /**
- * Exception classes that are candidates for no rollback. The {@link Step}
- * can not honour the no rollback hint in all circumstances, but any
- * exception on this list is counted as skippable, so even if there has to
- * be a rollback, then the step will not fail as long as the skip limit is
- * not breached.
- *
- * Defaults is empty.
- *
- * @param noRollbackExceptionClasses
- * the exception classes to set
- */
- public void setNoRollbackExceptionClasses(Collection> noRollbackExceptionClasses) {
- this.noRollbackExceptionClasses = noRollbackExceptionClasses;
- }
-
- /**
- * @param processorTransactional
- */
- public void setProcessorTransactional(boolean processorTransactional) {
- this.processorTransactional = processorTransactional;
- }
-
- /**
- * Convenience method for subclasses to get an exception classifier based on
- * the provided transaction attributes.
- *
- * @return an exception classifier: maps to true if an exception should
- * cause rollback
- */
- protected Classifier getRollbackClassifier() {
+ private Classifier getRollbackClassifier() {
Classifier classifier = new BinaryExceptionClassifier(noRollbackExceptionClasses, false);
@@ -317,16 +349,8 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean classifier = getRollbackClassifier();
return new DefaultTransactionAttribute(attribute) {
@Override
@@ -338,118 +362,33 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean itemReader = getItemReader();
- for (ItemStream stream : streams) {
- if (stream instanceof ItemReader>) {
- streamIsReader = true;
- chunkMonitor.registerItemStream(stream);
- } else {
- step.registerStream(stream);
- }
- }
- TaskExecutor taskExecutor = getTaskExecutor();
- // In cases where multiple nested item readers are registered,
- // they all want to get the open() and close() callbacks.
- if (streamIsReader) {
- // double registration is fine
- step.registerStream(chunkMonitor);
- boolean concurrent = taskExecutor != null && !(taskExecutor instanceof SyncTaskExecutor);
- if (!concurrent) {
- chunkMonitor.setItemReader(itemReader);
- } else {
- logger.warn("Asynchronous TaskExecutor detected (" + taskExecutor.getClass()
- + ") with ItemStream reader. This is probably an error, " + "and may lead to incorrect restart data being stored.");
- }
- }
- }
-
- /**
- * @return {@link ChunkProvider} configured for fault-tolerance.
- */
- @Override
- protected SimpleChunkProvider configureChunkProvider() {
-
- SkipPolicy readSkipPolicy = createSkipPolicy();
- readSkipPolicy = getFatalExceptionAwareProxy(readSkipPolicy);
- FaultTolerantChunkProvider chunkProvider = new FaultTolerantChunkProvider(getItemReader(), getChunkOperations());
- chunkProvider.setMaxSkipsOnRead(Math.max(getCommitInterval(), FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ));
- chunkProvider.setSkipPolicy(readSkipPolicy);
- chunkProvider.setRollbackClassifier(getRollbackClassifier());
-
- return chunkProvider;
-
- }
-
- /**
- * @return
- */
protected SkipPolicy createSkipPolicy() {
SkipPolicy skipPolicy = this.skipPolicy;
- Map, Boolean> map = new HashMap, Boolean>(skippableExceptionClasses);
+ Map, Boolean> map = new HashMap, Boolean>(
+ skippableExceptionClasses);
map.put(ForceRollbackForWriteSkipException.class, true);
- LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, map);
+ LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit , map);
if (skipPolicy == null) {
Assert.state(!(skippableExceptionClasses.isEmpty() && skipLimit > 0),
"If a skip limit is provided then skippable exceptions must also be specified");
skipPolicy = limitCheckingItemSkipPolicy;
- } else if (limitCheckingItemSkipPolicy != null) {
+ }
+ else if (limitCheckingItemSkipPolicy != null) {
skipPolicy = new CompositeSkipPolicy(new SkipPolicy[] { skipPolicy, limitCheckingItemSkipPolicy });
}
return skipPolicy;
}
- /**
- * @return {@link ChunkProcessor} configured for fault-tolerance.
- */
- @Override
- protected SimpleChunkProcessor configureChunkProcessor() {
-
- BatchRetryTemplate batchRetryTemplate = configureRetry();
-
- FaultTolerantChunkProcessor chunkProcessor = new FaultTolerantChunkProcessor(getItemProcessor(), getItemWriter(),
- batchRetryTemplate);
- chunkProcessor.setBuffering(!isReaderTransactionalQueue());
- chunkProcessor.setProcessorTransactional(processorTransactional);
-
- SkipPolicy writeSkipPolicy = createSkipPolicy();
- writeSkipPolicy = getFatalExceptionAwareProxy(writeSkipPolicy);
- chunkProcessor.setWriteSkipPolicy(writeSkipPolicy);
- chunkProcessor.setProcessSkipPolicy(writeSkipPolicy);
- chunkProcessor.setRollbackClassifier(getRollbackClassifier());
- chunkProcessor.setKeyGenerator(keyGenerator);
- chunkProcessor.setChunkMonitor(chunkMonitor);
-
- return chunkProcessor;
-
- }
-
/**
* @return fully configured retry template for item processing phase.
*/
- private BatchRetryTemplate configureRetry() {
+ private BatchRetryTemplate createRetryOperations() {
RetryPolicy retryPolicy = this.retryPolicy;
SimpleRetryPolicy simpleRetryPolicy = null;
- Map, Boolean> map = new HashMap, Boolean>(retryableExceptionClasses);
+ Map, Boolean> map = new HashMap, Boolean>(
+ retryableExceptionClasses);
map.put(ForceRollbackForWriteSkipException.class, true);
simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map);
@@ -457,7 +396,8 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean 0),
"If a retry limit is provided then retryable exceptions must also be specified");
retryPolicy = simpleRetryPolicy;
- } else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) {
+ }
+ else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) {
CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
compositeRetryPolicy.setPolicies(new RetryPolicy[] { retryPolicy, simpleRetryPolicy });
retryPolicy = compositeRetryPolicy;
@@ -474,28 +414,25 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean 0) {
- batchRetryTemplate.setRetryContextCache(new MapRetryContextCache(cacheCapacity));
- }
- } else {
+ if (retryContextCache != null) {
batchRetryTemplate.setRetryContextCache(retryContextCache);
}
if (retryListeners != null) {
- batchRetryTemplate.setListeners(retryListeners);
+ batchRetryTemplate.setListeners(retryListeners.toArray(new RetryListener[0]));
}
return batchRetryTemplate;
+
}
/**
- * Wrap the provided {@link #setRetryPolicy(RetryPolicy)} so that it never
- * retries explicitly non-retryable exceptions.
+ * Wrap the provided {@link #setRetryPolicy(RetryPolicy)} so that it never retries explicitly non-retryable
+ * exceptions.
*/
private RetryPolicy getFatalExceptionAwareProxy(RetryPolicy retryPolicy) {
@@ -505,7 +442,8 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean classifier = new SubclassClassifier(retryPolicy);
+ SubclassClassifier classifier = new SubclassClassifier(
+ retryPolicy);
classifier.setTypeMap(map);
ExceptionClassifierRetryPolicy retryPolicyWrapper = new ExceptionClassifierRetryPolicy();
@@ -515,11 +453,9 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean extends SimpleStepFactoryBean>) exceptions;
}
- @Override
- protected void registerChunkListeners(TaskletStep step, StepListener listener) {
- super.registerChunkListeners(step, new TerminateOnExceptionChunkListenerDelegate((ChunkListener) listener));
- }
-
/**
- * ChunkListener that wraps exceptions thrown from the ChunkListener in
- * {@link FatalStepExecutionException} to force termination of StepExecution
+ * ChunkListener that wraps exceptions thrown from the ChunkListener in {@link FatalStepExecutionException} to force
+ * termination of StepExecution
*
- * ChunkListeners shoulnd't throw exceptions and expect continued
- * processing, they must be handled in the implementation or the step will
- * terminate
+ * ChunkListeners shoulnd't throw exceptions and expect continued processing, they must be handled in the
+ * implementation or the step will terminate
*
*/
private class TerminateOnExceptionChunkListenerDelegate implements ChunkListener {
@@ -589,7 +519,8 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean extends SimpleStepFactoryBean {
+
+ private Flow flow;
+
+ public FlowStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ public FlowStepBuilder flow(Flow flow) {
+ this.flow = flow;
+ return this;
+ }
+
+ public Step build() {
+ FlowStep step = new FlowStep();
+ step.setName(getName());
+ step.setFlow(flow);
+ super.enhance(step);
+ try {
+ step.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ return step;
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java
new file mode 100644
index 000000000..66e8411cf
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.support.SimpleJobLauncher;
+import org.springframework.batch.core.step.job.JobParametersExtractor;
+import org.springframework.batch.core.step.job.JobStep;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class JobStepBuilder extends StepBuilderHelper {
+
+ private Job job;
+
+ private JobLauncher jobLauncher;
+
+ private JobParametersExtractor jobParametersExtractor;
+
+ public JobStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ public JobStepBuilder job(Job job) {
+ this.job = job;
+ return this;
+ }
+
+ public JobStepBuilder launcher(JobLauncher jobLauncher) {
+ this.jobLauncher = jobLauncher;
+ return this;
+ }
+
+ public JobStepBuilder parametersExtractor(JobParametersExtractor jobParametersExtractor) {
+ this.jobParametersExtractor = jobParametersExtractor;
+ return this;
+ }
+
+ public Step build() {
+
+ JobStep step = new JobStep();
+ step.setName(getName());
+ super.enhance(step);
+ if (job != null) {
+ step.setJob(job);
+ }
+ if (jobParametersExtractor != null) {
+ step.setJobParametersExtractor(jobParametersExtractor);
+ }
+ if (jobLauncher == null) {
+ SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
+ jobLauncher.setJobRepository(getJobRepository());
+ try {
+ jobLauncher.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new StepBuilderException(e);
+ }
+ this.jobLauncher = jobLauncher;
+ }
+ step.setJobLauncher(jobLauncher);
+ try {
+ step.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ return step;
+
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java
new file mode 100644
index 000000000..47b9ce7d1
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.partition.PartitionHandler;
+import org.springframework.batch.core.partition.StepExecutionSplitter;
+import org.springframework.batch.core.partition.support.PartitionStep;
+import org.springframework.batch.core.partition.support.Partitioner;
+import org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter;
+import org.springframework.batch.core.partition.support.StepExecutionAggregator;
+import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler;
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class PartitionStepBuilder extends StepBuilderHelper {
+
+ private TaskExecutor taskExecutor;
+
+ private Partitioner partitioner;
+
+ private static final int DEFAULT_GRID_SIZE = 6;
+
+ private Step step;
+
+ private PartitionHandler partitionHandler;
+
+ private int gridSize = DEFAULT_GRID_SIZE;
+
+ private StepExecutionSplitter splitter;
+
+ private StepExecutionAggregator aggregator;
+
+ private String stepName;
+
+ public PartitionStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ public PartitionStepBuilder partitioner(String slaveStepName, Partitioner partitioner) {
+ this.stepName = slaveStepName;
+ this.partitioner = partitioner;
+ return this;
+ }
+
+ public PartitionStepBuilder step(Step step) {
+ this.step = step;
+ return this;
+ }
+
+ public PartitionStepBuilder taskExecutor(TaskExecutor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ return this;
+ }
+
+ public PartitionStepBuilder partitionHandler(PartitionHandler partitionHandler) {
+ this.partitionHandler = partitionHandler;
+ return this;
+ }
+
+ public PartitionStepBuilder gridSize(int gridSize) {
+ this.gridSize = gridSize;
+ return this;
+ }
+
+ public Step build() {
+
+ PartitionStep step = new PartitionStep();
+ step.setName(getName());
+ super.enhance(step);
+
+ if (partitionHandler != null) {
+ step.setPartitionHandler(partitionHandler);
+ }
+ else {
+ TaskExecutorPartitionHandler partitionHandler = new TaskExecutorPartitionHandler();
+ partitionHandler.setStep(this.step);
+ if (taskExecutor == null) {
+ taskExecutor = new SyncTaskExecutor();
+ }
+ partitionHandler.setGridSize(gridSize);
+ partitionHandler.setTaskExecutor(taskExecutor);
+ step.setPartitionHandler(partitionHandler);
+ }
+
+ if (splitter!=null) {
+ step.setStepExecutionSplitter(splitter);
+ } else {
+
+ boolean allowStartIfComplete = isAllowStartIfComplete();
+ String name = stepName;
+ if (this.step != null) {
+ try {
+ allowStartIfComplete = this.step.isAllowStartIfComplete();
+ name = this.step.getName();
+ }
+ catch (Exception e) {
+ logger.info("Ignored exception from step asking for name and allowStartIfComplete flag. "
+ + "Using default from enclosing PartitionStep (" + name + "," + allowStartIfComplete + ").");
+ }
+ }
+ SimpleStepExecutionSplitter splitter = new SimpleStepExecutionSplitter();
+ splitter.setPartitioner(partitioner);
+ splitter.setJobRepository(getJobRepository());
+ splitter.setAllowStartIfComplete(allowStartIfComplete);
+ splitter.setStepName(name);
+ this.splitter = splitter;
+ step.setStepExecutionSplitter(splitter);
+
+ }
+
+ if (aggregator!=null) {
+ step.setStepExecutionAggregator(aggregator);
+ }
+
+ try {
+ step.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+
+ return step;
+
+ }
+
+ public PartitionStepBuilder splitter(StepExecutionSplitter splitter) {
+ this.splitter = splitter;
+ return this;
+ }
+
+ public PartitionStepBuilder aggregator(StepExecutionAggregator aggregator) {
+ this.aggregator = aggregator;
+ return this;
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java
new file mode 100644
index 000000000..b92913685
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.ItemProcessListener;
+import org.springframework.batch.core.ItemReadListener;
+import org.springframework.batch.core.ItemWriteListener;
+import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.listener.StepListenerFactoryBean;
+import org.springframework.batch.core.step.item.ChunkOrientedTasklet;
+import org.springframework.batch.core.step.item.SimpleChunkProcessor;
+import org.springframework.batch.core.step.item.SimpleChunkProvider;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.core.step.tasklet.TaskletStep;
+import org.springframework.batch.item.ItemProcessor;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemStream;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.repeat.CompletionPolicy;
+import org.springframework.batch.repeat.RepeatOperations;
+import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
+import org.springframework.batch.repeat.support.RepeatTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class SimpleStepBuilder extends AbstractTaskletStepBuilder> {
+
+ private static final int DEFAULT_COMMIT_INTERVAL = 1;
+
+ private ItemReader extends I> reader;
+
+ private ItemWriter super O> writer;
+
+ private ItemProcessor super I, ? extends O> processor;
+
+ private int chunkSize = 0;
+
+ private RepeatOperations chunkOperations;
+
+ private CompletionPolicy completionPolicy;
+
+ private Set itemListeners = new LinkedHashSet();
+
+ private boolean readerTransactionalQueue = false;
+
+ public SimpleStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ @Override
+ public TaskletStep build() {
+ registerAsStreamsAndListeners(reader, processor, writer);
+ return super.build();
+ }
+
+ @Override
+ protected Tasklet createTasklet() {
+ Assert.state(reader != null, "ItemReader must be provided");
+ Assert.state(processor != null || writer != null, "ItemWriter or ItemProcessor must be provided");
+ RepeatOperations repeatOperations = createChunkOperations();
+ SimpleChunkProvider chunkProvider = new SimpleChunkProvider(reader, repeatOperations);
+ SimpleChunkProcessor chunkProcessor = new SimpleChunkProcessor(processor, writer);
+ chunkProvider.setListeners(new ArrayList(itemListeners));
+ chunkProcessor.setListeners(new ArrayList(itemListeners));
+ ChunkOrientedTasklet tasklet = new ChunkOrientedTasklet(chunkProvider, chunkProcessor);
+ tasklet.setBuffering(!readerTransactionalQueue);
+ return tasklet;
+ }
+
+ public SimpleStepBuilder readerIsTransactionalQueue() {
+ this.readerTransactionalQueue = true;
+ return this;
+ }
+
+ public SimpleStepBuilder listener(ItemReadListener super I> listener) {
+ itemListeners.add(listener);
+ return this;
+ }
+
+ public SimpleStepBuilder listener(ItemWriteListener super O> listener) {
+ itemListeners.add(listener);
+ return this;
+ }
+
+ public SimpleStepBuilder listener(ItemProcessListener super I, ? super O> listener) {
+ itemListeners.add(listener);
+ return this;
+ }
+
+ public SimpleStepBuilder chunkOperations(RepeatOperations repeatTemplate) {
+ this.chunkOperations = repeatTemplate;
+ return this;
+ }
+
+ public SimpleStepBuilder completionPolicy(CompletionPolicy completionPolicy) {
+ Assert.state(chunkSize == 0 || completionPolicy == null,
+ "You must specify either a chunkCompletionPolicy or a commitInterval but not both.");
+ this.completionPolicy = completionPolicy;
+ return this;
+ }
+
+ public SimpleStepBuilder chunk(int chunkSize) {
+ Assert.state(completionPolicy == null || chunkSize == 0,
+ "You must specify either a chunkCompletionPolicy or a commitInterval but not both.");
+ this.chunkSize = chunkSize;
+ return this;
+ }
+
+ public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) {
+ this.completionPolicy = completionPolicy;
+ return this;
+ }
+
+ public SimpleStepBuilder reader(ItemReader extends I> reader) {
+ this.reader = reader;
+ return this;
+ }
+
+ public SimpleStepBuilder writer(ItemWriter super O> writer) {
+ this.writer = writer;
+ return this;
+ }
+
+ public SimpleStepBuilder processor(ItemProcessor super I, ? extends O> processor) {
+ this.processor = processor;
+ return this;
+ }
+
+ protected RepeatOperations createChunkOperations() {
+ RepeatOperations repeatOperations = chunkOperations;
+ if (repeatOperations == null) {
+ RepeatTemplate repeatTemplate = new RepeatTemplate();
+ repeatTemplate.setCompletionPolicy(getChunkCompletionPolicy());
+ repeatOperations = repeatTemplate;
+ }
+ return repeatOperations;
+ }
+
+ protected ItemReader extends I> getReader() {
+ return reader;
+ }
+
+ protected ItemWriter super O> getWriter() {
+ return writer;
+ }
+
+ protected ItemProcessor super I, ? extends O> getProcessor() {
+ return processor;
+ }
+
+ protected int getChunkSize() {
+ return chunkSize;
+ }
+
+ protected boolean isReaderTransactionalQueue() {
+ return readerTransactionalQueue;
+ }
+
+ protected Set getItemListeners() {
+ return itemListeners;
+ }
+
+ /**
+ * @return a {@link CompletionPolicy} consistent with the chunk size and injected policy (if present).
+ */
+ private CompletionPolicy getChunkCompletionPolicy() {
+ Assert.state(!(completionPolicy != null && chunkSize > 0),
+ "You must specify either a chunkCompletionPolicy or a commitInterval but not both.");
+ Assert.state(chunkSize >= 0, "The commitInterval must be positive or zero (for default value).");
+
+ if (completionPolicy != null) {
+ return completionPolicy;
+ }
+ if (chunkSize == 0) {
+ logger.info("Setting commit interval to default value (" + DEFAULT_COMMIT_INTERVAL + ")");
+ chunkSize = DEFAULT_COMMIT_INTERVAL;
+ }
+ return new SimpleCompletionPolicy(chunkSize);
+ }
+
+ private void registerAsStreamsAndListeners(ItemReader extends I> itemReader,
+ ItemProcessor super I, ? extends O> itemProcessor, ItemWriter super O> itemWriter) {
+ for (Object itemHandler : new Object[] { itemReader, itemWriter, itemProcessor }) {
+ if (itemHandler instanceof ItemStream) {
+ stream((ItemStream) itemHandler);
+ }
+ if (StepListenerFactoryBean.isListener(itemHandler)) {
+ StepListener listener = StepListenerFactoryBean.getListener(itemHandler);
+ if (listener instanceof StepExecutionListener) {
+ listener((StepExecutionListener) listener);
+ }
+ if (listener instanceof ChunkListener) {
+ listener((ChunkListener) listener);
+ }
+ if (listener instanceof ItemReadListener> || listener instanceof ItemProcessListener, ?>
+ || listener instanceof ItemWriteListener>) {
+ itemListeners.add(listener);
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java
new file mode 100644
index 000000000..ed31cb85d
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.flow.Flow;
+import org.springframework.batch.core.partition.support.Partitioner;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.CompletionPolicy;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class StepBuilder extends StepBuilderHelper {
+
+ public StepBuilder(String name) {
+ super(name);
+ }
+
+ public TaskletStepBuilder tasklet(Tasklet tasklet) {
+ return new TaskletStepBuilder(this).tasklet(tasklet);
+ }
+
+ public SimpleStepBuilder chunk(int chunkSize) {
+ return new SimpleStepBuilder(this).chunk(chunkSize);
+ }
+
+ public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) {
+ return new SimpleStepBuilder(this).completionPolicy(completionPolicy);
+ }
+
+ public PartitionStepBuilder partitioner(String stepName, Partitioner partitioner) {
+ return new PartitionStepBuilder(this).partitioner(stepName, partitioner);
+ }
+
+ public PartitionStepBuilder partitioner(Step step) {
+ return new PartitionStepBuilder(this).step(step);
+ }
+
+ public JobStepBuilder job(Job job) {
+ return new JobStepBuilder(this).job(job);
+ }
+
+ public FlowStepBuilder flow(Flow flow) {
+ return new FlowStepBuilder(this).flow(flow);
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java
new file mode 100644
index 000000000..0538d3e54
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+public class StepBuilderException extends RuntimeException {
+
+ public StepBuilderException(Exception e) {
+ super(e);
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java
new file mode 100644
index 000000000..bb95db50f
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.StepExecutionListener;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.AbstractStep;
+import org.springframework.batch.core.step.tasklet.TaskletStep;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public abstract class StepBuilderHelper> {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final CommonStepProperties properties;
+
+ public StepBuilderHelper(String name) {
+ this.properties = new CommonStepProperties();
+ properties.name = name;
+ }
+
+ protected StepBuilderHelper(StepBuilderHelper> parent) {
+ this.properties = parent.properties;
+ }
+
+ public StepBuilderHelper repository(JobRepository jobRepository) {
+ properties.jobRepository = jobRepository;
+ return this;
+ }
+
+ public StepBuilderHelper transactionManager(PlatformTransactionManager transactionManager) {
+ properties.transactionManager = transactionManager;
+ return this;
+ }
+
+ public StepBuilderHelper startLimit(int startLimit) {
+ properties.startLimit = startLimit;
+ return this;
+ }
+
+ public StepBuilderHelper listener(StepExecutionListener listener) {
+ properties.addStepExecutionListener(listener);
+ return this;
+ }
+
+ public StepBuilderHelper allowStartIfComplete(boolean allowStartIfComplete) {
+ properties.allowStartIfComplete = allowStartIfComplete;
+ return this;
+ }
+
+ protected String getName() {
+ return properties.name;
+ }
+
+ protected JobRepository getJobRepository() {
+ return properties.jobRepository;
+ }
+
+ protected PlatformTransactionManager getTransactionManager() {
+ return properties.transactionManager;
+ }
+
+ protected boolean isAllowStartIfComplete() {
+ return properties.allowStartIfComplete != null ? properties.allowStartIfComplete : false;
+ }
+
+ protected void enhance(Step target) {
+
+ if (target instanceof AbstractStep) {
+
+ AbstractStep step = (AbstractStep) target;
+ step.setJobRepository(properties.getJobRepository());
+
+ Boolean allowStartIfComplete = properties.allowStartIfComplete;
+ if (allowStartIfComplete != null) {
+ step.setAllowStartIfComplete(allowStartIfComplete);
+ }
+
+ step.setStartLimit(properties.startLimit);
+
+ List listeners = properties.stepExecutionListeners;
+ if (!listeners.isEmpty()) {
+ step.setStepExecutionListeners(listeners.toArray(new StepExecutionListener[0]));
+ }
+
+ }
+
+ if (target instanceof TaskletStep) {
+ TaskletStep step = (TaskletStep) target;
+ step.setTransactionManager(properties.transactionManager);
+ }
+
+ }
+
+ public static class CommonStepProperties {
+
+ private List stepExecutionListeners = new ArrayList();
+
+ private int startLimit = Integer.MAX_VALUE;
+
+ private Boolean allowStartIfComplete;
+
+ private JobRepository jobRepository;
+
+ private PlatformTransactionManager transactionManager;
+
+ public JobRepository getJobRepository() {
+ return jobRepository;
+ }
+
+ public void setJobRepository(JobRepository jobRepository) {
+ this.jobRepository = jobRepository;
+ }
+
+ public PlatformTransactionManager getTransactionManager() {
+ return transactionManager;
+ }
+
+ public void setTransactionManager(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getStepExecutionListeners() {
+ return stepExecutionListeners;
+ }
+
+ public void addStepExecutionListeners(List stepExecutionListeners) {
+ this.stepExecutionListeners.addAll(stepExecutionListeners);
+ }
+
+ public void addStepExecutionListener(StepExecutionListener stepExecutionListener) {
+ this.stepExecutionListeners.add(stepExecutionListener);
+ }
+
+ public Integer getStartLimit() {
+ return startLimit;
+ }
+
+ public void setStartLimit(Integer startLimit) {
+ this.startLimit = startLimit;
+ }
+
+ public Boolean getAllowStartIfComplete() {
+ return allowStartIfComplete;
+ }
+
+ public void setAllowStartIfComplete(Boolean allowStartIfComplete) {
+ this.allowStartIfComplete = allowStartIfComplete;
+ }
+
+ private String name;
+
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java
new file mode 100644
index 000000000..9289dd4cc
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2006-2011 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.step.builder;
+
+import org.springframework.batch.core.step.tasklet.Tasklet;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class TaskletStepBuilder extends AbstractTaskletStepBuilder {
+
+ private Tasklet tasklet;
+
+ public TaskletStepBuilder(StepBuilderHelper> parent) {
+ super(parent);
+ }
+
+ public TaskletStepBuilder tasklet(Tasklet tasklet) {
+ this.tasklet = tasklet;
+ return this;
+ }
+
+ @Override
+ protected Tasklet createTasklet() {
+ return tasklet;
+ }
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchListenerFactoryHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
similarity index 89%
rename from spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchListenerFactoryHelper.java
rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
index e336c78fd..72a5ebe1b 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchListenerFactoryHelper.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.batch.core.step.item;
+package org.springframework.batch.core.step.factory;
import java.util.ArrayList;
import java.util.List;
@@ -30,8 +30,7 @@ abstract class BatchListenerFactoryHelper {
public static List getListeners(StepListener[] listeners, Class super T> cls) {
List list = new ArrayList();
- for (int i = 0; i < listeners.length; i++) {
- StepListener stepListener = listeners[i];
+ for (StepListener stepListener : listeners) {
if (cls.isAssignableFrom(stepListener.getClass())) {
@SuppressWarnings("unchecked")
T listener = (T) stepListener;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java
new file mode 100755
index 000000000..8cee29a06
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2006-2007 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.step.factory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.springframework.batch.core.SkipListener;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder;
+import org.springframework.batch.core.step.builder.SimpleStepBuilder;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.batch.core.step.item.KeyGenerator;
+import org.springframework.batch.core.step.skip.SkipPolicy;
+import org.springframework.retry.RetryListener;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.backoff.BackOffPolicy;
+import org.springframework.retry.policy.MapRetryContextCache;
+import org.springframework.retry.policy.RetryContextCache;
+
+/**
+ * Factory bean for step that provides options for configuring skip behaviour. User can set {@link #setSkipLimit(int)}
+ * to set how many exceptions of {@link #setSkippableExceptionClasses(Collection)} types are tolerated.
+ * {@link #setFatalExceptionClasses(Collection)} will cause immediate termination of job - they are treated as higher
+ * priority than {@link #setSkippableExceptionClasses(Collection)}, so the two lists don't need to be exclusive.
+ *
+ * Skippable exceptions on write will by default cause transaction rollback - to avoid rollback for specific exception
+ * class include it in the transaction attribute as "no rollback for".
+ *
+ * @see SimpleStepFactoryBean
+ *
+ * @author Dave Syer
+ * @author Robert Kasanicky
+ * @author Morten Andersen-Gott
+ *
+ */
+public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean {
+
+ private Map, Boolean> skippableExceptionClasses = new HashMap, Boolean>();
+
+ private Collection> noRollbackExceptionClasses = new HashSet>();
+
+ private Map, Boolean> retryableExceptionClasses = new HashMap, Boolean>();
+
+ private int cacheCapacity = 0;
+
+ private int retryLimit = 0;
+
+ private int skipLimit = 0;
+
+ private SkipPolicy skipPolicy;
+
+ private BackOffPolicy backOffPolicy;
+
+ private RetryListener[] retryListeners;
+
+ private RetryPolicy retryPolicy;
+
+ private RetryContextCache retryContextCache;
+
+ private KeyGenerator keyGenerator;
+
+ private boolean processorTransactional = true;
+
+ /**
+ * The {@link KeyGenerator} to use to identify failed items across rollback. Not used in the case of the
+ * {@link #setIsReaderTransactionalQueue(boolean) transactional queue flag} being false (the default).
+ *
+ * @param keyGenerator the {@link KeyGenerator} to set
+ */
+ public void setKeyGenerator(KeyGenerator keyGenerator) {
+ this.keyGenerator = keyGenerator;
+ }
+
+ /**
+ * Setter for the retry policy. If this is specified the other retry properties are ignored (retryLimit,
+ * backOffPolicy, retryableExceptionClasses).
+ *
+ * @param retryPolicy a stateless {@link RetryPolicy}
+ */
+ public void setRetryPolicy(RetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ }
+
+ /**
+ * 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) {
+ this.retryLimit = retryLimit;
+ }
+
+ /**
+ * Public setter for the capacity of the cache in the retry policy. If more 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.
+ *
+ * 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}.
+ *
+ * This property is ignored if the {@link #setRetryContextCache(RetryContextCache)} is set directly.
+ *
+ * @param cacheCapacity the cache capacity to set (greater than 0 else ignored)
+ */
+ public void setCacheCapacity(int cacheCapacity) {
+ this.cacheCapacity = cacheCapacity;
+ }
+
+ /**
+ * Override the default retry context cache for retry of chunk processing. If this property is set then
+ * {@link #setCacheCapacity(int)} is ignored.
+ *
+ * @param retryContextCache the {@link RetryContextCache} to set
+ */
+ public void setRetryContextCache(RetryContextCache retryContextCache) {
+ this.retryContextCache = retryContextCache;
+ }
+
+ /**
+ * Public setter for the retryable exceptions classifier map (from throwable class to boolean, true is retryable).
+ *
+ * @param retryableExceptionClasses the retryableExceptionClasses to set
+ */
+ public void setRetryableExceptionClasses(Map, Boolean> retryableExceptionClasses) {
+ this.retryableExceptionClasses = retryableExceptionClasses;
+ }
+
+ /**
+ * Public setter for the {@link BackOffPolicy}.
+ *
+ * @param backOffPolicy the {@link BackOffPolicy} to set
+ */
+ public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
+ this.backOffPolicy = backOffPolicy;
+ }
+
+ /**
+ * Public setter for the {@link RetryListener}s.
+ *
+ * @param retryListeners the {@link RetryListener}s to set
+ */
+ public void setRetryListeners(RetryListener... retryListeners) {
+ this.retryListeners = retryListeners;
+ }
+
+ /**
+ * A limit that determines skip policy. If this value is positive then an exception in chunk processing will cause
+ * the item to be 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) {
+ this.skipLimit = skipLimit;
+ }
+
+ /**
+ * A {@link SkipPolicy} that determines the outcome of an exception when processing an item. Overrides the
+ * {@link #setSkipLimit(int) skipLimit}. The {@link #setSkippableExceptionClasses(Map) skippableExceptionClasses}
+ * are also ignored if this is set.
+ *
+ * @param skipPolicy the {@link SkipPolicy} to set
+ */
+ public void setSkipPolicy(SkipPolicy skipPolicy) {
+ this.skipPolicy = skipPolicy;
+ }
+
+ /**
+ * Exception classes that when raised won't crash the job but will result in the item which handling caused the
+ * exception being skipped. Any exception which is marked for "no rollback" is also skippable, but not vice versa.
+ * Remember to set the {@link #setSkipLimit(int) skip limit} as well.
+ *
+ * Defaults to all no exception.
+ *
+ * @param exceptionClasses defaults to Exception
+ */
+ public void setSkippableExceptionClasses(Map, Boolean> exceptionClasses) {
+ this.skippableExceptionClasses = exceptionClasses;
+ }
+
+ /**
+ * Exception classes that are candidates for no rollback. The {@link Step} can not honour the no rollback hint in
+ * all circumstances, but any exception on this list is counted as skippable, so even if there has to be a rollback,
+ * then the step will not fail as long as the skip limit is not breached.
+ *
+ * Defaults is empty.
+ *
+ * @param noRollbackExceptionClasses the exception classes to set
+ */
+ public void setNoRollbackExceptionClasses(Collection> noRollbackExceptionClasses) {
+ this.noRollbackExceptionClasses = noRollbackExceptionClasses;
+ }
+
+ /**
+ * @param processorTransactional
+ */
+ public void setProcessorTransactional(boolean processorTransactional) {
+ this.processorTransactional = processorTransactional;
+ }
+
+ protected SimpleStepBuilder createBuilder(String name) {
+ return new FaultTolerantStepBuilder(new StepBuilder(name));
+ }
+
+ @Override
+ protected void applyConfiguration(SimpleStepBuilder builder) {
+
+ FaultTolerantStepBuilder faultTolerantBuilder = (FaultTolerantStepBuilder) builder;
+
+ if (retryContextCache == null && cacheCapacity > 0) {
+ retryContextCache = new MapRetryContextCache(cacheCapacity);
+ }
+ faultTolerantBuilder.retryContextCache(retryContextCache);
+ for (SkipListener listener : BatchListenerFactoryHelper.