package org.javaee7.batch.batch.listeners; import org.javaee7.util.BatchTestHelper; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ArchivePaths; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Test; import org.junit.runner.RunWith; import javax.batch.operations.JobOperator; import javax.batch.runtime.*; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * 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: * * * +javax.batch.api.listener.AbstractJobListener+ * * +javax.batch.api.listener.AbstractStepListener+ * * +javax.batch.api.chunk.listener.AbstractChunkListener+ * * +javax.batch.api.chunk.listener.AbstractItemReadListener+ * * +javax.batch.api.chunk.listener.AbstractItemProcessListener+ * * +javax.batch.api.chunk.listener.AbstractItemWriteListener+ * * The Job Listener: * include::MyJobListener[] * * Allows you to execute code before and after the job execution. Useful to setup and clear resources needed by the job. * * The Step Listener: * include::MyStepListener[] * * Allows you to execute code before and after the step execution. Useful to setup and clear resources needed by the * step. * * The Chunk Listener: * include::MyChunkListener[] * * Allows you to execute code before and after the chunk processing. Useful to setup and clear resources needed by the * chunk. * * The Read Listener: * include::MyItemReadListener[] * * 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: * include::MyItemProcessorListener[] * * 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: * include::MyItemWriteListener[] * * 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. * * include::myJob.xml[] * * @author Roberto Cortez */ @RunWith(Arquillian.class) public class BatchListenersTest { /** * We're just going to deploy the application as a +web archive+. Note the inclusion of the following files: * * [source,file] * ---- * /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 +javax.batch.runtime.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 * * @throws Exception an exception if the batch could not complete successfully. */ @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); } }