Batch Listeners

Run
How to run the sample
The source code for this sample can be found in the javaee7-samples GitHub repository. The first thing we need to do is to get the source by downloading the repository and then go into the samples folder:
git clone git://github.com/javaee-samples/javaee7-samples.git
cd javaee7-samples/batch/batch-listeners/
Now we are ready to start testing. You can run all the tests in this sample by executing:
mvn test
Or you can run individual tests by executing one of the following:
mvn test -Dtest=BatchListenersTest

Batch Listeners - Applying Listeners to Job, Chunk, Step, Reader, Processor and Writer

BatchListenersTest

The Batch specification, provides several listeners to notify about specific event occurring during the batch processing execution.

Events can be caught via extending the following classes, for the appropriate batch lifecycle event:

The Job Listener:

@Named()
public class MyJobListener extends AbstractJobListener {

    public MyJobListener();

    @Override()
    public void beforeJob();

    @Override()
    public void afterJob();
}

Allows you to execute code before and after the job execution. Useful to setup and clear resources needed by the job.

The Step Listener:

@Named()
public class MyStepListener extends AbstractStepListener {

    public MyStepListener();

    @Override()
    public void beforeStep() throws Exception;

    @Override()
    public void afterStep() throws Exception;
}

Allows you to execute code before and after the step execution. Useful to setup and clear resources needed by the step.

The Chunk Listener:

@Named()
public class MyChunkListener extends AbstractChunkListener {

    public MyChunkListener();

    @Override()
    public void beforeChunk() throws Exception;

    @Override()
    public void afterChunk() throws Exception;
}

Allows you to execute code before and after the chunk processing. Useful to setup and clear resources needed by the chunk.

The Read Listener:

@Named()
public class MyItemReadListener extends AbstractItemReadListener {

    public MyItemReadListener();

    @Override()
    public void beforeRead() throws Exception;

    @Override()
    public void afterRead(Object item) throws Exception;

    @Override()
    public void onReadError(Exception ex) throws Exception;
}

Allows you to execute code before and after reading a element as well if an error occurs reading that element. Useful to setup additional resources and add additional information to the object reading. You can also provide some logic to treat a failed object read.

The Processor Listener:

@Named()
public class MyItemProcessorListener extends AbstractItemProcessListener {

    public MyItemProcessorListener();

    @Override()
    public void beforeProcess(Object item) throws Exception;

    @Override()
    public void afterProcess(Object item, Object result) throws Exception;

    @Override()
    public void onProcessError(Object item, Exception ex) throws Exception;
}

Allows you to execute code before and after processing a element as well if an error occurs processing that element. Useful to setup additional resources and add additional information to the object processing. You can also provide some logic to treat a failed object processing.

The Writer Listener:

@Named()
public class MyItemWriteListener extends AbstractItemWriteListener {

    public MyItemWriteListener();

    @Override()
    public void beforeWrite(List items) throws Exception;

    @Override()
    public void afterWrite(List items) throws Exception;

    @Override()
    public void onWriteError(List items, Exception ex) throws Exception;
}

Allows you to execute code before and after writing a element as well if an error occurs writing that element. Useful to setup additional resources and add additional information to the object writing. You can also provide some logic to treat a failed object write.

The listeners element can be used at the step level or the job level to define which listeners to run for each batch processing event.

<?xml version="1.0" encoding="UTF-8"?>
<job id="myJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
    <listeners>
        <listener ref="myJobListener"/>
    </listeners>
    <step id="myStep" >
        <listeners>
            <listener ref="myStepListener"/>
            <listener ref="myChunkListener"/>
            <listener ref="myItemReadListener"/>
            <listener ref="myItemProcessorListener"/>
            <listener ref="myItemWriteListener"/>
        </listeners>
        <chunk item-count="3">
            <reader ref="myItemReader"/>
            <processor ref="myItemProcessor"/>
            <writer ref="myItemWriter"/>
        </chunk>
    </step>
</job>

We’re just going to deploy the application as a web archive. Note the inclusion of the following files:

/META-INF/batch-jobs/myJob.xml

The myJob.xml file is needed for running the batch definition.

@Deployment
public static WebArchive createDeployment() {
    WebArchive war = ShrinkWrap.create(WebArchive.class)
            .addClass(BatchTestHelper.class)
            .addPackage("org.javaee7.batch.batch.listeners")
            .addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"))
            .addAsResource("META-INF/batch-jobs/myJob.xml");
    System.out.println(war.toString(true));
    return war;
}

In the test, we’re just going to invoke the batch execution and wait for completion. To validate the test expected behaviour we need to query the Metric object available in the step execution and also verify if the listeners were executed correctly via a CountDownLatch wait.

The batch process itself will read and process 10 elements from numbers 1 to 10, but only write the odd elements.

  • Each listener will decrement the total value of the CountDownLatch, until all the predicted events are executed. The number of predicted events is 60:

    • MyJobListener executes 2 times, 1 for MyJobListener#beforeJob and 1 more for MyJobListener#afterJob.

    • MyStepListener executes 2 times, 1 for MyStepListener#beforeStep and 1 more for MyStepListener#afterStep.

    • MyChunkListener executes 8 times, 4 for MyChunkListener#beforeChunk and 4 more for MyChunkListener#afterChunk. Chunk size is set to 3 and the total elements is 10, so 10/3 = 3 and 1 more for the last element, means 4 for each chunk listener event.

    • MyItemReader executes 22 times, 10 elements in total plus an empty read, so MyItemReadListener#beforeRead executes 11 times and MyItemReadListener#afterRead the other 11 times.

    • MyItemProcessorListener executes 20 times, 10 elements read in total, so MyItemProcessorLister#beforeProcess executes 10 times and MyItemProcessorLister#afterProcess the other 10 times.

    • MyItemWriterListener executed 6 times, 3 times for MyItemWriterListener#beforeWrite and another 3 times for MyItemWriterListener#afterWrite. This one is a bit more tricky, since not every element needs to be written. Looking at MyItemProcessor, only even records are going to be written. We also need to take into account the elements read per chunk, so: Chunk[1] read and process [1,2,3] and wrote [2,6], Chunk[2] read and process [4,5,6] and wrote [10], Chunk[3] read and process [7,8,9] and wrote [14,18], Chunk[4] read and process [10] and did not wrote anything, so only 3 writes for the full processing.

    • Total: 2 + 2 + 8 + 22 + 20 + 6 = 60

@Test
public void testBatchListeners() throws Exception {
    JobOperator jobOperator = BatchRuntime.getJobOperator();
    Long executionId = jobOperator.start("myJob", new Properties());
    JobExecution jobExecution = jobOperator.getJobExecution(executionId);

    jobExecution = BatchTestHelper.keepTestAlive(jobExecution);

    List<StepExecution> stepExecutions = jobOperator.getStepExecutions(executionId);
    for (StepExecution stepExecution : stepExecutions) {
        if (stepExecution.getStepName().equals("myStep")) {
            Map<Metric.MetricType, Long> metricsMap = BatchTestHelper.getMetricsMap(stepExecution.getMetrics());

            assertEquals(10L, metricsMap.get(Metric.MetricType.READ_COUNT).longValue());
            assertEquals(10L / 2L, metricsMap.get(Metric.MetricType.WRITE_COUNT).longValue());
            assertEquals(10L / 3 + (10L % 3 > 0 ? 1 : 0), metricsMap.get(Metric.MetricType.COMMIT_COUNT).longValue());
        }
    }

    assertTrue(BatchListenerRecorder.batchListenersCountDownLatch.await(0, TimeUnit.SECONDS));
    assertEquals(jobExecution.getBatchStatus(), BatchStatus.COMPLETED);
}

Share the Knowledge

Find this sample useful? Share on

There's a lot more about JavaEE to cover. If you're ready to learn more, check out the other available samples.

Help Improve

Find a bug in the sample? Something missing? You can fix it by editing the source, making the correction and sending a pull request. Or report the problem to the issue tracker

Recent Changelog

  • Dec 14, 2014: Switch from polling on jobexecution (for job completion) to polling with joboperator and executionid by Scott Kurz
  • Jul 05, 2014: Removed header license for batch xml files by Roberto Cortez
  • Jun 22, 2014: Removed header license. the licensing is now referenced in the license file in the root of the project by Roberto Cortez
  • Jun 20, 2014: Added fqn to java ee api references to generate direct links to javadocs by radcortez
  • Jun 19, 2014: Documentation clarifications and typos by radcortez
  • May 27, 2014: Added documentation to batch-listeners project by Roberto Cortez
  • Dec 31, 2013: Code style issues by Roberto Cortez
  • Dec 31, 2013: Removed servlets and jsp's by Roberto Cortez
  • Dec 04, 2013: Changed asserts for countdownlatch by Roberto Cortez
  • Dec 04, 2013: Refactored batch tests to use countdownlatch instead of boolean static variables to monitor execution by Roberto Cortez
How to help improve this sample
The source code for this sample can be found in the javaee7-samples GitHub repository. The first thing you need to do is to get the source by downloading the repository and then go into the samples folder:
git clone git://github.com/javaee-samples/javaee7-samples.git
cd javaee7-samples/batch/batch-listeners/

Do the changes as you see fit and send a pull request!

Good Luck!