Skip to content

Commit b19e11f

Browse files
committed
Add java configuration for the multi-record type sample
Issue spring-projects#3663
1 parent 52beed4 commit b19e11f

File tree

8 files changed

+317
-68
lines changed

8 files changed

+317
-68
lines changed

spring-batch-samples/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,29 @@ END
230230

231231
[MultiLine Input Job Sample](./src/main/java/org/springframework/batch/sample/file/multiline/README.md)
232232

233+
### MultiRecord type Input Job
234+
235+
The goal of this sample is to show how to use the `PatternMatchingCompositeLineMapper` API
236+
to process files containing lines of different types:
237+
238+
```
239+
CUST42001customer100012000
240+
CUST42002customer200022000
241+
CUST42003customer300032000
242+
TRADUK21341EAH45978 98.34customer1
243+
TRADUK21341EAH46112 18.12customer2
244+
CUST42004customer400042000
245+
CUST42005customer500052000
246+
TRADUK21341EAH47245 12.78customer3
247+
TRADUK21341EAH48108109.25customer4
248+
TRADUK21341EAH49854123.39customer5
249+
CUST42006customer600062000
250+
TRADUK21341EAH50234 32.45customer6
251+
...
252+
```
253+
254+
[MultiRecord type Input Job Sample](./src/main/java/org/springframework/batch/sample/file/multirecordtype/README.md)
255+
233256
### Football Job
234257

235258
This is a (American) Football statistics loading job. It loads two files containing players and games
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2014 the original author or authors.
2+
* Copyright 2006-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.batch.sample.iosample.internal;
17+
package org.springframework.batch.sample.file.multirecordtype;
1818

1919
import org.springframework.batch.item.file.transform.LineAggregator;
2020
import org.springframework.batch.sample.domain.trade.CustomerCredit;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.sample.file.multirecordtype;
17+
18+
import java.util.Map;
19+
20+
import javax.sql.DataSource;
21+
22+
import org.springframework.batch.core.Job;
23+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
24+
import org.springframework.batch.core.configuration.annotation.StepScope;
25+
import org.springframework.batch.core.job.builder.JobBuilder;
26+
import org.springframework.batch.core.repository.JobRepository;
27+
import org.springframework.batch.core.step.builder.StepBuilder;
28+
import org.springframework.batch.item.file.FlatFileItemReader;
29+
import org.springframework.batch.item.file.FlatFileItemWriter;
30+
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
31+
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
32+
import org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper;
33+
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
34+
import org.springframework.batch.item.file.transform.FixedLengthTokenizer;
35+
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
36+
import org.springframework.batch.item.file.transform.Range;
37+
import org.springframework.batch.sample.domain.trade.CustomerCredit;
38+
import org.springframework.batch.sample.domain.trade.Trade;
39+
import org.springframework.batch.sample.domain.trade.internal.CustomerCreditFieldSetMapper;
40+
import org.springframework.batch.sample.domain.trade.internal.TradeFieldSetMapper;
41+
import org.springframework.beans.factory.annotation.Value;
42+
import org.springframework.context.annotation.Bean;
43+
import org.springframework.context.annotation.Configuration;
44+
import org.springframework.core.io.Resource;
45+
import org.springframework.core.io.WritableResource;
46+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
47+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
48+
import org.springframework.jdbc.support.JdbcTransactionManager;
49+
50+
/**
51+
* @author Mahmoud Ben Hassine
52+
*/
53+
@Configuration
54+
@EnableBatchProcessing
55+
public class MultiRecordTypeJobConfiguration {
56+
57+
@Bean
58+
@StepScope
59+
public FlatFileItemReader itemReader(PatternMatchingCompositeLineMapper lineMapper,
60+
@Value("#{jobParameters[inputFile]}") Resource resource) {
61+
return new FlatFileItemReaderBuilder().name("itemReader").resource(resource).lineMapper(lineMapper).build();
62+
}
63+
64+
@Bean
65+
public PatternMatchingCompositeLineMapper prefixMatchingLineMapper() {
66+
PatternMatchingCompositeLineMapper mapper = new PatternMatchingCompositeLineMapper();
67+
mapper.setTokenizers(Map.of("TRAD*", tradeLineTokenizer(), "CUST*", customerLineTokenizer()));
68+
mapper.setFieldSetMappers(
69+
Map.of("TRAD*", new TradeFieldSetMapper(), "CUST*", new CustomerCreditFieldSetMapper()));
70+
return mapper;
71+
}
72+
73+
@Bean
74+
public FixedLengthTokenizer tradeLineTokenizer() {
75+
FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();
76+
tokenizer.setNames("isin", "quantity", "price", "customer");
77+
tokenizer.setColumns(new Range(5, 16), new Range(17, 19), new Range(20, 25), new Range(26, 34));
78+
return tokenizer;
79+
}
80+
81+
@Bean
82+
public FixedLengthTokenizer customerLineTokenizer() {
83+
FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();
84+
tokenizer.setNames("id", "name", "credit");
85+
tokenizer.setColumns(new Range(5, 9), new Range(10, 18), new Range(19, 26));
86+
return tokenizer;
87+
}
88+
89+
@Bean
90+
@StepScope
91+
public FlatFileItemWriter itemWriter(DelegatingTradeLineAggregator delegatingTradeLineAggregator,
92+
@Value("#{jobParameters[outputFile]}") WritableResource resource) {
93+
return new FlatFileItemWriterBuilder().name("iemWriter")
94+
.resource(resource)
95+
.lineAggregator(delegatingTradeLineAggregator)
96+
.build();
97+
}
98+
99+
@Bean
100+
public DelegatingTradeLineAggregator delegatingTradeLineAggregator(
101+
FormatterLineAggregator<Trade> tradeLineAggregator,
102+
FormatterLineAggregator<CustomerCredit> customerLineAggregator) {
103+
DelegatingTradeLineAggregator lineAggregator = new DelegatingTradeLineAggregator();
104+
lineAggregator.setTradeLineAggregator(tradeLineAggregator);
105+
lineAggregator.setCustomerLineAggregator(customerLineAggregator);
106+
return lineAggregator;
107+
}
108+
109+
@Bean
110+
public FormatterLineAggregator<Trade> tradeLineAggregator() {
111+
FormatterLineAggregator<Trade> formatterLineAggregator = new FormatterLineAggregator<>();
112+
BeanWrapperFieldExtractor<Trade> fieldExtractor = new BeanWrapperFieldExtractor<>();
113+
fieldExtractor.setNames(new String[] { "isin", "quantity", "price", "customer" });
114+
formatterLineAggregator.setFieldExtractor(fieldExtractor);
115+
formatterLineAggregator.setFormat("TRAD%-12s%-3d%6s%-9s");
116+
return formatterLineAggregator;
117+
}
118+
119+
@Bean
120+
public FormatterLineAggregator<CustomerCredit> customerLineAggregator() {
121+
FormatterLineAggregator<CustomerCredit> formatterLineAggregator = new FormatterLineAggregator<>();
122+
BeanWrapperFieldExtractor<CustomerCredit> fieldExtractor = new BeanWrapperFieldExtractor<>();
123+
fieldExtractor.setNames(new String[] { "id", "name", "credit" });
124+
formatterLineAggregator.setFieldExtractor(fieldExtractor);
125+
formatterLineAggregator.setFormat("CUST%05d%-9s%08.0f");
126+
return formatterLineAggregator;
127+
}
128+
129+
@Bean
130+
public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager,
131+
FlatFileItemReader itemReader, FlatFileItemWriter itemWriter) {
132+
return new JobBuilder("ioSampleJob", jobRepository)
133+
.start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager)
134+
.reader(itemReader)
135+
.writer(itemWriter)
136+
.build())
137+
.build();
138+
}
139+
140+
// Infrastructure beans
141+
142+
@Bean
143+
public DataSource dataSource() {
144+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
145+
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
146+
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
147+
.generateUniqueName(true)
148+
.build();
149+
}
150+
151+
@Bean
152+
public JdbcTransactionManager transactionManager(DataSource dataSource) {
153+
return new JdbcTransactionManager(dataSource);
154+
}
155+
156+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
### MultiRecord type Input Job
2+
3+
## About
4+
5+
The goal of this sample is to show how to use the `PatternMatchingCompositeLineMapper` API
6+
to process files containing lines of different types:
7+
8+
```
9+
CUST42001customer100012000
10+
CUST42002customer200022000
11+
CUST42003customer300032000
12+
TRADUK21341EAH45978 98.34customer1
13+
TRADUK21341EAH46112 18.12customer2
14+
CUST42004customer400042000
15+
CUST42005customer500052000
16+
TRADUK21341EAH47245 12.78customer3
17+
TRADUK21341EAH48108109.25customer4
18+
TRADUK21341EAH49854123.39customer5
19+
CUST42006customer600062000
20+
TRADUK21341EAH50234 32.45customer6
21+
...
22+
```
23+
24+
## Run the sample
25+
26+
You can run the sample from the command line as following:
27+
28+
```
29+
$>cd spring-batch-samples
30+
# Launch the sample using the XML configuration
31+
$>../mvnw -Dtest=MultiRecordTypeFunctionalTests#testLaunchJobWithXmlConfig test
32+
# Launch the sample using the Java configuration
33+
$>../mvnw -Dtest=MultiRecordTypeFunctionalTests#testLaunchJobWithJavaConfig test
34+
```
35+

spring-batch-samples/src/main/resources/jobs/iosample/multiRecordType.xml renamed to spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/multirecordtype/job/multiRecordType.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
</batch:step>
1515
</batch:job>
1616

17-
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
18-
<property name="resource" value="data/iosample/input/multiRecordType.txt" />
17+
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
18+
<property name="resource" value="#{jobParameters[inputFile]}" />
1919
<property name="lineMapper" ref="prefixMatchingLineMapper"/>
2020
</bean>
2121

@@ -54,10 +54,10 @@
5454
class="org.springframework.batch.sample.domain.trade.internal.CustomerCreditFieldSetMapper" />
5555

5656

57-
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
58-
<property name="resource" value="file:target/test-outputs/multiRecordTypeOutput.txt" />
57+
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
58+
<property name="resource" value="#{jobParameters[outputFile]}" />
5959
<property name="lineAggregator">
60-
<bean class="org.springframework.batch.sample.iosample.internal.DelegatingTradeLineAggregator">
60+
<bean class="org.springframework.batch.sample.file.multirecordtype.DelegatingTradeLineAggregator">
6161
<property name="tradeLineAggregator" ref="tradeLineAggregator" />
6262
<property name="customerLineAggregator" ref="customerLineAggregator" />
6363
</bean>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2006-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.sample.file.multirecordtype;
18+
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
22+
import org.junit.jupiter.api.Assertions;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.batch.core.BatchStatus;
26+
import org.springframework.batch.core.Job;
27+
import org.springframework.batch.core.JobExecution;
28+
import org.springframework.batch.core.JobParameters;
29+
import org.springframework.batch.core.JobParametersBuilder;
30+
import org.springframework.batch.core.launch.JobLauncher;
31+
import org.springframework.batch.sample.file.multiline.MultiLineJobConfiguration;
32+
import org.springframework.batch.test.JobLauncherTestUtils;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.context.ApplicationContext;
35+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
36+
import org.springframework.core.io.ClassPathResource;
37+
import org.springframework.core.io.FileSystemResource;
38+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
39+
40+
import static org.junit.jupiter.api.Assertions.assertEquals;
41+
42+
/**
43+
* @author Dan Garrette
44+
* @author Mahmoud Ben Hassine
45+
* @author Glenn Renfro
46+
* @since 2.0
47+
*/
48+
@SpringJUnitConfig(locations = { "/org/springframework/batch/sample/file/multirecordtype/job/multiRecordType.xml",
49+
"/simple-job-launcher-context.xml", "/job-runner-context.xml" })
50+
class MultiRecordTypeFunctionalTests {
51+
52+
private static final String OUTPUT_FILE = "target/test-outputs/multiRecordTypeOutput.txt";
53+
54+
private static final String INPUT_FILE = "org/springframework/batch/sample/file/multirecordtype/data/multiRecordType.txt";
55+
56+
@Autowired
57+
private JobLauncherTestUtils jobLauncherTestUtils;
58+
59+
@Test
60+
void testLaunchJobWithXmlConfig() throws Exception {
61+
// given
62+
JobParameters jobParameters = new JobParametersBuilder().addString("inputFile", INPUT_FILE)
63+
.addString("outputFile", "file:./" + OUTPUT_FILE)
64+
.toJobParameters();
65+
66+
// when
67+
JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
68+
69+
// then
70+
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
71+
Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath();
72+
Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath();
73+
Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile));
74+
}
75+
76+
@Test
77+
public void testLaunchJobWithJavaConfig() throws Exception {
78+
// given
79+
ApplicationContext context = new AnnotationConfigApplicationContext(MultiRecordTypeJobConfiguration.class);
80+
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
81+
Job job = context.getBean(Job.class);
82+
JobParameters jobParameters = new JobParametersBuilder().addString("inputFile", INPUT_FILE)
83+
.addString("outputFile", "file:./" + OUTPUT_FILE)
84+
.toJobParameters();
85+
86+
// when
87+
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
88+
89+
// then
90+
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
91+
Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath();
92+
Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath();
93+
Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile));
94+
}
95+
96+
}

0 commit comments

Comments
 (0)