Skip to content

Commit 1b8a012

Browse files
committed
Configure the right FieldExtractor based on the type of items in FlatFileItemWriterBuilder
Resolves #4161
1 parent 4d46d58 commit 1b8a012

File tree

2 files changed

+188
-15
lines changed

2 files changed

+188
-15
lines changed

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilder.java

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.batch.item.file.transform.FieldExtractor;
3232
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
3333
import org.springframework.batch.item.file.transform.LineAggregator;
34+
import org.springframework.batch.item.file.transform.RecordFieldExtractor;
3435
import org.springframework.core.io.WritableResource;
3536
import org.springframework.util.Assert;
3637

@@ -290,6 +291,8 @@ public static class FormattedBuilder<T> {
290291

291292
private List<String> names = new ArrayList<>();
292293

294+
private Class<T> sourceType;
295+
293296
protected FormattedBuilder(FlatFileItemWriterBuilder<T> parent) {
294297
this.parent = parent;
295298
}
@@ -336,6 +339,20 @@ public FormattedBuilder<T> maximumLength(int maximumLength) {
336339
return this;
337340
}
338341

342+
/**
343+
* Specify the type of items from which fields will be extracted. This is used to
344+
* configure the right {@link FieldExtractor} based on the given type (ie a record
345+
* or a regular class).
346+
* @param sourceType type of items from which fields will be extracted
347+
* @return The current instance of the builder.
348+
* @since 5.0
349+
*/
350+
public FormattedBuilder<T> sourceType(Class<T> sourceType) {
351+
this.sourceType = sourceType;
352+
353+
return this;
354+
}
355+
339356
/**
340357
* Set the {@link FieldExtractor} to use to extract fields from each item.
341358
* @param fieldExtractor to use to extract fields from each item
@@ -372,15 +389,20 @@ public FormatterLineAggregator<T> build() {
372389
formatterLineAggregator.setMaximumLength(this.maximumLength);
373390

374391
if (this.fieldExtractor == null) {
375-
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
376-
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
377-
try {
378-
beanWrapperFieldExtractor.afterPropertiesSet();
392+
if (this.sourceType != null && this.sourceType.isRecord()) {
393+
this.fieldExtractor = new RecordFieldExtractor<>(this.sourceType);
379394
}
380-
catch (Exception e) {
381-
throw new IllegalStateException("Unable to initialize FormatterLineAggregator", e);
395+
else {
396+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
397+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
398+
try {
399+
beanWrapperFieldExtractor.afterPropertiesSet();
400+
this.fieldExtractor = beanWrapperFieldExtractor;
401+
}
402+
catch (Exception e) {
403+
throw new IllegalStateException("Unable to initialize FormatterLineAggregator", e);
404+
}
382405
}
383-
this.fieldExtractor = beanWrapperFieldExtractor;
384406
}
385407

386408
formatterLineAggregator.setFieldExtractor(this.fieldExtractor);
@@ -404,6 +426,8 @@ public static class DelimitedBuilder<T> {
404426

405427
private FieldExtractor<T> fieldExtractor;
406428

429+
private Class<T> sourceType;
430+
407431
protected DelimitedBuilder(FlatFileItemWriterBuilder<T> parent) {
408432
this.parent = parent;
409433
}
@@ -419,6 +443,20 @@ public DelimitedBuilder<T> delimiter(String delimiter) {
419443
return this;
420444
}
421445

446+
/**
447+
* Specify the type of items from which fields will be extracted. This is used to
448+
* configure the right {@link FieldExtractor} based on the given type (ie a record
449+
* or a regular class).
450+
* @param sourceType type of items from which fields will be extracted
451+
* @return The current instance of the builder.
452+
* @since 5.0
453+
*/
454+
public DelimitedBuilder<T> sourceType(Class<T> sourceType) {
455+
this.sourceType = sourceType;
456+
457+
return this;
458+
}
459+
422460
/**
423461
* Names of each of the fields within the fields that are returned in the order
424462
* they occur within the delimited file. These names will be used to create a
@@ -453,15 +491,20 @@ public DelimitedLineAggregator<T> build() {
453491
}
454492

455493
if (this.fieldExtractor == null) {
456-
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
457-
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
458-
try {
459-
beanWrapperFieldExtractor.afterPropertiesSet();
494+
if (this.sourceType != null && this.sourceType.isRecord()) {
495+
this.fieldExtractor = new RecordFieldExtractor<>(this.sourceType);
460496
}
461-
catch (Exception e) {
462-
throw new IllegalStateException("Unable to initialize DelimitedLineAggregator", e);
497+
else {
498+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
499+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
500+
try {
501+
beanWrapperFieldExtractor.afterPropertiesSet();
502+
this.fieldExtractor = beanWrapperFieldExtractor;
503+
}
504+
catch (Exception e) {
505+
throw new IllegalStateException("Unable to initialize DelimitedLineAggregator", e);
506+
}
463507
}
464-
this.fieldExtractor = beanWrapperFieldExtractor;
465508
}
466509

467510
delimitedLineAggregator.setFieldExtractor(this.fieldExtractor);

spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilderTests.java

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import java.nio.charset.Charset;
2323
import java.util.Arrays;
2424

25+
import org.junit.Assert;
2526
import org.junit.Test;
2627

2728
import org.springframework.batch.item.ExecutionContext;
28-
2929
import org.springframework.batch.item.file.FlatFileItemWriter;
30+
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
31+
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
32+
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
3033
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
34+
import org.springframework.batch.item.file.transform.RecordFieldExtractor;
3135
import org.springframework.core.io.FileSystemResource;
3236
import org.springframework.core.io.Resource;
3337
import org.springframework.core.io.WritableResource;
@@ -236,6 +240,132 @@ public void testFlagsWithEncoding() throws Exception {
236240
validateBuilderFlags(writer, encoding);
237241
}
238242

243+
@Test
244+
public void testSetupDelimitedLineAggregatorWithRecordItemType() throws IOException {
245+
// given
246+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
247+
record Person(int id, String name) {
248+
}
249+
250+
// when
251+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
252+
.resource(output).delimited().sourceType(Person.class).names("id", "name").build();
253+
254+
// then
255+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
256+
Assert.assertNotNull(lineAggregator);
257+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
258+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
259+
Assert.assertNotNull(fieldExtractor);
260+
Assert.assertTrue(fieldExtractor instanceof RecordFieldExtractor);
261+
}
262+
263+
@Test
264+
public void testSetupDelimitedLineAggregatorWithClassItemType() throws IOException {
265+
// given
266+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
267+
class Person {
268+
269+
int id;
270+
271+
String name;
272+
273+
}
274+
275+
// when
276+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
277+
.resource(output).delimited().sourceType(Person.class).names("id", "name").build();
278+
279+
// then
280+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
281+
Assert.assertNotNull(lineAggregator);
282+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
283+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
284+
Assert.assertNotNull(fieldExtractor);
285+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
286+
}
287+
288+
@Test
289+
public void testSetupDelimitedLineAggregatorWithNoItemType() throws IOException {
290+
// given
291+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
292+
293+
// when
294+
FlatFileItemWriter writer = new FlatFileItemWriterBuilder<>().name("personWriter").resource(output).delimited()
295+
.names("id", "name").build();
296+
297+
// then
298+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
299+
Assert.assertNotNull(lineAggregator);
300+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
301+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
302+
Assert.assertNotNull(fieldExtractor);
303+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
304+
}
305+
306+
@Test
307+
public void testSetupFormatterLineAggregatorWithRecordItemType() throws IOException {
308+
// given
309+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
310+
record Person(int id, String name) {
311+
}
312+
313+
// when
314+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
315+
.resource(output).formatted().format("%2s%2s").sourceType(Person.class).names("id", "name").build();
316+
317+
// then
318+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
319+
Assert.assertNotNull(lineAggregator);
320+
Assert.assertTrue(lineAggregator instanceof FormatterLineAggregator);
321+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
322+
Assert.assertNotNull(fieldExtractor);
323+
Assert.assertTrue(fieldExtractor instanceof RecordFieldExtractor);
324+
}
325+
326+
@Test
327+
public void testSetupFormatterLineAggregatorWithClassItemType() throws IOException {
328+
// given
329+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
330+
class Person {
331+
332+
int id;
333+
334+
String name;
335+
336+
}
337+
338+
// when
339+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
340+
.resource(output).formatted().format("%2s%2s").sourceType(Person.class).names("id", "name").build();
341+
342+
// then
343+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
344+
Assert.assertNotNull(lineAggregator);
345+
Assert.assertTrue(lineAggregator instanceof FormatterLineAggregator);
346+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
347+
Assert.assertNotNull(fieldExtractor);
348+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
349+
}
350+
351+
@Test
352+
public void testSetupFormatterLineAggregatorWithNoItemType() throws IOException {
353+
// given
354+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
355+
356+
// when
357+
FlatFileItemWriter writer = new FlatFileItemWriterBuilder<>().name("personWriter").resource(output).formatted()
358+
.format("%2s%2s").names("id", "name").build();
359+
360+
// then
361+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
362+
Assert.assertNotNull(lineAggregator);
363+
Assert.assertTrue(lineAggregator instanceof FormatterLineAggregator);
364+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
365+
Assert.assertNotNull(fieldExtractor);
366+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
367+
}
368+
239369
private void validateBuilderFlags(FlatFileItemWriter<Foo> writer, String encoding) {
240370
assertFalse((Boolean) ReflectionTestUtils.getField(writer, "saveState"));
241371
assertTrue((Boolean) ReflectionTestUtils.getField(writer, "append"));

0 commit comments

Comments
 (0)