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.
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