Skip to content

Commit 4f1b4ce

Browse files
committed
[WIP] Configure the right FieldExtractor based on the type of items in FlatFileItemWriterBuilder
This is an attempt to get the generic type information at runtime from the builder and use it to detect if items type is a record or a regular class. This seems impossible due to type erasure. Issue spring-projects#4161
1 parent 4d46d58 commit 4f1b4ce

File tree

2 files changed

+131
-14
lines changed

2 files changed

+131
-14
lines changed

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

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.batch.item.file.builder;
1717

18+
import java.lang.reflect.ParameterizedType;
19+
import java.lang.reflect.Type;
20+
import java.lang.reflect.TypeVariable;
1821
import java.util.ArrayList;
1922
import java.util.Arrays;
2023
import java.util.List;
@@ -31,8 +34,16 @@
3134
import org.springframework.batch.item.file.transform.FieldExtractor;
3235
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
3336
import org.springframework.batch.item.file.transform.LineAggregator;
37+
import org.springframework.batch.item.file.transform.RecordFieldExtractor;
38+
import org.springframework.cglib.core.TypeUtils;
39+
import org.springframework.core.GenericTypeResolver;
40+
import org.springframework.core.ParameterizedTypeReference;
41+
import org.springframework.core.ResolvableType;
3442
import org.springframework.core.io.WritableResource;
3543
import org.springframework.util.Assert;
44+
import org.springframework.util.ClassUtils;
45+
import org.springframework.util.ObjectUtils;
46+
import org.springframework.util.ReflectionUtils;
3647

3748
/**
3849
* A builder implementation for the {@link FlatFileItemWriter}
@@ -46,7 +57,7 @@
4657
*/
4758
public class FlatFileItemWriterBuilder<T> {
4859

49-
protected Log logger = LogFactory.getLog(getClass());
60+
protected static Log logger = LogFactory.getLog(FlatFileItemWriterBuilder.class);
5061

5162
private WritableResource resource;
5263

@@ -372,21 +383,41 @@ public FormatterLineAggregator<T> build() {
372383
formatterLineAggregator.setMaximumLength(this.maximumLength);
373384

374385
if (this.fieldExtractor == null) {
375-
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
376-
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
386+
FieldExtractor<T> fieldExtractor = null;
387+
logger.info("No field extractor specified, trying to introspect item type from generic type information");
388+
Class<T> itemType = null;
377389
try {
378-
beanWrapperFieldExtractor.afterPropertiesSet();
379-
}
380-
catch (Exception e) {
390+
itemType = getItemType();
391+
if (itemType != null && itemType.isRecord()) {
392+
fieldExtractor = new RecordFieldExtractor<>(itemType);
393+
}
394+
else {
395+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
396+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
397+
beanWrapperFieldExtractor.afterPropertiesSet();
398+
fieldExtractor = beanWrapperFieldExtractor;
399+
}
400+
} catch (Exception e) {
381401
throw new IllegalStateException("Unable to initialize FormatterLineAggregator", e);
382402
}
383-
this.fieldExtractor = beanWrapperFieldExtractor;
403+
this.fieldExtractor = fieldExtractor;
384404
}
385405

386406
formatterLineAggregator.setFieldExtractor(this.fieldExtractor);
387407
return formatterLineAggregator;
388408
}
389409

410+
private Class<T> getItemType() throws ClassNotFoundException {
411+
TypeVariable<? extends Class<? extends FormattedBuilder>>[] typeParameters = this.getClass()
412+
.getTypeParameters();
413+
if (typeParameters != null && typeParameters.length > 0) {
414+
TypeVariable<? extends Class<? extends FormattedBuilder>> typeParameter = typeParameters[0];
415+
String typeName = typeParameter.getName();
416+
return (Class<T>) ClassUtils.forName(typeName, this.getClass().getClassLoader());
417+
}
418+
return null;
419+
}
420+
390421
}
391422

392423
/**
@@ -453,21 +484,41 @@ public DelimitedLineAggregator<T> build() {
453484
}
454485

455486
if (this.fieldExtractor == null) {
456-
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
457-
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
487+
FieldExtractor<T> fieldExtractor = null;
488+
logger.info("No field extractor specified, trying to introspect item type from generic type information");
489+
Class<T> itemType = null;
458490
try {
459-
beanWrapperFieldExtractor.afterPropertiesSet();
460-
}
461-
catch (Exception e) {
491+
itemType = getItemType();
492+
if (itemType != null && itemType.isRecord()) {
493+
fieldExtractor = new RecordFieldExtractor<>(itemType);
494+
}
495+
else {
496+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
497+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
498+
beanWrapperFieldExtractor.afterPropertiesSet();
499+
fieldExtractor = beanWrapperFieldExtractor;
500+
}
501+
} catch (Exception e) {
462502
throw new IllegalStateException("Unable to initialize DelimitedLineAggregator", e);
463503
}
464-
this.fieldExtractor = beanWrapperFieldExtractor;
504+
this.fieldExtractor = fieldExtractor;
465505
}
466506

467507
delimitedLineAggregator.setFieldExtractor(this.fieldExtractor);
468508
return delimitedLineAggregator;
469509
}
470510

511+
private Class<T> getItemType() throws ClassNotFoundException {
512+
TypeVariable<? extends Class<? extends DelimitedBuilder>>[] typeParameters = this.getClass()
513+
.getTypeParameters();
514+
if (typeParameters != null && typeParameters.length > 0) {
515+
TypeVariable<? extends Class<? extends DelimitedBuilder>> typeParameter = typeParameters[0];
516+
String typeName = typeParameter.getName();
517+
return (Class<T>) ClassUtils.forName(typeName, this.getClass().getClassLoader());
518+
}
519+
return null;
520+
}
521+
471522
}
472523

473524
/**

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
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;
3032
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
33+
import org.springframework.batch.item.file.transform.RecordFieldExtractor;
3134
import org.springframework.core.io.FileSystemResource;
3235
import org.springframework.core.io.Resource;
3336
import org.springframework.core.io.WritableResource;
@@ -236,6 +239,69 @@ public void testFlagsWithEncoding() throws Exception {
236239
validateBuilderFlags(writer, encoding);
237240
}
238241

242+
@Test
243+
public void testSetupWithRecordItemType() throws IOException {
244+
// given
245+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
246+
record Person(int id, String name) {
247+
}
248+
249+
// when
250+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
251+
.resource(output).delimited().names("id", "name").build();
252+
253+
// then
254+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
255+
Assert.assertNotNull(lineAggregator);
256+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
257+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
258+
Assert.assertNotNull(fieldExtractor);
259+
Assert.assertTrue(fieldExtractor instanceof RecordFieldExtractor);
260+
}
261+
262+
@Test
263+
public void testSetupWithClassItemType() throws IOException {
264+
// given
265+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
266+
class Person {
267+
268+
int id;
269+
270+
String name;
271+
272+
}
273+
274+
// when
275+
FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>().name("personWriter")
276+
.resource(output).delimited().names("id", "name").build();
277+
278+
// then
279+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
280+
Assert.assertNotNull(lineAggregator);
281+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
282+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
283+
Assert.assertNotNull(fieldExtractor);
284+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
285+
}
286+
287+
@Test
288+
public void testSetupWithNoGeneticType() throws IOException {
289+
// given
290+
WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt"));
291+
292+
// when
293+
FlatFileItemWriter writer = new FlatFileItemWriterBuilder().name("personWriter")
294+
.resource(output).delimited().names("id", "name").build();
295+
296+
// then
297+
Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator");
298+
Assert.assertNotNull(lineAggregator);
299+
Assert.assertTrue(lineAggregator instanceof DelimitedLineAggregator);
300+
Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor");
301+
Assert.assertNotNull(fieldExtractor);
302+
Assert.assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor);
303+
}
304+
239305
private void validateBuilderFlags(FlatFileItemWriter<Foo> writer, String encoding) {
240306
assertFalse((Boolean) ReflectionTestUtils.getField(writer, "saveState"));
241307
assertTrue((Boolean) ReflectionTestUtils.getField(writer, "append"));

0 commit comments

Comments
 (0)