diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilder.java index 22e8c6411c..84b9b70504 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilder.java @@ -50,6 +50,7 @@ * @author Mahmoud Ben Hassine * @author Drummond Dawson * @author Stefano Cordio + * @author Hyunggeol Lee * @since 4.0 * @see FlatFileItemWriter */ @@ -395,7 +396,11 @@ public FormatterLineAggregator build() { if (this.fieldExtractor == null) { if (this.sourceType != null && this.sourceType.isRecord()) { - this.fieldExtractor = new RecordFieldExtractor<>(this.sourceType); + RecordFieldExtractor recordFieldExtractor = new RecordFieldExtractor<>(this.sourceType); + if (!this.names.isEmpty()) { + recordFieldExtractor.setNames(this.names.toArray(new String[0])); + } + this.fieldExtractor = recordFieldExtractor; } else { BeanWrapperFieldExtractor beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>(); @@ -512,7 +517,11 @@ public DelimitedLineAggregator build() { if (this.fieldExtractor == null) { if (this.sourceType != null && this.sourceType.isRecord()) { - this.fieldExtractor = new RecordFieldExtractor<>(this.sourceType); + RecordFieldExtractor recordFieldExtractor = new RecordFieldExtractor<>(this.sourceType); + if (!this.names.isEmpty()) { + recordFieldExtractor.setNames(this.names.toArray(new String[0])); + } + this.fieldExtractor = recordFieldExtractor; } else { BeanWrapperFieldExtractor beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilderTests.java index cb10246091..1d17345f89 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/builder/FlatFileItemWriterBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 2016-2025 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. @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -49,6 +50,7 @@ * @author Mahmoud Ben Hassine * @author Drummond Dawson * @author Glenn Renfro + * @author Hyunggeol Lee */ class FlatFileItemWriterBuilderTests { @@ -486,6 +488,191 @@ void testSetupFormatterLineAggregatorWithNoItemType() throws IOException { assertInstanceOf(BeanWrapperFieldExtractor.class, fieldExtractor); } + @Test + void testDelimitedWithRecordAndSelectedFields() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("delimited-selected", "csv")); + record Person(int id, String name, String email, int age) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("personWriter") + .resource(output) + .delimited() + .delimiter(",") + .sourceType(Person.class) + .names("name", "age") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("name", "age"), names); + } + + @Test + void testDelimitedWithRecordFieldReordering() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("delimited-reorder", "csv")); + record Employee(int id, String firstName, String lastName, String dept) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("employeeWriter") + .resource(output) + .delimited() + .delimiter("|") + .sourceType(Employee.class) + .names("lastName", "firstName", "id") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); + + Object delimiter = ReflectionTestUtils.getField(lineAggregator, "delimiter"); + assertEquals("|", delimiter); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("lastName", "firstName", "id"), names); + } + + @Test + void testDelimitedWithRecordAllFields() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("delimited-all", "csv")); + record Product(String code, String name, double price) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("productWriter") + .resource(output) + .delimited() + .sourceType(Product.class) + .names("code", "name", "price") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("code", "name", "price"), names); + } + + @Test + void testFormattedWithRecordAndSelectedFields() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("formatted-selected", "txt")); + record Person(int id, String name, String email) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("personWriter") + .resource(output) + .formatted() + .format("%-10s%3d") + .sourceType(Person.class) + .names("name", "id") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); + + Object format = ReflectionTestUtils.getField(lineAggregator, "format"); + assertEquals("%-10s%3d", format); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("name", "id"), names); + } + + @Test + void testFormattedWithRecordFieldReordering() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("formatted-reorder", "txt")); + record Employee(int id, String firstName, String lastName, String dept) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("employeeWriter") + .resource(output) + .formatted() + .format("%-15s%-15s%5d") + .sourceType(Employee.class) + .names("lastName", "firstName", "id") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); + + Object format = ReflectionTestUtils.getField(lineAggregator, "format"); + assertEquals("%-15s%-15s%5d", format); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("lastName", "firstName", "id"), names); + } + + @Test + void testFormattedWithRecordAllFields() throws IOException { + // given + WritableResource output = new FileSystemResource(File.createTempFile("formatted-all", "txt")); + record Product(String code, String name, double price) { + } + + // when + FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("productWriter") + .resource(output) + .formatted() + .format("%-10s%-20s%10.2f") + .sourceType(Product.class) + .names("code", "name", "price") + .build(); + + // then + Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); + assertNotNull(lineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); + + Object format = ReflectionTestUtils.getField(lineAggregator, "format"); + assertEquals("%-10s%-20s%10.2f", format); + + Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); + assertNotNull(fieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); + + Object names = ReflectionTestUtils.getField(fieldExtractor, "names"); + assertEquals(Arrays.asList("code", "name", "price"), names); + } + private void validateBuilderFlags(FlatFileItemWriter writer, String encoding) { assertFalse((Boolean) ReflectionTestUtils.getField(writer, "saveState")); assertTrue((Boolean) ReflectionTestUtils.getField(writer, "append"));