Search code examples
javaspringparsingspring-batchbatch-processing

Optional Multi-line Entry for Spring Batch LineMapper


I have a file that normally has the following format:

property_A_1@property_B_1@property_C_1@property_D_1
property_A_2@property_B_2@property_C_2@property_D_2
property_A_3@property_B_3@property_C_3@property_D_3

This should be mapped to a custom class with four properties, @ as a delimiter. However, there are occasions where the property_B might contain a new line as part of its characters, e.g.:

property_A_1@property_B_1@property_C_1@property_D_1
property_A_2@property_B_2_i
property_B_2_ii
property_B_2_iii
property_B_2_iiii@property_C_2@property_D_2
property_A_3@property_B_3@property_C_3@property_D_3

The number of these lines can vary and are not fixed. In this case, I still need to map the second entry as before, except that property_b_2's should contain the data between the first @ and the second @.

I can live with no new line if I can replace them with spaces, so as if the actual entry hypothetically looks like:

property_A_2@property_B_2_i property_B_2_ii property_B_2_iii@property_B_2_iiii@property_C_2@property_D_2

Is there a way to accomplish this with ItemReader and LineMapper?


Solution

  • I solved this by overriding DefaultRecordSeparatorPolicy#isEndOfRecord() of my ItemReader. I also needed to get read of unterminated quotations check as the content might have an uneven pair of quotation characters:

    itemReader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy() {
    
        private static final String CONTINUATION = "\\";
    
        private String continuation = CONTINUATION;
        private String final String delimiter ="@";
    
        @Override
        public boolean isEndOfRecord(String line) {
            return StringUtils.countOccurrencesOf(line, delimiter) >=3 &&
                   !isQuoteUnterminated(line) && 
                   !isContinued(line);
        }
    
        private boolean isQuoteUnterminated(String line) {
            return false;
        }
    
        private boolean isContinued(String line) {
            if (line == null) {
                return false;
            }
            return line.trim().endsWith(continuation);
        }
    });