/*
* 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.core.repository.dao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class AbstractJobExecutionDaoTests {
protected JobExecutionDao dao;
protected JobInstance jobInstance;
protected JobExecution execution;
protected JobParameters jobParameters;
/**
* @return tested object ready for use
*/
protected abstract JobExecutionDao getJobExecutionDao();
protected abstract JobInstanceDao getJobInstanceDao();
/**
* @return tested object ready for use
*/
protected StepExecutionDao getStepExecutionDao() {
return null;
}
@Before
public void onSetUp() throws Exception {
dao = getJobExecutionDao();
jobParameters = new JobParameters();
jobInstance = getJobInstanceDao().createJobInstance("execTestJob", jobParameters);
execution = new JobExecution(jobInstance, new JobParameters());
}
/**
* Save and find a job execution.
*/
@Transactional
@Test
public void testSaveAndFind() {
execution.setStartTime(new Date(System.currentTimeMillis()));
execution.setLastUpdated(new Date(System.currentTimeMillis()));
execution.setExitStatus(ExitStatus.UNKNOWN);
execution.setEndTime(new Date(System.currentTimeMillis()));
dao.saveJobExecution(execution);
List<JobExecution> executions = dao.findJobExecutions(jobInstance);
assertEquals(1, executions.size());
assertEquals(execution, executions.get(0));
assertExecutionsAreEqual(execution, executions.get(0));
}
/**
* Executions should be returned in the reverse order they were saved.
*/
@Transactional
@Test
public void testFindExecutionsOrdering() {
List<JobExecution> execs = new ArrayList<JobExecution>();
for (int i = 0; i < 10; i++) {
JobExecution exec = new JobExecution(jobInstance, jobParameters);
exec.setCreateTime(new Date(i));
execs.add(exec);
dao.saveJobExecution(exec);
}
List<JobExecution> retrieved = dao.findJobExecutions(jobInstance);
Collections.reverse(retrieved);
for (int i = 0; i < 10; i++) {
assertExecutionsAreEqual(execs.get(i), retrieved.get(i));
}
}
/**
* Save and find a job execution.
*/
@Transactional
@Test
public void testFindNonExistentExecutions() {
List<JobExecution> executions = dao.findJobExecutions(jobInstance);
assertEquals(0, executions.size());
}
/**
* Saving sets id to the entity.
*/
@Transactional
@Test
public void testSaveAddsIdAndVersion() {
assertNull(execution.getId());
assertNull(execution.getVersion());
dao.saveJobExecution(execution);
assertNotNull(execution.getId());
assertNotNull(execution.getVersion());
}
/**
* Update and retrieve job execution - check attributes have changed as
* expected.
*/
@Transactional
@Test
public void testUpdateExecution() {
execution.setStatus(BatchStatus.STARTED);
dao.saveJobExecution(execution);
execution.setLastUpdated(new Date(0));
execution.setStatus(BatchStatus.COMPLETED);
dao.updateJobExecution(execution);
JobExecution updated = dao.findJobExecutions(jobInstance).get(0);
assertEquals(execution, updated);
assertEquals(BatchStatus.COMPLETED, updated.getStatus());
assertExecutionsAreEqual(execution, updated);
}
/**
* Check the execution with most recent start time is returned
*/
@Transactional
@Test
public void testGetLastExecution() {
JobExecution exec1 = new JobExecution(jobInstance, jobParameters);
exec1.setCreateTime(new Date(0));
JobExecution exec2 = new JobExecution(jobInstance, jobParameters);
exec2.setCreateTime(new Date(1));
dao.saveJobExecution(exec1);
dao.saveJobExecution(exec2);
JobExecution last = dao.getLastJobExecution(jobInstance);
assertEquals(exec2, last);
}
/**
* Check the execution is returned
*/
@Transactional
@Test
public void testGetMissingLastExecution() {
JobExecution value = dao.getLastJobExecution(jobInstance);
assertNull(value);
}
/**
* Check the execution is returned
*/
@Transactional
@Test
public void testFindRunningExecutions() {
JobExecution exec = new JobExecution(jobInstance, jobParameters);
exec.setCreateTime(new Date(0));
exec.setEndTime(new Date(1L));
exec.setLastUpdated(new Date(5L));
dao.saveJobExecution(exec);
exec = new JobExecution(jobInstance, jobParameters);
exec.setLastUpdated(new Date(5L));
exec.createStepExecution("step");
dao.saveJobExecution(exec);
StepExecutionDao stepExecutionDao = getStepExecutionDao();
if (stepExecutionDao != null) {
for (StepExecution stepExecution : exec.getStepExecutions()) {
stepExecutionDao.saveStepExecution(stepExecution);
}
}
Set<JobExecution> values = dao.findRunningJobExecutions(exec.getJobInstance().getJobName());
assertEquals(1, values.size());
JobExecution value = values.iterator().next();
assertEquals(exec, value);
assertEquals(5L, value.getLastUpdated().getTime());
}
/**
* Check the execution is returned
*/
@Transactional
@Test
public void testNoRunningExecutions() {
Set<JobExecution> values = dao.findRunningJobExecutions("no-such-job");
assertEquals(0, values.size());
}
/**
* Check the execution is returned
*/
@Transactional
@Test
public void testGetExecution() {
JobExecution exec = new JobExecution(jobInstance, jobParameters);
exec.setCreateTime(new Date(0));
exec.createStepExecution("step");
dao.saveJobExecution(exec);
StepExecutionDao stepExecutionDao = getStepExecutionDao();
if (stepExecutionDao != null) {
for (StepExecution stepExecution : exec.getStepExecutions()) {
stepExecutionDao.saveStepExecution(stepExecution);
}
}
JobExecution value = dao.getJobExecution(exec.getId());
assertEquals(exec, value);
// N.B. the job instance is not re-hydrated in the JDBC case...
}
/**
* Check the execution is returned
*/
@Transactional
@Test
public void testGetMissingExecution() {
JobExecution value = dao.getJobExecution(54321L);
assertNull(value);
}
/**
* Exception should be raised when the version of update argument doesn't
* match the version of persisted entity.
*/
@Transactional
@Test
public void testConcurrentModificationException() {
JobExecution exec1 = new JobExecution(jobInstance, jobParameters);
dao.saveJobExecution(exec1);
JobExecution exec2 = new JobExecution(jobInstance, jobParameters);
exec2.setId(exec1.getId());
exec2.incrementVersion();
assertEquals((Integer) 0, exec1.getVersion());
assertEquals(exec1.getVersion(), exec2.getVersion());
dao.updateJobExecution(exec1);
assertEquals((Integer) 1, exec1.getVersion());
try {
dao.updateJobExecution(exec2);
fail();
}
catch (OptimisticLockingFailureException e) {
// expected
}
}
/**
* Successful synchronization from STARTED to STOPPING status.
*/
@Transactional
@Test
public void testSynchronizeStatusUpgrade() {
JobExecution exec1 = new JobExecution(jobInstance, jobParameters);
exec1.setStatus(BatchStatus.STOPPING);
dao.saveJobExecution(exec1);
JobExecution exec2 = new JobExecution(jobInstance, jobParameters);
assertTrue(exec1.getId() != null);
exec2.setId(exec1.getId());
exec2.setStatus(BatchStatus.STARTED);
exec2.setVersion(7);
assertTrue(exec1.getVersion() != exec2.getVersion());
assertTrue(exec1.getStatus() != exec2.getStatus());
dao.synchronizeStatus(exec2);
assertEquals(exec1.getVersion(), exec2.getVersion());
assertEquals(exec1.getStatus(), exec2.getStatus());
}
/**
* UNKNOWN status won't be changed by synchronizeStatus, because it is the
* 'largest' BatchStatus (will not downgrade).
*/
@Transactional
@Test
public void testSynchronizeStatusDowngrade() {
JobExecution exec1 = new JobExecution(jobInstance, jobParameters);
exec1.setStatus(BatchStatus.STARTED);
dao.saveJobExecution(exec1);
JobExecution exec2 = new JobExecution(jobInstance, jobParameters);
assertTrue(exec1.getId() != null);
exec2.setId(exec1.getId());
exec2.setStatus(BatchStatus.UNKNOWN);
exec2.setVersion(7);
assertTrue(exec1.getVersion() != exec2.getVersion());
assertTrue(exec1.getStatus().isLessThan(exec2.getStatus()));
dao.synchronizeStatus(exec2);
assertEquals(exec1.getVersion(), exec2.getVersion());
assertEquals(BatchStatus.UNKNOWN, exec2.getStatus());
}
/*
* Check to make sure the executions are equal. Normally, comparing the id's
* is sufficient. However, for testing purposes, especially of a DAO, we
* need to make sure all the fields are being stored/retrieved correctly.
*/
private void assertExecutionsAreEqual(JobExecution lhs, JobExecution rhs) {
assertEquals(lhs.getId(), rhs.getId());
assertEquals(lhs.getStartTime(), rhs.getStartTime());
assertEquals(lhs.getStatus(), rhs.getStatus());
assertEquals(lhs.getEndTime(), rhs.getEndTime());
assertEquals(lhs.getCreateTime(), rhs.getCreateTime());
assertEquals(lhs.getLastUpdated(), rhs.getLastUpdated());
assertEquals(lhs.getVersion(), rhs.getVersion());
}
}