RESOLVED - issue BATCH-1093: Make AbstractJobTests.makeUniqueJobParameters() public

RESOLVED - issue BATCH-1096: Add late binding to some io samples 
RESOLVED - issue BATCH-1087: ClassPathXmlJobRegistry does not accept patterns for resources 
RESOLVED - BATCH-1098: added test for @AfterWrite in FaultTolerantChinkProcessorTests
This commit is contained in:
dsyer
2009-02-24 15:34:49 +00:00
parent 753bda60df
commit b5450ddc67
11 changed files with 157 additions and 56 deletions

View File

@@ -39,13 +39,20 @@ public class JobParametersBuilder {
private final Map<String, JobParameter> parameterMap;
/**
* Default constructor. Initializes the builder
* Default constructor. Initializes the builder with empty parameters.
*/
public JobParametersBuilder() {
this.parameterMap = new LinkedHashMap<String, JobParameter>();
}
/**
* Copy constructor. Initializes the builder with the supplied parameters.
*/
public JobParametersBuilder(JobParameters jobParameters) {
this.parameterMap = new LinkedHashMap<String, JobParameter>(jobParameters.getParameters());
}
/**
* Add a new String parameter for the given key.
*

View File

@@ -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<I, O> extends SimpleChunkProcessor<I, O> {
@@ -40,11 +41,11 @@ public class FaultTolerantChunkProcessor<I, O> extends SimpleChunkProcessor<I, O
private final BatchRetryTemplate batchRetryTemplate;
private Classifier<Throwable, Boolean> rollbackClassifier;
private Classifier<Throwable, Boolean> 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<I, O> extends SimpleChunkProcessor<I, O
}
try {
doWrite(Collections.singletonList(item));
writeItems(Collections.singletonList(item));
contribution.incrementWriteCount(1);
}
catch (Exception e) {

View File

@@ -121,7 +121,7 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
protected final void doWrite(List<O> 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<I, O> implements ChunkProcessor<I>, Initializi
}
}
protected void writeItems(List<O> items) throws Exception {
itemWriter.write(items);
}
public final void process(StepContribution contribution, Chunk<I> inputs) throws Exception {
// If there is no input we don't have to do anything more

View File

@@ -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<String> parameters = parametersBuilder.toJobParameters().getParameters().keySet().iterator();
assertEquals("STRING", parameters.next());
}
public void testOrderedTypes(){
parametersBuilder.addDate("SCHEDULE_DATE", date);
parametersBuilder.addLong("LONG", new Long(1));

View File

@@ -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<String> list = new ArrayList<String>();
@Test
public void testWrite() throws Exception {
FaultTolerantChunkProcessor<String, String> processor = new FaultTolerantChunkProcessor<String, String>(
new PassThroughItemProcessor<String>(), new ItemWriter<String>() {
private List<String> after = new ArrayList<String>();
private FaultTolerantChunkProcessor<String, String> processor;
private StepContribution contribution = new StepExecution("foo", new JobExecution(0L)).createStepContribution();
@Before
public void setUp() {
processor = new FaultTolerantChunkProcessor<String, String>(new PassThroughItemProcessor<String>(),
new ItemWriter<String>() {
public void write(List<? extends String> items) throws Exception {
if (items.contains("fail")) {
throw new RuntimeException("Planned failure!");
}
list.addAll(items);
}
}, batchRetryTemplate);
Chunk<String> inputs = new Chunk<String>();
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<String> inputs = new Chunk<String>(Arrays.asList("1", "2"));
processor.process(contribution, inputs);
assertEquals(2, list.size());
}
@Test
public void testTransform() throws Exception {
FaultTolerantChunkProcessor<String, String> processor = new FaultTolerantChunkProcessor<String, String>(
new ItemProcessor<String, String>() {
public String process(String item) throws Exception {
return item.equals("1") ? null : item;
}
}, new ItemWriter<String>() {
public void write(List<? extends String> items) throws Exception {
list.addAll(items);
}
}, batchRetryTemplate);
Chunk<String> inputs = new Chunk<String>();
inputs.add("1");
inputs.add("2");
processor.process(new StepExecution("foo", new JobExecution(0L)).createStepContribution(), inputs);
processor.setItemProcessor(new ItemProcessor<String, String>() {
public String process(String item) throws Exception {
return item.equals("1") ? null : item;
}
});
Chunk<String> inputs = new Chunk<String>(Arrays.asList("1", "2"));
processor.process(contribution, inputs);
assertEquals(1, list.size());
}
@Test
public void testAfterWrite() throws Exception {
Chunk<String> chunk = new Chunk<String>(Arrays.asList("foo", "fail", "bar"));
processor.setListeners(Arrays.asList(new ItemListenerSupport<String, String>() {
@Override
public void afterWrite(List<? extends String> 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());
}
}

View File

@@ -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<String> list = new ArrayList<String>();
private List<String> list = new ArrayList<String>();
@Before
public void setUp() {
processor = new SimpleChunkProcessor<String,String>(new PassThroughItemProcessor<String>(), new ItemWriter<String>() {
public void write(List<? extends String> items) throws Exception {
list.addAll(items);
}
});
processor = new SimpleChunkProcessor<String, String>(new PassThroughItemProcessor<String>(),
new ItemWriter<String>() {
public void write(List<? extends String> items) throws Exception {
if (items.contains("fail")) {
throw new RuntimeException("Planned failure!");
}
list.addAll(items);
}
});
}
@Test

View File

@@ -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">
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="data/iosample/input/delimited.csv" />
<bean id="itemReaderParent" class="org.springframework.batch.item.file.FlatFileItemReader" abstract="true">
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
@@ -30,6 +29,14 @@
</property>
</bean>
<bean id="itemReader" parent="itemReaderParent" scope="step" autowire-candidate="false">
<property name="resource" value="#{jobParameters[fileName]}" />
</bean>
<bean id="itemReaderForTest" parent="itemReaderParent" >
<property name="resource" value="data/iosample/input/delimited.csv" />
</bean>
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputResource" />
<property name="lineAggregator">

View File

@@ -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">
<bean id="itemReader"
class="org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="classpath:data/iosample/input/delimited*.csv" />
<bean id="itemReaderParent"
class="org.springframework.batch.item.file.MultiResourceItemReader" abstract="true">
<property name="delegate">
<bean class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
@@ -38,6 +37,14 @@
</property>
</bean>
<bean id="itemReader" parent="itemReaderParent" scope="step" autowire-candidate="false">
<property name="resources" value="#{jobParameters[file.path]}/delimited*.csv" />
</bean>
<bean id="itemReaderForTest" parent="itemReaderParent">
<property name="resources" value="classpath:data/iosample/input/delimited*.csv" />
</bean>
<bean id="itemWriter"
class="org.springframework.batch.item.file.MultiResourceItemWriter">
<property name="resource"

View File

@@ -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.FlatFileItemReader;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
@@ -36,11 +38,17 @@ public class DelimitedFunctionalTests extends AbstractIoSampleTests {
@Autowired
private Resource outputResource;
@Override
protected void pointReaderToOutput(ItemReader<CustomerCredit> reader) {
FlatFileItemReader<CustomerCredit> fileReader = (FlatFileItemReader<CustomerCredit>) reader;
fileReader.setResource(outputResource);
}
}
@Override
protected JobParameters getUniqueJobParameters() {
return new JobParametersBuilder(super.getUniqueJobParameters()).addString("fileName",
"data/iosample/input/delimited.csv").toJobParameters();
}
}

View File

@@ -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();
}
}

View File

@@ -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<String, JobParameter> parameters = new HashMap<String, JobParameter>();
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);