Skip to content

Commit e83ed1d

Browse files
fmbenhassinemminella
authored andcommitted
Add convenience methods in FlatFileItemWriterBuilder to generate delimited files
Resolves BATCH-2696
1 parent 70d0e3c commit e83ed1d

File tree

2 files changed

+416
-4
lines changed

2 files changed

+416
-4
lines changed

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

Lines changed: 259 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 the original author or authors.
2+
* Copyright 2016-2018 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.
@@ -15,18 +15,29 @@
1515
*/
1616
package org.springframework.batch.item.file.builder;
1717

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.Locale;
22+
1823
import org.springframework.batch.item.file.FlatFileFooterCallback;
1924
import org.springframework.batch.item.file.FlatFileHeaderCallback;
2025
import org.springframework.batch.item.file.FlatFileItemWriter;
26+
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
27+
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
28+
import org.springframework.batch.item.file.transform.FieldExtractor;
29+
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
2130
import org.springframework.batch.item.file.transform.LineAggregator;
2231
import org.springframework.core.io.Resource;
2332
import org.springframework.util.Assert;
33+
import org.springframework.util.StringUtils;
2434

2535
/**
2636
* A builder implementation for the {@link FlatFileItemWriter}
2737
*
2838
* @author Michael Minella
2939
* @author Glenn Renfro
40+
* @author Mahmoud Ben Hassine
3041
* @since 4.0
3142
* @see FlatFileItemWriter
3243
*/
@@ -58,6 +69,10 @@ public class FlatFileItemWriterBuilder<T> {
5869

5970
private String name;
6071

72+
private DelimitedBuilder<T> delimitedBuilder;
73+
74+
private FormattedBuilder<T> formattedBuilder;
75+
6176
/**
6277
* Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport}
6378
* should be persisted within the {@link org.springframework.batch.item.ExecutionContext}
@@ -155,7 +170,7 @@ public FlatFileItemWriterBuilder<T> encoding(String encoding) {
155170
}
156171

157172
/**
158-
* If set to true, once the step is complete, if the resource previously provdied is
173+
* If set to true, once the step is complete, if the resource previously provided is
159174
* empty, it will be deleted.
160175
*
161176
* @param shouldDelete defaults to false
@@ -234,14 +249,245 @@ public FlatFileItemWriterBuilder<T> transactional(boolean transactional) {
234249
return this;
235250
}
236251

252+
/**
253+
* Returns an instance of a {@link DelimitedBuilder} for building a
254+
* {@link DelimitedLineAggregator}. The {@link DelimitedLineAggregator} configured by
255+
* this builder will only be used if one is not explicitly configured via
256+
* {@link FlatFileItemWriterBuilder#lineAggregator}
257+
*
258+
* @return a {@link DelimitedBuilder}
259+
*
260+
*/
261+
public DelimitedBuilder<T> delimited() {
262+
this.delimitedBuilder = new DelimitedBuilder<>(this);
263+
return this.delimitedBuilder;
264+
}
265+
266+
/**
267+
* Returns an instance of a {@link FormattedBuilder} for building a
268+
* {@link FormatterLineAggregator}. The {@link FormatterLineAggregator} configured by
269+
* this builder will only be used if one is not explicitly configured via
270+
* {@link FlatFileItemWriterBuilder#lineAggregator}
271+
*
272+
* @return a {@link FormattedBuilder}
273+
*
274+
*/
275+
public FormattedBuilder<T> formatted() {
276+
this.formattedBuilder = new FormattedBuilder<>(this);
277+
return this.formattedBuilder;
278+
}
279+
280+
/**
281+
* A builder for constructing a {@link FormatterLineAggregator}.
282+
*
283+
* @param <T> the type of the parent {@link FlatFileItemWriterBuilder}
284+
*/
285+
public static class FormattedBuilder<T> {
286+
287+
private FlatFileItemWriterBuilder<T> parent;
288+
289+
private String format;
290+
291+
private Locale locale = Locale.getDefault();
292+
293+
private int maximumLength = 0;
294+
295+
private int minimumLength = 0;
296+
297+
private FieldExtractor<T> fieldExtractor;
298+
299+
private List<String> names = new ArrayList<>();
300+
301+
protected FormattedBuilder(FlatFileItemWriterBuilder<T> parent) {
302+
this.parent = parent;
303+
}
304+
305+
/**
306+
* Set the format string used to aggregate items
307+
* @param format used to aggregate items
308+
* @return The instance of the builder for chaining.
309+
*/
310+
public FormattedBuilder<T> format(String format) {
311+
this.format = format;
312+
return this;
313+
}
314+
315+
/**
316+
* Set the locale.
317+
* @param locale to use
318+
* @return The instance of the builder for chaining.
319+
*/
320+
public FormattedBuilder<T> locale(Locale locale) {
321+
this.locale = locale;
322+
return this;
323+
}
324+
325+
/**
326+
* Set the minimum length of the formatted string. If this is not set
327+
* the default is to allow any length.
328+
* @param minimumLength of the formatted string
329+
* @return The instance of the builder for chaining.
330+
*/
331+
public FormattedBuilder<T> minimumLength(int minimumLength) {
332+
this.minimumLength = minimumLength;
333+
return this;
334+
}
335+
336+
/**
337+
* Set the maximum length of the formatted string. If this is not set
338+
* the default is to allow any length.
339+
* @param maximumLength of the formatted string
340+
* @return The instance of the builder for chaining.
341+
*/
342+
public FormattedBuilder<T> maximumLength(int maximumLength) {
343+
this.maximumLength = maximumLength;
344+
return this;
345+
}
346+
347+
/**
348+
* Set the {@link FieldExtractor} to use to extract fields from each item.
349+
* @param fieldExtractor to use to extract fields from each item
350+
* @return The current instance of the builder
351+
*/
352+
public FlatFileItemWriterBuilder<T> fieldExtractor(FieldExtractor<T> fieldExtractor) {
353+
this.fieldExtractor = fieldExtractor;
354+
return this.parent;
355+
}
356+
357+
/**
358+
* Names of each of the fields within the fields that are returned in the order
359+
* they occur within the formatted file. These names will be used to create
360+
* a {@link BeanWrapperFieldExtractor} only if no explicit field extractor
361+
* is set via {@link FormattedBuilder#fieldExtractor(FieldExtractor)}.
362+
*
363+
* @param names names of each field
364+
* @return The parent {@link FlatFileItemWriterBuilder}
365+
* @see BeanWrapperFieldExtractor#setNames(String[])
366+
*/
367+
public FlatFileItemWriterBuilder<T> names(String[] names) {
368+
this.names.addAll(Arrays.asList(names));
369+
return this.parent;
370+
}
371+
372+
public FormatterLineAggregator<T> build() {
373+
Assert.notNull(this.format, "A format is required");
374+
Assert.isTrue((this.names != null && !this.names.isEmpty()) || this.fieldExtractor != null,
375+
"A list of field names or a field extractor is required");
376+
377+
FormatterLineAggregator<T> formatterLineAggregator = new FormatterLineAggregator<>();
378+
formatterLineAggregator.setFormat(this.format);
379+
formatterLineAggregator.setLocale(this.locale);
380+
formatterLineAggregator.setMinimumLength(this.minimumLength);
381+
formatterLineAggregator.setMaximumLength(this.maximumLength);
382+
383+
if (this.fieldExtractor == null) {
384+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
385+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
386+
try {
387+
beanWrapperFieldExtractor.afterPropertiesSet();
388+
}
389+
catch (Exception e) {
390+
throw new IllegalStateException("Unable to initialize FormatterLineAggregator", e);
391+
}
392+
this.fieldExtractor = beanWrapperFieldExtractor;
393+
}
394+
395+
formatterLineAggregator.setFieldExtractor(this.fieldExtractor);
396+
return formatterLineAggregator;
397+
}
398+
}
399+
400+
/**
401+
* A builder for constructing a {@link DelimitedLineAggregator}
402+
*
403+
* @param <T> the type of the parent {@link FlatFileItemWriterBuilder}
404+
*/
405+
public static class DelimitedBuilder<T> {
406+
407+
private FlatFileItemWriterBuilder<T> parent;
408+
409+
private List<String> names = new ArrayList<>();
410+
411+
private String delimiter = ",";
412+
413+
private FieldExtractor<T> fieldExtractor;
414+
415+
protected DelimitedBuilder(FlatFileItemWriterBuilder<T> parent) {
416+
this.parent = parent;
417+
}
418+
419+
/**
420+
* Define the delimiter for the file.
421+
*
422+
* @param delimiter String used as a delimiter between fields.
423+
* @return The instance of the builder for chaining.
424+
* @see DelimitedLineAggregator#setDelimiter(String)
425+
*/
426+
public DelimitedBuilder<T> delimiter(String delimiter) {
427+
this.delimiter = delimiter;
428+
return this;
429+
}
430+
431+
/**
432+
* Names of each of the fields within the fields that are returned in the order
433+
* they occur within the delimited file. These names will be used to create
434+
* a {@link BeanWrapperFieldExtractor} only if no explicit field extractor
435+
* is set via {@link DelimitedBuilder#fieldExtractor(FieldExtractor)}.
436+
*
437+
* @param names names of each field
438+
* @return The parent {@link FlatFileItemWriterBuilder}
439+
* @see BeanWrapperFieldExtractor#setNames(String[])
440+
*/
441+
public FlatFileItemWriterBuilder<T> names(String[] names) {
442+
this.names.addAll(Arrays.asList(names));
443+
return this.parent;
444+
}
445+
446+
/**
447+
* Set the {@link FieldExtractor} to use to extract fields from each item.
448+
* @param fieldExtractor to use to extract fields from each item
449+
* @return The parent {@link FlatFileItemWriterBuilder}
450+
*/
451+
public FlatFileItemWriterBuilder<T> fieldExtractor(FieldExtractor<T> fieldExtractor) {
452+
this.fieldExtractor = fieldExtractor;
453+
return this.parent;
454+
}
455+
456+
public DelimitedLineAggregator<T> build() {
457+
Assert.isTrue((this.names != null && !this.names.isEmpty()) || this.fieldExtractor != null,
458+
"A list of field names or a field extractor is required");
459+
460+
DelimitedLineAggregator<T> delimitedLineAggregator = new DelimitedLineAggregator<>();
461+
if (StringUtils.hasLength(this.delimiter)) {
462+
delimitedLineAggregator.setDelimiter(this.delimiter);
463+
}
464+
465+
if (this.fieldExtractor == null) {
466+
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
467+
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
468+
try {
469+
beanWrapperFieldExtractor.afterPropertiesSet();
470+
}
471+
catch (Exception e) {
472+
throw new IllegalStateException("Unable to initialize DelimitedLineAggregator", e);
473+
}
474+
this.fieldExtractor = beanWrapperFieldExtractor;
475+
}
476+
477+
delimitedLineAggregator.setFieldExtractor(this.fieldExtractor);
478+
return delimitedLineAggregator;
479+
}
480+
}
481+
237482
/**
238483
* Validates and builds a {@link FlatFileItemWriter}.
239484
*
240485
* @return a {@link FlatFileItemWriter}
241486
*/
242487
public FlatFileItemWriter<T> build() {
243488

244-
Assert.notNull(this.lineAggregator, "A LineAggregator is required");
489+
Assert.isTrue(this.lineAggregator != null || this.delimitedBuilder != null || this.formattedBuilder != null,
490+
"A LineAggregator or a DelimitedBuilder or a FormattedBuilder is required");
245491
Assert.notNull(this.resource, "A Resource is required");
246492

247493
if(this.saveState) {
@@ -256,6 +502,16 @@ public FlatFileItemWriter<T> build() {
256502
writer.setFooterCallback(this.footerCallback);
257503
writer.setForceSync(this.forceSync);
258504
writer.setHeaderCallback(this.headerCallback);
505+
if (this.lineAggregator == null) {
506+
Assert.state(this.delimitedBuilder == null || this.formattedBuilder == null,
507+
"Either a DelimitedLineAggregator or a FormatterLineAggregator should be provided, but not both");
508+
if (this.delimitedBuilder != null) {
509+
this.lineAggregator = this.delimitedBuilder.build();
510+
}
511+
else {
512+
this.lineAggregator = this.formattedBuilder.build();
513+
}
514+
}
259515
writer.setLineAggregator(this.lineAggregator);
260516
writer.setLineSeparator(this.lineSeparator);
261517
writer.setResource(this.resource);

0 commit comments

Comments
 (0)