diff --git a/docs/src/site/docbook/reference/common-patterns.xml b/docs/src/site/docbook/reference/common-patterns.xml
index 36fd7304f..f2e61800f 100644
--- a/docs/src/site/docbook/reference/common-patterns.xml
+++ b/docs/src/site/docbook/reference/common-patterns.xml
@@ -124,17 +124,13 @@
strategy which signals a complete batch when the item to be processed is
null. A more sophisticated completion policy could be implemented and
injected into the Step through the
- RepeatOperationsStepFactoryBean:
+ SimpleStepFactoryBean:
<bean id="simpleStep"
- class="org.springframework.batch.core.step.item.RepeatOperationsStepFactoryBean" >
+ class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" >
...
- <property name="chunkOperations">
- <bean class="org.springframework.batch.repeat.support.RepeatTemplate">
- <property name="completionPolicy">
- <bean class="org.example...SpecialCompletionPolicy"/>
- </property>
- </bean>
+ <property name="chunkCompletionPolicy">
+ <bean class="org.example...SpecialCompletionPolicy"/>
</property>
</bean>
@@ -156,11 +152,9 @@
}
public void afterRead(Object item) {
-
if (isPoisonPill(item)) {
stepExecution.setTerminateOnly(true);
}
-
}
}
@@ -175,59 +169,125 @@
Adding a Footer Record
- A very common requirement is to aggregate information during the
- output process and to append a record at the end of a file summarizing the
- data, or providing a checksum. This can also be achieved with a callback
- in the step, normally as part of a custom
- ItemWriter. In this case, since a job is
- accumulating state that should not be lost if the job aborts, the
- ItemStream interface should be implemented:
+ Often when writing to flat files, a "footer" record must be appended
+ to the end of the file, after all processing has be completed. This can
+ also be achieved using the FlatFileFooterCallback
+ interface provided by Spring Batch. The
+ FlatFileFooterCallback (and its counterpart, the
+ FlatFileHeaderCallback) are optional properties of
+ the FlatFileItemWriter:
- public class CustomItemWriter implements ItemWriter<Trade>,
- ItemStream, StepListener
-{
+ <bean id="itemWriter"
+ class="org.springframework.batch.item.file.FlatFileItemWriter">
+ <property name="resource" ref="outputResource" />
+ <property name="lineAggregator" ref="lineAggregator"/>
+ <property name="headerCallback" ref="headerCallback" />
+ <property name="footerCallback" ref="footerCallback" />
+ </bean>
+
- private static final String TOTAL_AMOUNT_KEY = "total.amount";
+ The footer callback interface is very simple. It has just one method
+ that is called when the footer must be written:
- private ItemWriter delegate;
+ public interface FlatFileFooterCallback {
+ void writeFooter(Writer writer) throws IOException;
+
+ }
+
+
+ Writing a Summary Footer
+
+ A very common requirement involving footer records is to aggregate
+ information during the output process and to append this information to
+ the end of the file. This footer serves as a summarization of the file
+ or provides a checksum.
+
+ For example, if a batch job is writing Trade records to a flat
+ file, and there is a requirement that the total amount from all the
+ Trades is placed in a footer, then the following ItemWriter
+ implementation can be used:
+
+ public class TradeItemWriter implements ItemWriter<Trade>,
+ FlatFileFooterCallback {
+
+ private ItemWriter<Trade> delegate;
private double totalAmount = 0.0;
- public void setDelegate(ItemWriter delegate) { ... }
-
- public ExitStatus afterStep(StepExecution stepExecution) {
- // Add the footer record here...
- delegate.write("Total Amount Processed: " + totalAmount);
- }
-
- public void open(ExecutionContext executionContext) {
- if (executionContext.containsKey(TOTAL_AMOUNT_KEY) {
- totalAmount = executionContext.getDouble(TOTAL_AMOUNT_KEY);
+ public void write(List<? extends Trade> items) {
+ delegate.write(items);
+ for(Trade trade : items) {
+ totalAmount += trade.getAmount();
}
}
- public void update(ExecutionContext executionContext) {
- executionContext.setDouble(TOTAL_AMOUNT_KEY, totalAmount);
- }
-
- public void write(Trade item) {
- delegate.write(item);
- totalAmount += item.getAmount();
+ public void writeFooter(Writer writer) throws IOException {
+ writer.write("Total Amount Processed: " + totalAmount);
}
-}
+ public void setDelegate(ItemWriter delegate) {...}
+ }
- The custom writer in the example is stateful (it maintains its total
- in an instance variable totalAmount), but the state is
- stored through the ItemStream interface in the
- ExecutionContext. In this way we can be sure that
- when the open() callback is received on a restart. The
- framework garuntees we always get the last value that was committed. It
- should be noted that it is not always necessary to implement
- ItemStream. For example, if the
- ItemWriter is re-runnable, in the sense that it
- maintains its own state in a transactional resource like a database, there
- is no need to maintain state within the writer itself.
+ This TradeItemWriter stores a
+ totalAmount value that is increased with the
+ amount from each Trade item written.
+ After the last Trade is processed, the framework
+ will call writeFooter, which will put that
+ totalAmount into the file. In order for the method to be
+ called, the TradeItemWriter must be wired into
+ the FlatFileItemWriter as the
+ footerCallback:
+
+ <bean id="tradeItemWriter" class="..TradeItemWriter">
+ <property name="delegate" ref="flatFileItemWriter" />
+ </bean>
+
+ <bean id="flatFileItemWriter"
+ class="org.springframework.batch.item.file.FlatFileItemWriter">
+ <property name="resource" ref="outputResource" />
+ <property name="lineAggregator" ref="lineAggregator"/>
+ <property name="footerCallback" ref="tradeItemWriter" />
+ </bean>
+
+
+ The way that the TradeItemWriter has been
+ so far will only function correctly if the Step
+ is not restartable. This is because the class is stateful (since it
+ stores the totalAmount), but the totalAmount
+ is not persisted to the database, and therefore, it cannot be retrieved
+ in the event of a restart. In order to make this class restartable, the
+ ItemStream interface should be implemented along
+ with the methods open and
+ update:
+
+ private static final String TOTAL_AMOUNT_KEY = "total.amount";
+
+ public void open(ExecutionContext executionContext) {
+ if (executionContext.containsKey(TOTAL_AMOUNT_KEY) {
+ totalAmount = executionContext.getDouble(TOTAL_AMOUNT_KEY);
+ }
+ }
+
+ public void update(ExecutionContext executionContext) {
+ executionContext.setDouble(TOTAL_AMOUNT_KEY, totalAmount);
+ }
+
+ The update method will store the most
+ current version of totalAmount to the
+ ExecutionContext just before that object is
+ persisted to the database. The open method will
+ retrieve any existing totalAmount from the
+ ExecutionContext and use it as the starting point
+ for processing, allowing the TradeItemWriter to
+ pick up on restart where it left off the previous time the
+ Step was executed.
+
+ It should be noted that it is not always necessary to implement
+ ItemStream. For example, if the
+ ItemWriter is re-runnable, meaning that it
+ maintains its own state in a transactional resource like a database,
+ there is no need to maintain state within the writer itself.
+
diff --git a/docs/src/site/docbook/reference/job.xml b/docs/src/site/docbook/reference/job.xml
index 4c265f61d..e1fc41fcf 100644
--- a/docs/src/site/docbook/reference/job.xml
+++ b/docs/src/site/docbook/reference/job.xml
@@ -494,9 +494,9 @@
linkend="domain" />. The first argument is 'endOfDayJob.xml', which is
the Spring ApplicationContext containing the
Job. The second argument, 'endOfDay' represents
- the job name. The final argument, 'schedule.date=01-01-2008' will be
- converted into JobParameters. An example of the
- XML configuration is below:
+ the job name. The final argument, 'schedule.date(date)=2008/01/01'
+ will be converted into JobParameters. An
+ example of the XML configuration is below:
<job id="endOfDay">
<steps>