7.4.1. Data Bind

Provides a function to handle data such as CSV, TSV and fixed-length as Java Beans objects Map objects.

7.4.1.1. Function overview

7.4.1.1.1. Data can be handled as a Java Beans object

Data of data files can be handled as a Java Beans object.

When converting to a Java Beans object, type conversion is automatically performed using BeanUtil for the property type defined in the Java Beans class. When there is failure in the type conversion, an exception occurs and the Java Beans object is not generated.

Important

When reading external data such as upload files, all properties of the Java Beans class must be defined as String type since it is necessary to notify an invalid value as a business error without abnormal termination, even if the data is invalid.

See below for details on how to handle data as a Java Beans object.

7.4.1.1.2. Data can be handled as a Map object

Data of data files can be handled as a Map object.

When converting to a Map object, all values are stored in String type.

See below for details.

7.4.1.1.3. Annotations can be used to specify the data file format

The data file format can be defined using annotations or DataBindConfig instead of describing it in the configuration file.

See below for the detailed format specification methods.

7.4.1.2. Module list

<dependency>
  <groupId>com.nablarch.framework</groupId>
  <artifactId>nablarch-common-databind</artifactId>
</dependency>

<!-- Only when using file download -->
<dependency>
  <groupId>com.nablarch.framework</groupId>
  <artifactId>nablarch-fw-web-extension</artifactId>
</dependency>

7.4.1.3. How to use

7.4.1.3.1. Read data as a Java Beans object

Data files are read one by one from the beginning, and can be acquired as Java Beans objects.

The data is read by using ObjectMapper [1] that is generated by ObjectMapperFactory#create , and the data is read based on the annotation defined in the Java Beans class specified when ObjectMapper is generated.

See below for details on how to define annotations for Java Beans classes.

An implementation example to read all the data is shown below.

try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, inputStream)) {
    Person person;
    while ((person = mapper.read()) != null) {
        // Describes the process for each Java Beans object (conversion processing to Java Beans object, etc.)
    }
} catch (InvalidDataFormatException e) {
    // Describes the process when the format of the read data is invalid
}

Important

After reading all the data, release the resource using ObjectMapper#close .

However, it is possible to omit the close process in Java7 or later environments by using try-with-resources .

7.4.1.3.2. Write the contents of the Java Beans object to the data file

The contents of the Java Beans object can be written to the data file one by one.

The contents are written to the data file by using ObjectMapper [1] that is generated by ObjectMapperFactory#create , and the data is written based on the annotation defined in the Java Beans class specified when ObjectMapper is generated.

See below for details on how to define annotations for Java Beans classes.

An implementation example when writing all the Java Beans objects in the list to a data file is shown below.

try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, outputStream)) {
    for (Person person : personList) {
        mapper.write(person);
    }
}

Tip

If the value of the property is null , outputs a value indicating that there is no input. For example, empty characters are output when writing to a CSV file.

7.4.1.3.3. Read data as a Map object

Data files are read one by one from the beginning and can be acquired as Map objects.

The data is read by using ObjectMapper [1] that is generated by ObjectMapperFactory#create , and the data is read based on the configuration value of DataBindConfig specified when ObjectMapper is generated.

See below for details on how to configure DataBindConfig .

Implementation example to read all the data of a CSV file is shown below.

// Create DataBindConfig object
DataBindConfig config = CsvDataBindConfig.DEFAULT.withHeaderTitles("Age", "Name")
                                                 .withProperties("age", "name");
try (ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, inputStream, config)) {
    Person person;
    while ((person = mapper.read()) != null) {
        // Describes the process for each Java Beans object (conversion processing to Java Beans object, etc.)
    }
} catch (InvalidDataFormatException e) {
    // Describes the process when the format of the read data is invalid
}

7.4.1.3.4. Write the contents of the Map object to the data file

The contents of the map object can be written to the data file one by one.

The contents are written to the data file by using ObjectMapper [1] that is generated by ObjectMapperFactory#create , and the data is written based on the configuration value of DataBindConfig specified when ObjectMapper is generated.

See below for details on how to configure DataBindConfig .

An implementation example when writing all Map objects in the list to a CSV file is shown below.

// Create DataBindConfig object
DataBindConfig config = CsvDataBindConfig.DEFAULT.withHeaderTitles("Age", "Name")
                                                 .withProperties("age", "name");
try (ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, outputStream, config)) {
    for (Map<String, Object> person : personList) {
        mapper.write(person);
    }
}

Tip

If the value of the Map object is null , outputs a value indicating that there is no input. For example, empty characters are output when writing to a CSV file.

7.4.1.3.5. Get the logical line number of the file data

When acquiring the file data as a Java Beans object, the logical line number of the data can also be acquired by defining properties in the Java Beans class and using LineNumber .

For example, it is used to output the line number of the data with the validation error to the log when checking the input values.

An implementation example is shown below.

private Long lineNumber;

@LineNumber
public Long getLineNumber() {
    return lineNumber;
}

Tip

Note that it is not possible to get the line number of the data when acquiring as a Map object.

7.4.1.3.6. Check the data input values

Input values can be checked by Bean Validation since the data can be read as a Java Beans object.

An implementation example is shown below.

try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, inputStream)) {
    Person person;
    while ((person = mapper.read()) != null) {
        // Execute the input value check
        ValidatorUtil.validate(person);

        // Subsequent process is omitted
    }
} catch (InvalidDataFormatException e) {
    // Describe the process when data file format is invalid
}

7.4.1.3.7. Used for file download

An implementation example to download the contents of a Java Beans object as a data file in a web application is shown below.

Point
  • Since the memory may be burdened when a large amount of data is downloaded when the data is expanded in the memory, the data is output to a temporary file.
  • For writing to a data file, see Write the contents of the Java Beans object to the data file .
  • Specify a data file when creating a FileResponse .
  • To delete the file automatically at the end of the request process, specify true in the second argument of the FileResponse constructor.
  • Configure Content-Type and Content-Disposition to response.
public HttpResponse download(HttpRequest request, ExecutionContext context) {

    // Business process

    final Path path = Files.createTempFile(null, null);
    try (ObjectMapper<Person> mapper =
            ObjectMapperFactory.create(Person.class, Files.newOutputStream(path))) {
        for (Person person : persons) {
            mapper.write(BeanUtil.createAndCopy(PersonDto.class, person));
        }
    }

    // Configure file to the body
    FileResponse response = new FileResponse(path.toFile(), true);

    // Configure Content-Type header and Content-Disposition header
    response.setContentType("text/csv; charset=Shift_JIS");
    response.setContentDisposition("person.csv");

    return response;
}

7.4.1.3.8. Read the data of an upload file

An implementation example for reading a data file uploaded as a Java Beans object in the web application from the screen is shown below.

Point
List<PartInfo> partInfoList = request.getPart("uploadFile");
if (partInfoList.isEmpty()) {
    // Describe the process when upload file is not found
}

PartInfo partInfo = partInfoList.get(0);
try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, partInfo.getInputStream())) {
    Person person;
    while ((person = mapper.read()) != null) {
        // Execute the input value check
        ValidatorUtil.validate(person);

        // Subsequent process is omitted
    }
} catch (InvalidDataFormatException e) {
    // Describe the process when data file format is invalid
}

7.4.1.3.9. Specify the CSV file format

There are 2 ways of specifying the CSV file format: When binding to a Java Beans class and when binding to a Map class.

When binding to a Java Beans class

Specify the format using the following annotations.

The format of the CSV file can be selected from a format set prepared in advance. For information on format set, see Format sets that can be specified as the CSV file format .

An implementation example is shown below.

@Csv(type = Csv.CsvType.DEFAULT, properties = {"age", "name"}, headers = {"Age", "Name"})
public class Person {
    private Integer age;
    private String name;

    // Getter and setter are omitted.
}

If the CSV file format does not correspond to any of the pre-prepared format sets, the format can be specified individually using CsvFormat .

An implementation example is shown below.

// Specify CUSTOM in the type attribute.
@Csv(type = Csv.CsvType.CUSTOM, properties = {"age", "name"})
@CsvFormat(
        fieldSeparator = '\t',
        lineSeparator = "\r\n",
        quote = '\'',
        ignoreEmptyLine = false,
        requiredHeader = false,
        charset = "UTF−8",
        quoteMode = CsvDataBindConfig.QuoteMode.ALL,
        emptyToNull = true)
public class Person {
    private Integer age;
    private String name;

    // Getter and setter are omitted.
}

Tip

Since the format is specified by an annotation when binding to a Java Beans class, the format cannot be specified using DataBindConfig when ObjectMapper is generated.

When binding to a Map class

Specify the format separately using CsvDataBindConfig when ObjectMapper is generated.

The property name configured in CsvDataBindConfig#withProperties is used as the key of the Map object when specifying the format. When the header row exists in CSV, the header title can be used as a key by omitting the configuration of the property name.

An implementation example is shown below.

Point
  • Define the header title and property name according to the order of CSV items
// Define the header title and property name according to the order of CSV items
DataBindConfig config = CsvDataBindConfig.DEFAULT.withHeaderTitles("Age", "Name")
                                                 .withProperties("age", "name");
ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, outputStream, config);

7.4.1.3.10. Specify the fixed-length file format

There are 2 ways of specifying the fixed-length file format: When binding to a Java Beans class and when binding to a Map class.

When binding to a Java Beans class

Specify the format using the following annotations.

In addition, a converter that performs conversion such as padding and trimming can be specified for each field of the fixed-length file. For converters that can be specified as standard, see below the package nablarch.common.databind.fixedlength.converter .

An implementation example is shown below.

@FixedLength(length = 19, charset = "MS932", lineSeparator = "\r\n")
public class Person {

    @Field(offset = 1, length = 3)
    @Lpad
    private Integer age;

    @Field(offset = 4, length = 16)
    @Rpad
    private String name;

    // Getter and setter are omitted.
}

If the format has unused areas as shown below, they will be automatically padded with the characters configured in FixedLength#fillChar when writing to a fixed-length file. (The default is half-width space)

@FixedLength(length = 24, charset = "MS932", lineSeparator = "\r\n", fillChar = '0')
public class Person {

    @Field(offset = 1, length = 3)
    @Lpad
    private Integer age;

    @Field(offset = 9, length = 16)
    @Rpad
    private String name;

    // Getter and setter are omitted.
}
When binding to a Map class

Specify the format separately using FixedLengthDataBindConfig when ObjectMapper is generated.

FixedLengthDataBindConfig can be generated using FixedLengthDataBindConfigBuilder .

An implementation example is shown below.

final DataBindConfig config = FixedLengthDataBindConfigBuilder
        .newBuilder()
        .length(19)
        .charset(Charset.forName("MS932"))
        .lineSeparator("\r\n")
        .singleLayout()
        .field("age", 1, 3, new Lpad.Converter('0'))
        .field("name", 4, 16, new Rpad.RpadConverter(' '))
        .build();

final ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, outputStream, config);

7.4.1.3.11. Specify multiple formats for the fixed-length file

There are 2 ways of specifying the fixed-length file with multiple formats: When binding to a Java Beans class and when binding to a Map class.

When binding to a Java Beans class

Fixed-length files in multiple formats can be supported by defining a JavaBeans class for each format and creating an inherited class of MultiLayout with the Java Beans classes as properties.

An implementation example for specifying the formats is shown below.

Point
  • Define a Java Beans class for each format.
  • Define an inherited class of MultiLayout with a Java Beans class that defines the above format as a property.
  • Configure the FixedLength annotation to the inherited class of MultiLayout and configure true in the multiLayout attribute.
  • Override the MultiLayout#getRecordIdentifier method and return RecordIdentifier that identifies the format to which the target data is associated.
@FixedLength(length = 20, charset = "MS932", lineSeparator = "\r\n", multiLayout = true)
public class Person extends MultiLayout {

    @Record
    private Header header;

    @Record
    private Data data;

    @Override
    public RecordIdentifier getRecordIdentifier() {
        return new RecordIdentifier() {
            @Override
            public RecordName identifyRecordName(byte[] record) {
                return record[0] == 0x31 ? RecordType.HEADER : RecordType.DATA;
            }
        };
    }

    // Getter and setter are omitted.
}

public class Header {

    @Field(offset = 1, length = 1)
    private Long id;

    @Rpad
    @Field(offset = 2, length = 19)
    private String field;

    // Getter and setter are omitted.
}

public class Data {

    @Field(offset = 1, length = 1)
    private Long id;

    @Lpad
    @Field(offset = 2, length = 3)
    private Long age;

    @Rpad
    @Field(offset = 5, length = 16)
    private String name;

    // Getter and setter are omitted.
}

enum RecordType implements MultiLayoutConfig.RecordName {
    HEADER {
        @Override
        public String getRecordName() {
            return "header";
        }
    },
    DATA {
        @Override
        public String getRecordName() {
            return "data";
        }
    }
}

An implementation example of reading and writing fixed-length data based on a specified format is shown next.

// Implementation example for reading
try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, inputStream)) {
    final Person person = mapper.read();
    if (RecordType.HEADER == person.getRecordName()) {
        final Header header = person.getHeader();

        // Subsequent process is omitted
    }
}

// Implementation example for writing
try (ObjectMapper<Person> mapper = ObjectMapperFactory.create(Person.class, outputStream)) {
    final Person person = new Person();
    person.setHeader(new Header("1", "test"));
    mapper.write(person);
}
When binding to a Map class

When binding a fixed-length file to a Map class, the format can be specified using the same procedure as the format specification method Format specification method when binding fixed-length file to Map class.

An implementation example for specifying the formats is shown below.

Point
  • Call the multiLayout method and generate DataBindConfig for multi-layout.
  • Specify the implementation class of RecordIdentifier in the recordIdentifier method to identify the associated format of the target data.
final DataBindConfig config = FixedLengthDataBindConfigBuilder
        .newBuilder()
        .length(20)
        .charset(Charset.forName("MS932"))
        .lineSeparator("\r\n")
        .multiLayout()
        .record("header")
        .field("id", 1, 1, new DefaultConverter())
        .field("field", 2, 19, new Rpad.RpadConverter(' '))
        .record("data")
        .field("id", 1, 1, new DefaultConverter())
        .field("age", 2, 3, new Lpad.LpadConverter('0'))
        .field("name", 5, 16, new Rpad.RpadConverter(' '))
        .recordIdentifier(new RecordIdentifier() {
            @Override
            public RecordName identifyRecordName(byte[] record) {
                return record[0] == 0x31 ? RecordType.HEADER : RecordType.DATA;
            }
        })
        .build();

An implementation example of reading and writing fixed-length data based on a specified format is shown next.

// Implementation example for reading
try (ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, inputStream, config)) {
    final Map<String, ?> map = mapper.read();
    if (RecordType.HEADER == map.get("recordName")) {
        final Map<String, ?> header = map.get("header");

        // Subsequent process is omitted
    }
}

// Implementation example for writing
try (ObjectMapper<Map> mapper = ObjectMapperFactory.create(Map.class, outputStream, config)) {
    final Map<String, ?> header = new HashMap<>();
    header.put("id", "1");
    header.put("field", "test");

    final Map<String, ?> map = new HashMap<>();
    map.put("recordName", RecordType.HEADER);
    map.put("header", header);

    mapper.write(map);
}

7.4.1.3.12. Format the display format of the output data

When the data is output, display format of the data such as date and numbers can be formatted by using Formatter .

For details, see Formatter .

7.4.1.4. Expansion example

7.4.1.4.1. Add a file format that can be bound to the Java Beans class.

The following steps are required to add a file format that can be bound to the Java Beans class.

  1. Create an implementation class of ObjectMapper to bind the Java Beans class with the file of a specified format.
  2. Create a class inheriting ObjectMapperFactory , and add the process to generate the implementation class of ObjectMapper created earlier.
  3. Configure the inherited class of ObjectMapperFactory in the component configuration file. The configuration example of the component configuration file is shown below.
Point
  • The component name should be objectMapperFactory .
<component name="objectMapperFactory" class="sample.SampleObjectMapperFactory" />

7.4.1.5. Format sets that can be specified as the CSV file format

The format sets and configuration values for the CSV file provided by default are as follows.

DEFAULT RFC4180 EXCEL TSV
Column break Comma (,) Comma (,) Comma (,) Tab(\t)
Line break Carriage return and Line feed (\r\n) Carriage return and Line feed (\r\n) Carriage return and Line feed (\r\n) Carriage return and Line feed (\r\n)
Field enclosing character Double quotation (”) Double quotation (”) Double quotation (”) Double quotation (”)
Ignore empty lines true false false false
With header line true false false false
Character code UTF-8 UTF-8 UTF-8 UTF-8
Quote mode NORMAL NORMAL NORMAL NORMAL
Quote mode

Quote mode indicates which field is enclosed by field enclosing characters when writing to a CSV file. The following modes can be selected from for the quote mode.

Quote mode name Field to be enclosed with field enclosing characters
NORMAL Fields that contain either field enclosing characters, column delimiters or line breaks
ALL All fields

Tip

When reading a CSV file, the existence of field enclosing characters is automatically determined without using the quote mode.

[1](1, 2, 3, 4) Since the reading and writing of ObjectMapper is not thread safe, operation is not guaranteed when it is called from multiple threads simultaneously. For this reason, synchronous operation is performed by the caller when the instance ObjectMapper is shared by multiple threads.