Add convenience methods in FlatFileItemWriterBuilder to generate delimited files

Resolves BATCH-2696
This commit is contained in:
Mahmoud Ben Hassine
2018-08-21 23:59:03 +02:00
committed by Michael Minella
parent 70d0e3c510
commit e83ed1db11
2 changed files with 416 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2018 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.
@@ -15,18 +15,29 @@
*/
package org.springframework.batch.item.file.builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.springframework.batch.item.file.FlatFileFooterCallback;
import org.springframework.batch.item.file.FlatFileHeaderCallback;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.file.transform.FieldExtractor;
import org.springframework.batch.item.file.transform.FormatterLineAggregator;
import org.springframework.batch.item.file.transform.LineAggregator;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A builder implementation for the {@link FlatFileItemWriter}
*
* @author Michael Minella
* @author Glenn Renfro
* @author Mahmoud Ben Hassine
* @since 4.0
* @see FlatFileItemWriter
*/
@@ -58,6 +69,10 @@ public class FlatFileItemWriterBuilder<T> {
private String name;
private DelimitedBuilder<T> delimitedBuilder;
private FormattedBuilder<T> formattedBuilder;
/**
* Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport}
* should be persisted within the {@link org.springframework.batch.item.ExecutionContext}
@@ -155,7 +170,7 @@ public class FlatFileItemWriterBuilder<T> {
}
/**
* If set to true, once the step is complete, if the resource previously provdied is
* If set to true, once the step is complete, if the resource previously provided is
* empty, it will be deleted.
*
* @param shouldDelete defaults to false
@@ -234,6 +249,236 @@ public class FlatFileItemWriterBuilder<T> {
return this;
}
/**
* Returns an instance of a {@link DelimitedBuilder} for building a
* {@link DelimitedLineAggregator}. The {@link DelimitedLineAggregator} configured by
* this builder will only be used if one is not explicitly configured via
* {@link FlatFileItemWriterBuilder#lineAggregator}
*
* @return a {@link DelimitedBuilder}
*
*/
public DelimitedBuilder<T> delimited() {
this.delimitedBuilder = new DelimitedBuilder<>(this);
return this.delimitedBuilder;
}
/**
* Returns an instance of a {@link FormattedBuilder} for building a
* {@link FormatterLineAggregator}. The {@link FormatterLineAggregator} configured by
* this builder will only be used if one is not explicitly configured via
* {@link FlatFileItemWriterBuilder#lineAggregator}
*
* @return a {@link FormattedBuilder}
*
*/
public FormattedBuilder<T> formatted() {
this.formattedBuilder = new FormattedBuilder<>(this);
return this.formattedBuilder;
}
/**
* A builder for constructing a {@link FormatterLineAggregator}.
*
* @param <T> the type of the parent {@link FlatFileItemWriterBuilder}
*/
public static class FormattedBuilder<T> {
private FlatFileItemWriterBuilder<T> parent;
private String format;
private Locale locale = Locale.getDefault();
private int maximumLength = 0;
private int minimumLength = 0;
private FieldExtractor<T> fieldExtractor;
private List<String> names = new ArrayList<>();
protected FormattedBuilder(FlatFileItemWriterBuilder<T> parent) {
this.parent = parent;
}
/**
* Set the format string used to aggregate items
* @param format used to aggregate items
* @return The instance of the builder for chaining.
*/
public FormattedBuilder<T> format(String format) {
this.format = format;
return this;
}
/**
* Set the locale.
* @param locale to use
* @return The instance of the builder for chaining.
*/
public FormattedBuilder<T> locale(Locale locale) {
this.locale = locale;
return this;
}
/**
* Set the minimum length of the formatted string. If this is not set
* the default is to allow any length.
* @param minimumLength of the formatted string
* @return The instance of the builder for chaining.
*/
public FormattedBuilder<T> minimumLength(int minimumLength) {
this.minimumLength = minimumLength;
return this;
}
/**
* Set the maximum length of the formatted string. If this is not set
* the default is to allow any length.
* @param maximumLength of the formatted string
* @return The instance of the builder for chaining.
*/
public FormattedBuilder<T> maximumLength(int maximumLength) {
this.maximumLength = maximumLength;
return this;
}
/**
* Set the {@link FieldExtractor} to use to extract fields from each item.
* @param fieldExtractor to use to extract fields from each item
* @return The current instance of the builder
*/
public FlatFileItemWriterBuilder<T> fieldExtractor(FieldExtractor<T> fieldExtractor) {
this.fieldExtractor = fieldExtractor;
return this.parent;
}
/**
* Names of each of the fields within the fields that are returned in the order
* they occur within the formatted file. These names will be used to create
* a {@link BeanWrapperFieldExtractor} only if no explicit field extractor
* is set via {@link FormattedBuilder#fieldExtractor(FieldExtractor)}.
*
* @param names names of each field
* @return The parent {@link FlatFileItemWriterBuilder}
* @see BeanWrapperFieldExtractor#setNames(String[])
*/
public FlatFileItemWriterBuilder<T> names(String[] names) {
this.names.addAll(Arrays.asList(names));
return this.parent;
}
public FormatterLineAggregator<T> build() {
Assert.notNull(this.format, "A format is required");
Assert.isTrue((this.names != null && !this.names.isEmpty()) || this.fieldExtractor != null,
"A list of field names or a field extractor is required");
FormatterLineAggregator<T> formatterLineAggregator = new FormatterLineAggregator<>();
formatterLineAggregator.setFormat(this.format);
formatterLineAggregator.setLocale(this.locale);
formatterLineAggregator.setMinimumLength(this.minimumLength);
formatterLineAggregator.setMaximumLength(this.maximumLength);
if (this.fieldExtractor == null) {
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
try {
beanWrapperFieldExtractor.afterPropertiesSet();
}
catch (Exception e) {
throw new IllegalStateException("Unable to initialize FormatterLineAggregator", e);
}
this.fieldExtractor = beanWrapperFieldExtractor;
}
formatterLineAggregator.setFieldExtractor(this.fieldExtractor);
return formatterLineAggregator;
}
}
/**
* A builder for constructing a {@link DelimitedLineAggregator}
*
* @param <T> the type of the parent {@link FlatFileItemWriterBuilder}
*/
public static class DelimitedBuilder<T> {
private FlatFileItemWriterBuilder<T> parent;
private List<String> names = new ArrayList<>();
private String delimiter = ",";
private FieldExtractor<T> fieldExtractor;
protected DelimitedBuilder(FlatFileItemWriterBuilder<T> parent) {
this.parent = parent;
}
/**
* Define the delimiter for the file.
*
* @param delimiter String used as a delimiter between fields.
* @return The instance of the builder for chaining.
* @see DelimitedLineAggregator#setDelimiter(String)
*/
public DelimitedBuilder<T> delimiter(String delimiter) {
this.delimiter = delimiter;
return this;
}
/**
* Names of each of the fields within the fields that are returned in the order
* they occur within the delimited file. These names will be used to create
* a {@link BeanWrapperFieldExtractor} only if no explicit field extractor
* is set via {@link DelimitedBuilder#fieldExtractor(FieldExtractor)}.
*
* @param names names of each field
* @return The parent {@link FlatFileItemWriterBuilder}
* @see BeanWrapperFieldExtractor#setNames(String[])
*/
public FlatFileItemWriterBuilder<T> names(String[] names) {
this.names.addAll(Arrays.asList(names));
return this.parent;
}
/**
* Set the {@link FieldExtractor} to use to extract fields from each item.
* @param fieldExtractor to use to extract fields from each item
* @return The parent {@link FlatFileItemWriterBuilder}
*/
public FlatFileItemWriterBuilder<T> fieldExtractor(FieldExtractor<T> fieldExtractor) {
this.fieldExtractor = fieldExtractor;
return this.parent;
}
public DelimitedLineAggregator<T> build() {
Assert.isTrue((this.names != null && !this.names.isEmpty()) || this.fieldExtractor != null,
"A list of field names or a field extractor is required");
DelimitedLineAggregator<T> delimitedLineAggregator = new DelimitedLineAggregator<>();
if (StringUtils.hasLength(this.delimiter)) {
delimitedLineAggregator.setDelimiter(this.delimiter);
}
if (this.fieldExtractor == null) {
BeanWrapperFieldExtractor<T> beanWrapperFieldExtractor = new BeanWrapperFieldExtractor<>();
beanWrapperFieldExtractor.setNames(this.names.toArray(new String[this.names.size()]));
try {
beanWrapperFieldExtractor.afterPropertiesSet();
}
catch (Exception e) {
throw new IllegalStateException("Unable to initialize DelimitedLineAggregator", e);
}
this.fieldExtractor = beanWrapperFieldExtractor;
}
delimitedLineAggregator.setFieldExtractor(this.fieldExtractor);
return delimitedLineAggregator;
}
}
/**
* Validates and builds a {@link FlatFileItemWriter}.
*
@@ -241,7 +486,8 @@ public class FlatFileItemWriterBuilder<T> {
*/
public FlatFileItemWriter<T> build() {
Assert.notNull(this.lineAggregator, "A LineAggregator is required");
Assert.isTrue(this.lineAggregator != null || this.delimitedBuilder != null || this.formattedBuilder != null,
"A LineAggregator or a DelimitedBuilder or a FormattedBuilder is required");
Assert.notNull(this.resource, "A Resource is required");
if(this.saveState) {
@@ -256,6 +502,16 @@ public class FlatFileItemWriterBuilder<T> {
writer.setFooterCallback(this.footerCallback);
writer.setForceSync(this.forceSync);
writer.setHeaderCallback(this.headerCallback);
if (this.lineAggregator == null) {
Assert.state(this.delimitedBuilder == null || this.formattedBuilder == null,
"Either a DelimitedLineAggregator or a FormatterLineAggregator should be provided, but not both");
if (this.delimitedBuilder != null) {
this.lineAggregator = this.delimitedBuilder.build();
}
else {
this.lineAggregator = this.formattedBuilder.build();
}
}
writer.setLineAggregator(this.lineAggregator);
writer.setLineSeparator(this.lineSeparator);
writer.setResource(this.resource);