0
votes

I'm trying to write a spring batch job which reads data from database and writes to FlatFile. I need to write the total records, total lines in the header of the FlatFile. Using the headerCallback property in Writer for that purpose.

Problem statement:

I'm setting the values in ExecutionContext in Processor and trying to retrieve the values in headerCallback of the Writer. But getting the values as null in headerCallback.

Tried using ExecutionContextPromotionListener and also by injecting the values directly into headerCallback using SpEL. Details are as below:

Job configuration:

<batch:job id="fooBatchJob">
<batch:step id="step1">
        <batch:tasklet>
            <batch:chunk reader="fooJdbcItemReader" writer="fooFileItemWriter" processor="fooProcessor"
                commit-interval="100">
            </batch:chunk>
        </batch:tasklet>
        <batch:listeners>
            <batch:listener ref="execContextPromotionListener"/>
        </batch:listeners>
    </batch:step>
</batch:job>

<!-- Listeners -->

<bean id="execContextPromotionListener" class="org.springframework.batch.core.listener.ExecutionContextPromotionListener">
    <property name="strict" value="TRUE"/>
    <property name="keys">
        <list>
            <value>TOTAL_RECORDS</value>
            <value>TOTAL_LINES</value>
        </list>
    </property>
</bean>

The values of TOTAL_RECORDS and TOTAL_LINES are populated with the values from the query in ItemReader.

I'm setting the values in Processor as below:

public class FooProcessor implements ItemProcessor<MyObj,MyObj>, StepExecutionListener {

    private StepExecution stepExecution;

    public MyObj process(MyObj item) throws Exception {

        if(item != null && item.getRecordType().equals("HEADER")) {

            this.stepExecution.getExecutionContext().put("TOTAL_RECORDS", item.getMetaData().getTotalRecordsCount());
            this.stepExecution.getExecutionContext().put("TOTAL_LINES", item.getMetaData().getTotalLines());
            // below lines printed for debugging:
            System.out.println("TOTAL_RECORDS:"+this.stepExecution.getExecutionContext().getInt("TOTAL_RECORDS"));
            System.out.println("TOTAL_LINES:"+this.stepExecution.getExecutionContext().getInt("TOTAL_LINES"));
            return null;
        }
        return item;
    }

    @Override
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;

    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}

Now, here is the Writer configuration:

<bean id="fooFileItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
        <property name="resource" value="file:output/result-file.asc"/>
        <property name="shouldDeleteIfExists" value="true" />
        <property name="headerCallback" ref="myHeaderFooter"/>
        <property name="footerCallback" ref="myHeaderFooter"/>
        <property name="lineAggregator">
            <bean
                class="com.domain.batch.transform.FooLineAggregator">
            </bean>
        </property>
    </bean> 

<!-- Header and Footer Callback -->
    <bean id="myHeaderFooter" class="com.domain.batch.transform.MyHeaderFooter" scope="step">
        <property name="stepExecution" value="#{stepExecution}" />
        <property name="totalRecords" value="#{jobExecutionContext['TOTAL_RECORDS']}"/>
    </bean> 

Below is the listing of MyHeaderFooter.java:

public class MyHeaderFooter implements FlatFileHeaderCallback, FlatFileFooterCallback {

    private StepExecution stepExecution;
    private Integer totalRecords;

    private static final String ITEM_END_INDICATOR = "RE";
    private static final String SPACE_DELIMITER = " ";
    private static final String LINE_SEPARATOR = System.lineSeparator();

    @Override
    public void writeHeader(Writer writer) throws IOException {
        // printing out null
        System.out.println("Total records from jobExecContext : "+totalRecords); 
        ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
        // printing out null
        System.out.println("Total Records :"+ executionContext.get("TOTAL_RECORDS"));
       // printing out null
        System.out.println("Total Lines:"+ executionContext.get("TOTAL_LINES"));
        // Below lines commented out for brevity
        //String header = buildHeader(...);
        //writer.write(header);
    }

    @Override
    public void writeFooter(Writer writer) throws IOException {
        String footer = "footer data....";
        writer.write(footer);
    }

public void setStepExecution(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }

    public void setTotalRecords(Integer totalRecords) {
        this.totalRecords = totalRecords;
    }
}

Not sure what i'm doing wrong with the configuration. Any clue to find out the issue would be helpful.

1
Did you find any way out to achieve what you intended to? I have a similar requirement.truekiller
@truekiller As @Mahmoud Ben Hassine pointed out, you can execute queries inside writeHeader method to get the total records count and then write it. Instead trying to calculate the total records count in processing phase will be a problem since those value won't be available to header. Reason being header will be initiated even before reading, processing..Vignesh

1 Answers

3
votes

The writeHeader method is called while opening the fooFileItemWriter. Reading, processing and writing have not started yet at that point in time. So the information you are requesting is not available yet.

Note that it would work if you want to write these information in a footer line.

There is a similar question to this one, I'm adding it here for reference: Passing variables to process.