Sunday 27 October 2013

How to pass delimiter dynamically to spring DelimitedLineTokenizer


In spring batch, if the user wants to decide a Delimiter at run time based on the certain criteria. Built-in spring batch Tokenizer classes won’t support this feature. If we use our own customized class instead of using spring provided Tokenizer class, spring batch throws an error.

Actual Configuration:

<bean id="defaultTokenizer" class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
            <property name="delimiter" value="," />
</bean>

<bean id="fieldSetMapper" class="com.mapper.CustomDataUploadFieldSetMapper" />

<bean id="defaultLineMapper" class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer" ref="defaultTokenizer"></property>
            <property name="fieldSetMapper" ref="fieldSetMapper"></property>
</bean>

<bean id="customDataItemReader" class="com.reader.CustomDataLoadReader" scope="prototype">
            <property name="lineMapper" ref="defaultLineMapper"></property>
</bean>


Here, if we provide overridden Tokenizer instead of DelimitedLineTokenizer, spring container will throw an error while server starts up.

We can overcome this limitation by using anonymous inner bean configuration as follows.

<bean id="customDataItemReader" class="com.reader.customerDataItemReader" scope="prototype">
      <property name="lineMapper">
            <bean class="com.mapper.CustomLineMapper">
                    <property name="lineTokenizer">
                        <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
                                 <property name="delimiter" value="," />
                        </bean>
                    </property>
                    <property name="fieldSetMapper">
                        <bean class="com.mapper.SegmentFieldSetMapper" />
                    </property>
            </bean>
       </property>
</bean>

Write a CustomLineMapper by extending DefaultLineMapper and override 
mapLine(String line, int lineNumber) throws Exception method according to requirements.
Example: Now we need use pipe(|) symbol as delimiter if existed in the line, else need to use comma(,) as default delimiter.


public class CustomLineMapper<T> extends DefaultLineMapper<T> {

    private LineTokenizer lineTokenizer;
    private FieldSetMapper<CustomerInformationVO> fieldSetMapper;

    public <T> mapLine(String line, int lineNumber) throws Exception {
       try {
           setLineTokenizer(new DelimitedLineTokenizer(StringUtils.contains(
                  line, '|') ? '|' : ','));
           return this.fieldSetMapper.mapFieldSet(this.lineTokenizer
                  .tokenize(line));
       } catch (Exception ex) {
           throw new FlatFileParseException("Parsing error at line: "
                  + lineNumber + ", input=[" + line + "]", ex, line,
                  lineNumber);
       }
    }

    public LineTokenizer getLineTokenizer() {
       return lineTokenizer;
    }

    public void setLineTokenizer(LineTokenizer lineTokenizer) {
       this.lineTokenizer = lineTokenizer;
    }

    public void afterPropertiesSet() {
       Assert.notNull(this.lineTokenizer, "The LineTokenizer must be set");
       Assert.notNull(this.fieldSetMapper, "The FieldSetMapper must be set");
    }

    public FieldSetMapper<CustomerInformationVO> getFieldSetMapper() {
       return fieldSetMapper;
    }

    public void setFieldSetMapper(
           FieldSetMapper<CustomerInformationVO> fieldSetMapper) {
       this.fieldSetMapper = fieldSetMapper;
    }

}

No comments:

Post a Comment