diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java index 9b3ed50b3..03ef38bc0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java @@ -39,13 +39,20 @@ public class JobParametersBuilder { private final Map parameterMap; /** - * Default constructor. Initializes the builder + * Default constructor. Initializes the builder with empty parameters. */ public JobParametersBuilder() { this.parameterMap = new LinkedHashMap(); } + /** + * Copy constructor. Initializes the builder with the supplied parameters. + */ + public JobParametersBuilder(JobParameters jobParameters) { + this.parameterMap = new LinkedHashMap(jobParameters.getParameters()); + } + /** * Add a new String parameter for the given key. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java index dadcc1d8a..5e96331fb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java @@ -30,6 +30,7 @@ import org.springframework.batch.retry.RetryCallback; import org.springframework.batch.retry.RetryContext; import org.springframework.batch.retry.RetryException; import org.springframework.batch.retry.support.DefaultRetryState; +import org.springframework.batch.support.BinaryExceptionClassifier; import org.springframework.batch.support.Classifier; public class FaultTolerantChunkProcessor extends SimpleChunkProcessor { @@ -40,11 +41,11 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor rollbackClassifier; + private Classifier rollbackClassifier = new BinaryExceptionClassifier(true); private Log logger = LogFactory.getLog(getClass()); - private boolean buffering; + private boolean buffering = true; public void setProcessSkipPolicy(SkipPolicy SkipPolicy) { this.itemProcessSkipPolicy = SkipPolicy; @@ -155,7 +156,7 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor implements ChunkProcessor, Initializi protected final void doWrite(List items) throws Exception { try { listener.beforeWrite(items); - itemWriter.write(items); + writeItems(items); listener.afterWrite(items); } catch (Exception e) { @@ -130,6 +130,10 @@ public class SimpleChunkProcessor implements ChunkProcessor, Initializi } } + protected void writeItems(List items) throws Exception { + itemWriter.write(items); + } + public final void process(StepContribution contribution, Chunk inputs) throws Exception { // If there is no input we don't have to do anything more diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java index 132558a62..1c2022d4c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java @@ -28,6 +28,13 @@ public class JobParametersBuilderTests extends TestCase { assertEquals("string value", parameters.getString("STRING")); } + public void testCopy(){ + parametersBuilder.addString("STRING", "string value"); + parametersBuilder = new JobParametersBuilder(parametersBuilder.toJobParameters()); + Iterator parameters = parametersBuilder.toJobParameters().getParameters().keySet().iterator(); + assertEquals("STRING", parameters.next()); + } + public void testOrderedTypes(){ parametersBuilder.addDate("SCHEDULE_DATE", date); parametersBuilder.addLong("LONG", new Long(1)); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java index 2b518b29b..2e0e36d9d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java @@ -3,14 +3,20 @@ package org.springframework.batch.core.step.item; import static org.junit.Assert.assertEquals; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.listener.ItemListenerSupport; +import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.support.PassThroughItemProcessor; +import org.springframework.batch.retry.policy.NeverRetryPolicy; public class FaultTolerantChunkProcessorTests { @@ -18,37 +24,74 @@ public class FaultTolerantChunkProcessorTests { private List list = new ArrayList(); - @Test - public void testWrite() throws Exception { - FaultTolerantChunkProcessor processor = new FaultTolerantChunkProcessor( - new PassThroughItemProcessor(), new ItemWriter() { + private List after = new ArrayList(); + + private FaultTolerantChunkProcessor processor; + + private StepContribution contribution = new StepExecution("foo", new JobExecution(0L)).createStepContribution(); + + @Before + public void setUp() { + processor = new FaultTolerantChunkProcessor(new PassThroughItemProcessor(), + new ItemWriter() { public void write(List items) throws Exception { + if (items.contains("fail")) { + throw new RuntimeException("Planned failure!"); + } list.addAll(items); } }, batchRetryTemplate); - Chunk inputs = new Chunk(); - inputs.add("1"); - inputs.add("2"); - processor.process(new StepExecution("foo", new JobExecution(0L)).createStepContribution(), inputs); + batchRetryTemplate.setRetryPolicy(new NeverRetryPolicy()); + } + + @Test + public void testWrite() throws Exception { + Chunk inputs = new Chunk(Arrays.asList("1", "2")); + processor.process(contribution, inputs); assertEquals(2, list.size()); } @Test public void testTransform() throws Exception { - FaultTolerantChunkProcessor processor = new FaultTolerantChunkProcessor( - new ItemProcessor() { - public String process(String item) throws Exception { - return item.equals("1") ? null : item; - } - }, new ItemWriter() { - public void write(List items) throws Exception { - list.addAll(items); - } - }, batchRetryTemplate); - Chunk inputs = new Chunk(); - inputs.add("1"); - inputs.add("2"); - processor.process(new StepExecution("foo", new JobExecution(0L)).createStepContribution(), inputs); + processor.setItemProcessor(new ItemProcessor() { + public String process(String item) throws Exception { + return item.equals("1") ? null : item; + } + }); + Chunk inputs = new Chunk(Arrays.asList("1", "2")); + processor.process(contribution, inputs); assertEquals(1, list.size()); } + + @Test + public void testAfterWrite() throws Exception { + Chunk chunk = new Chunk(Arrays.asList("foo", "fail", "bar")); + processor.setListeners(Arrays.asList(new ItemListenerSupport() { + @Override + public void afterWrite(List item) { + after.addAll(item); + } + })); + processor.setWriteSkipPolicy(new AlwaysSkipItemSkipPolicy()); + try { + processor.process(contribution, chunk); + } + catch (RuntimeException e) { + assertEquals("Planned failure!", e.getMessage()); + } + try { + processor.process(contribution, chunk); + } + catch (RuntimeException e) { + assertEquals("Planned failure!", e.getMessage()); + } + assertEquals(2, chunk.getItems().size()); + processor.process(contribution, chunk); + // foo is written twice because the failure is detected on the second + // attempt when throttling + assertEquals("[foo, foo, bar]", list.toString()); + // but the after listener is only called once, which is important + assertEquals(2, after.size()); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java index f150151c7..c154d8015 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java @@ -22,15 +22,19 @@ public class SimpleChunkProcessorTests { private StepContribution contribution = new StepContribution(new StepExecution("foo", new JobExecution( new JobInstance(123L, new JobParameters(), "job")))); - protected List list = new ArrayList(); + private List list = new ArrayList(); @Before public void setUp() { - processor = new SimpleChunkProcessor(new PassThroughItemProcessor(), new ItemWriter() { - public void write(List items) throws Exception { - list.addAll(items); - } - }); + processor = new SimpleChunkProcessor(new PassThroughItemProcessor(), + new ItemWriter() { + public void write(List items) throws Exception { + if (items.contains("fail")) { + throw new RuntimeException("Planned failure!"); + } + list.addAll(items); + } + }); } @Test diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml b/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml index 45196b0f7..6333f2651 100644 --- a/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml +++ b/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml @@ -11,8 +11,7 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> - - + @@ -30,6 +29,14 @@ + + + + + + + + diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/multiResource.xml b/spring-batch-samples/src/main/resources/jobs/iosample/multiResource.xml index b804d9dcb..74a998186 100644 --- a/spring-batch-samples/src/main/resources/jobs/iosample/multiResource.xml +++ b/spring-batch-samples/src/main/resources/jobs/iosample/multiResource.xml @@ -9,9 +9,8 @@ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> - - + @@ -38,6 +37,14 @@ + + + + + + + + reader) { FlatFileItemReader fileReader = (FlatFileItemReader) reader; fileReader.setResource(outputResource); } - -} + + @Override + protected JobParameters getUniqueJobParameters() { + return new JobParametersBuilder(super.getUniqueJobParameters()).addString("fileName", + "data/iosample/input/delimited.csv").toJobParameters(); + } + +} \ No newline at end of file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/MultiResourceFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/MultiResourceFunctionalTests.java index 20cb31951..8269746e5 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/MultiResourceFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/MultiResourceFunctionalTests.java @@ -17,6 +17,8 @@ package org.springframework.batch.sample.iosample; import org.junit.runner.RunWith; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.file.MultiResourceItemReader; import org.springframework.batch.sample.domain.trade.CustomerCredit; @@ -42,5 +44,11 @@ public class MultiResourceFunctionalTests extends AbstractIoSampleTests { new FileSystemResource("target/test-outputs/multiResourceOutput.csv.2") }); } + + @Override + protected JobParameters getUniqueJobParameters() { + JobParametersBuilder builder = new JobParametersBuilder(super.getUniqueJobParameters()); + return builder.addString("file.path", "classpath:data/iosample/input/").toJobParameters(); + } } diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java index e0ee50f3f..881e24b21 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java @@ -41,8 +41,8 @@ import org.springframework.context.ApplicationContext; * entire {@link AbstractJob}, allowing for end to end testing of individual * steps, without having to run every step in the job. Any test classes * inheriting from this class should make sure they are part of an - * {@link ApplicationContext}, which is generally expected to be done as part - * of the Spring test framework. Furthermore, the {@link ApplicationContext} in + * {@link ApplicationContext}, which is generally expected to be done as part of + * the Spring test framework. Furthermore, the {@link ApplicationContext} in * which it is a part of is expected to have one {@link JobLauncher}, * {@link JobRepository}, and a single {@link AbstractJob} implementation. * @@ -79,14 +79,14 @@ public abstract class AbstractJobTests { private StepRunner stepRunner; /** - * @return the job repository + * @return the job repository which is autowired by type */ public JobRepository getJobRepository() { return jobRepository; } /** - * @return the job + * @return the job which is autowired by type */ public AbstractJob getJob() { return job; @@ -102,34 +102,40 @@ public abstract class AbstractJobTests { /** * Launch the entire job, including all steps. * - * @return JobExecution, so that the test may validate the exit status + * @return JobExecution, so that the test can validate the exit status * @throws Exception */ - public JobExecution launchJob() throws Exception { - return this.launchJob(this.makeUniqueJobParameters()); + protected JobExecution launchJob() throws Exception { + return this.launchJob(this.getUniqueJobParameters()); } /** * Launch the entire job, including all steps * * @param jobParameters - * @return JobExecution, so that the test may validate the exit status + * @return JobExecution, so that the test can validate the exit status * @throws Exception */ - public JobExecution launchJob(JobParameters jobParameters) throws Exception { + protected JobExecution launchJob(JobParameters jobParameters) throws Exception { return getJobLauncher().run(this.job, jobParameters); } /** * @return a new JobParameters object containing only a parameter for the - * current timestamp, to ensure that the job instance will be unique + * current timestamp, to ensure that the job instance will be unique. */ - public JobParameters makeUniqueJobParameters() { + protected JobParameters getUniqueJobParameters() { Map parameters = new HashMap(); parameters.put("timestamp", new JobParameter(new Date().getTime())); return new JobParameters(parameters); } + /** + * Convenient method for subclasses to grab a {@link StepRunner} for running + * steps by name. + * + * @return a {@link StepRunner} + */ protected StepRunner getStepRunner() { if (this.stepRunner == null) { this.stepRunner = new StepRunner(getJobLauncher(), getJobRepository()); @@ -138,16 +144,15 @@ public abstract class AbstractJobTests { } /** - * Launch just the specified step in the job. An IllegalStateException is thrown - * if there is no Step with the given name. + * Launch just the specified step in the job. An IllegalStateException is + * thrown if there is no Step with the given name. * * @param stepName * @return JobExecution */ public JobExecution launchStep(String stepName) { Step step = this.job.getStep(stepName); - if(step == null) - { + if (step == null) { throw new IllegalStateException("No Step found with name: [" + stepName + "]"); } return getStepRunner().launchStep(step);