/*
* Copyright 2008-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.sample;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.JobParametersNotFoundException;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.sample.common.SkipCheckingListener;
import org.springframework.batch.sample.domain.trade.internal.TradeWriter;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Error is encountered during writing - transaction is rolled back and the
* error item is skipped on second attempt to process the chunk.
*
* @author Robert Kasanicky
* @author Dan Garrette
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/skipSample-job-launcher-context.xml" })
public class SkipSampleFunctionalTests {
private JdbcOperations jdbcTemplate;
@Autowired
private JobExplorer jobExplorer;
@Autowired
private JobOperator jobOperator;
@Autowired
@Qualifier("customerIncrementer")
private DataFieldMaxValueIncrementer incrementer;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Before
public void setUp() {
jdbcTemplate.update("DELETE from TRADE");
jdbcTemplate.update("DELETE from CUSTOMER");
for (int i = 1; i < 10; i++) {
jdbcTemplate.update("INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (" + incrementer.nextIntValue() + ", 0, 'customer" + i + "', 100000)");
}
jdbcTemplate.update("DELETE from ERROR_LOG");
}
/**
* LAUNCH 1 <br>
* <br>
* step1
* <ul>
* <li>The step name is saved to the job execution context.
* <li>Read five records from flat file and insert them into the TRADE
* table.
* <li>One record will be invalid, and it will be skipped. Four records will
* be written to the database.
* <li>The skip will result in an exit status that directs the job to run
* the error logging step.
* </ul>
* errorPrint1
* <ul>
* <li>The error logging step will log one record using the step name from
* the job execution context.
* </ul>
* step2
* <ul>
* <li>The step name is saved to the job execution context.
* <li>Read four records from the TRADE table and processes them.
* <li>One record will be invalid, and it will be skipped. Three records
* will be stored in the writer's "items" property.
* <li>The skip will result in an exit status that directs the job to run
* the error logging step.
* </ul>
* errorPrint2
* <ul>
* <li>The error logging step will log one record using the step name from
* the job execution context.
* </ul>
* <br>
* <br>
* LAUNCH 2 <br>
* <br>
* step1
* <ul>
* <li>The step name is saved to the job execution context.
* <li>Read five records from flat file and insert them into the TRADE
* table.
* <li>No skips will occur.
* <li>The exist status of SUCCESS will direct the job to step2.
* </ul>
* errorPrint1
* <ul>
* <li>This step does not occur. No error records are logged.
* </ul>
* step2
* <ul>
* <li>The step name is saved to the job execution context.
* <li>Read five records from the TRADE table and processes them.
* <li>No skips will occur.
* <li>The exist status of SUCCESS will direct the job to end.
* </ul>
* errorPrint2
* <ul>
* <li>This step does not occur. No error records are logged.
* </ul>
*/
@Test
public void testJobIncrementing() {
//
// Launch 1
//
long id1 = launchJobWithIncrementer();
JobExecution execution1 = jobExplorer.getJobExecution(id1);
assertEquals(BatchStatus.COMPLETED, execution1.getStatus());
validateLaunchWithSkips(execution1);
//
// Clear the data
//
setUp();
//
// Launch 2
//
long id2 = launchJobWithIncrementer();
JobExecution execution2 = jobExplorer.getJobExecution(id2);
assertEquals(BatchStatus.COMPLETED, execution2.getStatus());
validateLaunchWithoutSkips(execution2);
//
// Make sure that the launches were separate executions and separate
// instances
//
assertTrue(id1 != id2);
assertTrue(!execution1.getJobId().equals(execution2.getJobId()));
}
private void validateLaunchWithSkips(JobExecution jobExecution) {
// Step1: 9 input records, 1 skipped in read, 1 skipped in write =>
// 7 written to output
assertEquals(7, JdbcTestUtils.countRowsInTable((JdbcTemplate) jdbcTemplate, "TRADE"));
// Step2: 7 input records, 1 skipped on process, 1 on write => 5 written
// to output
// System.err.println(jdbcTemplate.queryForList("SELECT * FROM TRADE"));
assertEquals(5, jdbcTemplate.queryForObject("SELECT COUNT(*) from TRADE where VERSION=?", Integer.class, 1).intValue());
// 1 record skipped in processing second step
assertEquals(1, SkipCheckingListener.getProcessSkips());
// Both steps contained skips
assertEquals(2, JdbcTestUtils.countRowsInTable((JdbcTemplate) jdbcTemplate, "ERROR_LOG"));
assertEquals("2 records were skipped!", jdbcTemplate.queryForObject(
"SELECT MESSAGE from ERROR_LOG where JOB_NAME = ? and STEP_NAME = ?", String.class, "skipJob", "step1"));
assertEquals("2 records were skipped!", jdbcTemplate.queryForObject(
"SELECT MESSAGE from ERROR_LOG where JOB_NAME = ? and STEP_NAME = ?", String.class, "skipJob", "step2"));
System.err.println(jobExecution.getExecutionContext());
assertEquals(new BigDecimal("340.45"), jobExecution.getExecutionContext().get(TradeWriter.TOTAL_AMOUNT_KEY));
Map<String, Object> step1Execution = getStepExecutionAsMap(jobExecution, "step1");
assertEquals(new Long(4), step1Execution.get("COMMIT_COUNT"));
assertEquals(new Long(8), step1Execution.get("READ_COUNT"));
assertEquals(new Long(7), step1Execution.get("WRITE_COUNT"));
}
private void validateLaunchWithoutSkips(JobExecution jobExecution) {
// Step1: 5 input records => 5 written to output
assertEquals(5, JdbcTestUtils.countRowsInTable((JdbcTemplate) jdbcTemplate, "TRADE"));
// Step2: 5 input records => 5 written to output
assertEquals(5, jdbcTemplate.queryForObject("SELECT COUNT(*) from TRADE where VERSION=?", Integer.class, 1).intValue());
// Neither step contained skips
assertEquals(0, JdbcTestUtils.countRowsInTable((JdbcTemplate) jdbcTemplate, "ERROR_LOG"));
assertEquals(new BigDecimal("270.75"), jobExecution.getExecutionContext().get(TradeWriter.TOTAL_AMOUNT_KEY));
}
private Map<String, Object> getStepExecutionAsMap(JobExecution jobExecution, String stepName) {
long jobExecutionId = jobExecution.getId();
return jdbcTemplate.queryForMap(
"SELECT * from BATCH_STEP_EXECUTION where JOB_EXECUTION_ID = ? and STEP_NAME = ?", jobExecutionId,
stepName);
}
/**
* Launch the entire job, including all steps, in order.
*
* @return JobExecution, so that the test may validate the exit status
*/
public long launchJobWithIncrementer() {
SkipCheckingListener.resetProcessSkips();
try {
return this.jobOperator.startNextInstance("skipJob");
}
catch (NoSuchJobException e) {
throw new RuntimeException(e);
}
catch (JobExecutionAlreadyRunningException e) {
throw new RuntimeException(e);
}
catch (JobParametersNotFoundException e) {
throw new RuntimeException(e);
}
catch (JobRestartException e) {
throw new RuntimeException(e);
}
catch (JobInstanceAlreadyCompleteException e) {
throw new RuntimeException(e);
}
catch (UnexpectedJobExecutionException e) {
throw new RuntimeException(e);
}
catch (JobParametersInvalidException e) {
throw new RuntimeException(e);
}
}
}