diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/BatchStatus.java b/spring-batch-core/src/main/java/org/springframework/batch/core/BatchStatus.java index a669b606b..309dd5239 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/BatchStatus.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/BatchStatus.java @@ -26,21 +26,82 @@ package org.springframework.batch.core; public enum BatchStatus { /** - * The order of the status values is significant. An execution is expected - * to move up from lowest to highest, hopefully stopping at COMPLETED. After - * COMPLETED, higher values signify more serious failure. + * The order of the status values is significant because it can be used to + * aggregate a set of status values - the result should be the maximum + * value. Since COMPLETED is first in the order, only if all elements of an + * execution are COMPLETED will the aggregate status be COMPLETED. A running + * execution is expected to move from STARTING to STARTED to COMPLETED + * (through the order defined by {@link #upgradeTo(BatchStatus)}). Higher + * values than STARTED signify more serious failure. */ - STARTING, STARTED, COMPLETED, PAUSED, FAILED, STOPPING, STOPPED, UNKNOWN; + COMPLETED, STARTING, WAITING, STARTED, FAILED, STOPPING, STOPPED, UNKNOWN; public static BatchStatus max(BatchStatus status1, BatchStatus status2) { - if (status1.compareTo(status2) < 0) { + if (status1.isLessThan(status2)) { return status2; } - if (status1.compareTo(status2) > 0) { + if (status1.isGreaterThan(status2)) { return status1; } else return status1; } + /** + * Convenience method to decide if a status indicates work is in progress. + * + * @return true if the status is STARTING, STARTED or WAITING + */ + public boolean isRunning() { + return this == STARTING || this == STARTED || this == WAITING; + } + + /** + * Convenience method to decide if a status indicates execution was unsuccessful. + * + * @return true if the status is FAILED or greater + */ + public boolean isUnsuccessful() { + return this==FAILED || this.isGreaterThan(FAILED); + } + + /** + * Method used to move status values through their logical progression, and + * override less severe failures with more severe ones. This value is + * compared with the parameter and the one that has higher priority is + * returned. If both are STARTED or less than the value returned is the + * largest in the sequence STARTING, STARTED, COMPLETED. Otherwise the value + * returned is the maximum of the two. + * + * @param other another status to compare to + * @return either this or the other status depending on their priority + */ + public BatchStatus upgradeTo(BatchStatus other) { + if (isGreaterThan(STARTED) || other.isGreaterThan(STARTED)) { + return max(this, other); + } + // Both less than or equal to STARTED + if (this == COMPLETED) + return COMPLETED; + if (other == COMPLETED) + return COMPLETED; + return max(this, other); + } + + /** + * @param other a status value to compare + * @return true if this is greater than other + */ + public boolean isGreaterThan(BatchStatus other) { + return this.compareTo(other) > 0; + } + + /** + * @param other a status value to compare + * @return true if this is less than other + */ + public boolean isLessThan(BatchStatus other) { + return this.compareTo(other) < 0; + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java index f218ac5f2..ad7ee34c5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java @@ -121,9 +121,7 @@ public class JobExecution extends Entity { * @param status the new status value */ public void upgradeStatus(BatchStatus status) { - if (status.compareTo(this.status) > 0) { - this.status = status; - } + this.status = this.status.upgradeTo(status); } /** @@ -211,23 +209,26 @@ public class JobExecution extends Entity { } /** - * Signal that this job execution wishes to be paused. Uses - * {@link #upgradeStatus(BatchStatus)} so that a failed execution stays - * failed. + * Signal that this job execution wishes to wait for work to complete, and + * no more processing will take place on the current thread. A failed + * execution stays failed. */ - public void pause() { - upgradeStatus(BatchStatus.PAUSED); + public void pauseAndWait() { + if (!status.isUnsuccessful()) { + status = BatchStatus.WAITING; + } } /** - * Test if the {@link JobExecution} has been paused. + * Test if the {@link JobExecution} has been paused and is waiting for work + * to be done. * - * @see #pause() + * @see #pauseAndWait() * - * @return true if this instance is paused + * @return true if this instance is waiting */ - public boolean isPaused() { - return status == BatchStatus.PAUSED; + public boolean isWaiting() { + return status == BatchStatus.WAITING; } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java index a075a0590..c88ea0fb5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java @@ -53,7 +53,7 @@ public class StepExecution extends Entity { private volatile int rollbackCount = 0; private volatile int readSkipCount = 0; - + private volatile int processSkipCount = 0; private volatile int writeSkipCount = 0; @@ -71,7 +71,7 @@ public class StepExecution extends Entity { private volatile boolean terminateOnly; private volatile int filterCount; - + private transient volatile List failureExceptions = new ArrayList(); /** @@ -155,7 +155,7 @@ public class StepExecution extends Entity { public void setEndTime(Date endTime) { this.endTime = endTime; } - + /** * Returns the current number of items read for this execution * @@ -209,10 +209,11 @@ public class StepExecution extends Entity { public int getFilterCount() { return filterCount; } - + /** * Public setter for the number of items filtered out of this execution. - * @param filterCount the number of items filtered out of this execution to set + * @param filterCount the number of items filtered out of this execution to + * set */ public void setFilterCount(int filterCount) { this.filterCount = filterCount; @@ -261,6 +262,40 @@ public class StepExecution extends Entity { this.status = status; } + /** + * Upgrade the status field if the provided value is greater than the + * existing one. Clients using this method to set the status can be sure + * that they don't overwrite a failed status with an successful one. + * + * @param status the new status value + */ + public void upgradeStatus(BatchStatus status) { + this.status = this.status.upgradeTo(status); + } + + /** + * Signal that this job execution wishes to wait for work to complete, and + * no more processing will take place on the current thread. A failed + * execution stays failed. + */ + public void pauseAndWait() { + if (!status.isUnsuccessful()) { + status = BatchStatus.WAITING; + } + } + + /** + * Test if the {@link JobExecution} has been paused and is waiting for work + * to be done. + * + * @see #pauseAndWait() + * + * @return true if this instance is waiting + */ + public boolean isWaiting() { + return status == BatchStatus.WAITING; + } + /** * @return the name of the step */ @@ -410,7 +445,7 @@ public class StepExecution extends Entity { public void setWriteSkipCount(int writeSkipCount) { this.writeSkipCount = writeSkipCount; } - + /** * @return the number of records skipped during processing */ @@ -442,12 +477,12 @@ public class StepExecution extends Entity { public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } - + public List getFailureExceptions() { return failureExceptions; } - - public void addFailureException(Throwable throwable){ + + public void addFailureException(Throwable throwable) { this.failureExceptions.add(throwable); } @@ -470,7 +505,8 @@ public class StepExecution extends Entity { } /** - * Deserialise and ensure transient fields are re-instantiated when read back + * Deserialise and ensure transient fields are re-instantiated when read + * back */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); @@ -490,9 +526,10 @@ public class StepExecution extends Entity { public String toString() { return super.toString() - + String.format(", name=%s, status=%s, exitStatus=%s, readCount=%d, filterCount=%d, writeCount=%d readSkipCount=%d, writeSkipCount=%d" - + ", commitCount=%d, rollbackCount=%d", stepName, status, exitStatus, readCount, filterCount, writeCount, readSkipCount, writeSkipCount, - commitCount, rollbackCount); + + String.format( + ", name=%s, status=%s, exitStatus=%s, readCount=%d, filterCount=%d, writeCount=%d readSkipCount=%d, writeSkipCount=%d" + + ", commitCount=%d, rollbackCount=%d", stepName, status, exitStatus, readCount, + filterCount, writeCount, readSkipCount, writeSkipCount, commitCount, rollbackCount); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java index a7fb940d0..d39b0ab3e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java @@ -99,7 +99,7 @@ public class SimpleJobExplorer implements JobExplorer { * @see org.springframework.batch.core.explore.JobExplorer#getStepExecution(java.lang.Long) */ public StepExecution getStepExecution(Long executionId, String stepName) { - JobExecution jobExecution = jobExecutionDao.getJobExecution(executionId); + JobExecution jobExecution = getJobExecution(executionId); return stepExecutionDao.getStepExecution(jobExecution, stepName); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 71a86db25..bfa15b44a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -219,7 +219,7 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea execution.setStartTime(new Date()); // If paused we need to retain the status so that subclasses can // handle the resume, otherwise we mark it as started... - if (!execution.isPaused()) { + if (!execution.isWaiting()) { updateStatus(execution, BatchStatus.STARTED); } @@ -228,7 +228,7 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea StepExecution lastStepExecution = doExecute(execution); if (lastStepExecution != null) { - if (!execution.isPaused()) { + if (!execution.isWaiting()) { // If the subclass wants to pause don't change the // status. execution.setStatus(lastStepExecution.getStatus()); @@ -247,13 +247,13 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea } catch (JobInterruptedException e) { - logger.error(e); + logger.error("Encountered interruption executing job", e); execution.setExitStatus(ExitStatus.FAILED); execution.setStatus(BatchStatus.STOPPED); execution.addFailureException(e); } catch (Throwable t) { - logger.error(t); + logger.error("Encountered error executing job", t); execution.setExitStatus(ExitStatus.FAILED); execution.setStatus(BatchStatus.FAILED); execution.addFailureException(t); @@ -307,14 +307,13 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea JobInstance jobInstance = execution.getJobInstance(); - StepExecution currentStepExecution = null; + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); + StepExecution currentStepExecution = lastStepExecution; - if (shouldStart(jobInstance, step)) { + if (shouldStart(lastStepExecution, jobInstance, step)) { currentStepExecution = execution.createStepExecution(step.getName()); - StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); - boolean isRestart = (lastStepExecution != null && !lastStepExecution.getStatus().equals( BatchStatus.COMPLETED)) ? true : false; @@ -343,18 +342,19 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea /** * Given a step and configuration, return true if the step should start, * false if it should not, and throw an exception if the job should finish. + * @param lastStepExecution the last step execution + * @param jobInstance + * @param step * * @throws StartLimitExceededException if the start limit has been exceeded * for this step * @throws JobRestartException if the job is in an inconsistent state from * an earlier failure */ - private boolean shouldStart(JobInstance jobInstance, Step step) throws JobRestartException, + private boolean shouldStart(StepExecution lastStepExecution, JobInstance jobInstance, Step step) throws JobRestartException, StartLimitExceededException { BatchStatus stepStatus; - // if the last execution is null, the step has never been executed. - StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); if (lastStepExecution == null) { stepStatus = BatchStatus.STARTING; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/PauseState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/PauseState.java index 4bd50dc4a..06e10d907 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/PauseState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/PauseState.java @@ -27,8 +27,8 @@ public class PauseState extends AbstractState { // not already paused we pause it, and expect the flow to respect the // status. synchronized (jobExecution) { - if (!jobExecution.isPaused()) { - jobExecution.pause(); + if (!jobExecution.isWaiting()) { + jobExecution.pauseAndWait(); return FlowExecution.PAUSED; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java index 12c7ffdba..0c35fb20a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java @@ -84,7 +84,7 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean { final JobExecution jobExecution; JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters); if (lastExecution != null) { - if (lastExecution.isPaused()) { + if (lastExecution.isWaiting()) { jobExecution = lastExecution; // this execution will be continued => delete the end time jobExecution.setEndTime(null); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index ad93a0ae3..c0dbfc8b7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -370,7 +370,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean { public boolean pause(long executionId) throws NoSuchJobExecutionException { JobExecution jobExecution = findExecutionById(executionId); - jobExecution.pause(); + jobExecution.pauseAndWait(); jobRepository.update(jobExecution); return true; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java index 9454bddc4..9cdf877ce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java @@ -73,15 +73,16 @@ public class PartitionStep extends AbstractStep { // Wait for task completion and then aggregate the results Collection executions = partitionHandler.handle(stepExecutionSplitter, stepExecution); + stepExecution.upgradeStatus(BatchStatus.COMPLETED); aggregator.aggregate(stepExecution, executions); - - if (stepExecution.getJobExecution().isPaused()) { - return; - } // If anything failed or had a problem we need to crap out + if (stepExecution.getStatus().isUnsuccessful()) { + throw new JobExecutionException("Partition handler returned an unsuccessful step"); + } + if (stepExecution.getStatus() != BatchStatus.COMPLETED) { - throw new JobExecutionException("Partition handler returned an incomplete step"); + stepExecution.pauseAndWait(); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java index f29b93a7d..62ca2cbd3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java @@ -191,7 +191,7 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements if (count == 0) { int curentVersion = getJdbcTemplate().queryForInt(getQuery(CURRENT_VERSION_JOB_EXECUTION), new Object[] { jobExecution.getId() }); - throw new OptimisticLockingFailureException("Attempt to update step execution id=" + throw new OptimisticLockingFailureException("Attempt to update job execution id=" + jobExecution.getId() + " with wrong version (" + jobExecution.getVersion() + "), where current version is " + curentVersion); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index b53e52fb9..84e3b0365 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -199,6 +199,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw } stepExecution.setStatus(BatchStatus.COMPLETED); + logger.debug("Step execution success: " + stepExecution); try { getJobRepository().update(stepExecution); @@ -265,6 +266,9 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw + "This job is now in an unknown state and should not be restarted.", commitException); stepExecution.addFailureException(commitException); } + + logger.debug("Step execution complete: " + stepExecution); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/BatchStatusTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/BatchStatusTests.java index dcfa815d9..d6069a0bd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/BatchStatusTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/BatchStatusTests.java @@ -44,6 +44,42 @@ public class BatchStatusTests { assertEquals(BatchStatus.FAILED, BatchStatus.max(BatchStatus.FAILED,BatchStatus.COMPLETED)); assertEquals(BatchStatus.FAILED, BatchStatus.max(BatchStatus.COMPLETED, BatchStatus.FAILED)); assertEquals(BatchStatus.FAILED, BatchStatus.max(BatchStatus.FAILED, BatchStatus.FAILED)); + assertEquals(BatchStatus.STARTED, BatchStatus.max(BatchStatus.STARTED, BatchStatus.STARTING)); + assertEquals(BatchStatus.STARTED, BatchStatus.max(BatchStatus.COMPLETED, BatchStatus.STARTED)); + } + + @Test + public void testUpgradeStatusFinished() { + assertEquals(BatchStatus.FAILED, BatchStatus.FAILED.upgradeTo(BatchStatus.COMPLETED)); + assertEquals(BatchStatus.FAILED, BatchStatus.COMPLETED.upgradeTo(BatchStatus.FAILED)); + } + + @Test + public void testUpgradeStatusUnfinished() { + assertEquals(BatchStatus.COMPLETED, BatchStatus.STARTING.upgradeTo(BatchStatus.COMPLETED)); + assertEquals(BatchStatus.COMPLETED, BatchStatus.COMPLETED.upgradeTo(BatchStatus.STARTING)); + assertEquals(BatchStatus.STARTED, BatchStatus.STARTING.upgradeTo(BatchStatus.STARTED)); + assertEquals(BatchStatus.STARTED, BatchStatus.STARTED.upgradeTo(BatchStatus.STARTING)); + assertEquals(BatchStatus.COMPLETED, BatchStatus.COMPLETED.upgradeTo(BatchStatus.WAITING)); + assertEquals(BatchStatus.STARTED, BatchStatus.STARTED.upgradeTo(BatchStatus.WAITING)); + } + + @Test + public void testIsRunning() { + assertFalse(BatchStatus.FAILED.isRunning()); + assertFalse(BatchStatus.COMPLETED.isRunning()); + assertTrue(BatchStatus.STARTED.isRunning()); + assertTrue(BatchStatus.STARTING.isRunning()); + assertTrue(BatchStatus.WAITING.isRunning()); + } + + @Test + public void testIsUnsuccessful() { + assertTrue(BatchStatus.FAILED.isUnsuccessful()); + assertFalse(BatchStatus.COMPLETED.isUnsuccessful()); + assertFalse(BatchStatus.STARTED.isUnsuccessful()); + assertFalse(BatchStatus.STARTING.isUnsuccessful()); + assertFalse(BatchStatus.WAITING.isUnsuccessful()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java index baa8dfd5a..2ab0a249b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java @@ -109,12 +109,34 @@ public class JobExecutionTests { /** * Test method for - * {@link org.springframework.batch.core.JobExecution#pause()}. + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. */ @Test - public void testPause() { - execution.pause(); - assertEquals(BatchStatus.PAUSED, execution.getStatus()); + public void testPauseAndWait() { + execution.pauseAndWait(); + assertEquals(BatchStatus.WAITING, execution.getStatus()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. + */ + @Test + public void testPauseAndWaitWhenFailed() { + execution.setStatus(BatchStatus.FAILED); + execution.pauseAndWait(); + assertEquals(BatchStatus.FAILED, execution.getStatus()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. + */ + @Test + public void testPauseAndWaitWhenStarted() { + execution.setStatus(BatchStatus.STARTED); + execution.pauseAndWait(); + assertEquals(BatchStatus.WAITING, execution.getStatus()); } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java index 084a0b767..9454e0c86 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java @@ -275,6 +275,49 @@ public class StepExecutionTests { assertEquals(exception, execution.getFailureExceptions().get(0)); } + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. + */ + @Test + public void testPauseAndWait() { + execution.pauseAndWait(); + assertEquals(BatchStatus.WAITING, execution.getStatus()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. + */ + @Test + public void testPauseAndWaitWhenFailed() { + execution.setStatus(BatchStatus.FAILED); + execution.pauseAndWait(); + assertEquals(BatchStatus.FAILED, execution.getStatus()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#pauseAndWait()}. + */ + @Test + public void testPauseAndWaitWhenStarted() { + execution.setStatus(BatchStatus.STARTED); + execution.pauseAndWait(); + assertEquals(BatchStatus.WAITING, execution.getStatus()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.JobExecution#getStatus()}. + */ + @Test + public void testDowngradeStatus() { + execution.setStatus(BatchStatus.FAILED); + execution.upgradeStatus(BatchStatus.COMPLETED); + assertEquals(BatchStatus.FAILED, execution.getStatus()); + } + private StepExecution newStepExecution(Step step, Long long2) { JobInstance job = new JobInstance(new Long(3), new JobParameters(), "testJob"); StepExecution execution = new StepExecution(step.getName(), new JobExecution(job, long2), new Long(4)); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/exlore/support/SimpleJobExplorerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/exlore/support/SimpleJobExplorerTests.java index 8e1406cc0..e5b788ccf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/exlore/support/SimpleJobExplorerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/exlore/support/SimpleJobExplorerTests.java @@ -76,6 +76,7 @@ public class SimpleJobExplorerTests extends TestCase { public void testGetStepExecution() throws Exception { expect(jobExecutionDao.getJobExecution(123L)).andReturn(jobExecution); expect(stepExecutionDao.getStepExecution(jobExecution, "foo")).andReturn(null); + expect(stepExecutionDao.getStepExecutions(jobExecution)).andReturn(null); replay(jobExecutionDao, stepExecutionDao); jobExplorer.getStepExecution(123L,"foo"); verify(jobExecutionDao, stepExecutionDao); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index 2434fb235..b27c253a9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -313,6 +313,16 @@ public class SimpleJobTests { ex.getMessage().indexOf("start limit exceeded") >= 0); } + @Test + public void testStepAlreadyComplete() throws Exception { + stepExecution1.setStatus(BatchStatus.COMPLETED); + jobRepository.add(stepExecution1); + job.execute(jobExecution); + assertEquals(0, jobExecution.getFailureExceptions().size()); + assertEquals(1, jobExecution.getStepExecutions().size()); + assertEquals(stepExecution2.getStepName(), jobExecution.getStepExecutions().iterator().next().getStepName()); + } + @Test public void testNoSteps() throws Exception { job.setSteps(new ArrayList()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java index f884f6415..71e5543ab 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java @@ -265,7 +265,7 @@ public class FlowJobTests { if (!jobExecution.getAllFailureExceptions().isEmpty()) { throw jobExecution.getAllFailureExceptions().get(0); } - assertEquals(BatchStatus.PAUSED, jobExecution.getStatus()); + assertEquals(BatchStatus.WAITING, jobExecution.getStatus()); assertEquals(1, jobExecution.getStepExecutions().size()); job.execute(jobExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java index ee37031bc..0122726bf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java @@ -194,7 +194,7 @@ public class SimpleJobLauncherTests { public void testResumePausedInstance() throws Exception { long id = 9; JobExecution jobExecution = new JobExecution(null, id); - jobExecution.pause(); + jobExecution.pauseAndWait(); expect(jobRepository.getLastJobExecution(job.getName(), jobParameters)).andReturn(jobExecution); replay(jobRepository); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java index c90d987f3..92949dc0c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java @@ -402,7 +402,7 @@ public class SimpleJobOperatorTests { jobOperator.pause(111L); verify(jobExplorer); verify(jobRepository); - assertEquals(BatchStatus.PAUSED, jobExecution.getStatus()); + assertEquals(BatchStatus.WAITING, jobExecution.getStatus()); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/StepExecutionAggregatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/StepExecutionAggregatorTests.java index 97943e75e..c9fff9c88 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/StepExecutionAggregatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/StepExecutionAggregatorTests.java @@ -40,7 +40,7 @@ public class StepExecutionAggregatorTests { stepExecution2.setStatus(BatchStatus.COMPLETED); aggregator.aggregate(result, Arrays. asList(stepExecution1, stepExecution2)); assertNotNull(result); - assertEquals(BatchStatus.COMPLETED, result.getStatus()); + assertEquals(BatchStatus.STARTING, result.getStatus()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java index a43578e6e..cb7e604c8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java @@ -224,6 +224,7 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional JobExecution value = dao.getJobExecution(exec.getId()); assertEquals(exec, value); + // N.B. the job instance is not re-hydrated in the JDBC case... } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java index 5b46b296a..2cc1f8e6a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java @@ -77,9 +77,10 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona dao = getStepExecutionDao(); } - @Transactional @Test + @Transactional + @Test public void testSaveExecutionAssignsIdAndVersion() throws Exception { - + assertNull(stepExecution.getId()); assertNull(stepExecution.getVersion()); dao.saveStepExecution(stepExecution); @@ -87,9 +88,10 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona assertNotNull(stepExecution.getVersion()); } - @Transactional @Test + @Transactional + @Test public void testSaveAndGetExecution() { - + stepExecution.setStatus(BatchStatus.STARTED); stepExecution.setReadSkipCount(7); stepExecution.setWriteSkipCount(5); @@ -102,18 +104,26 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona dao.saveStepExecution(stepExecution); StepExecution retrieved = dao.getStepExecution(jobExecution, step.getName()); - + assertStepExecutionsAreEqual(stepExecution, retrieved); assertNotNull(retrieved.getVersion()); assertNotNull(retrieved.getJobExecution()); assertNotNull(retrieved.getJobExecution().getId()); - + assertNotNull(retrieved.getJobExecution().getJobId()); + assertNotNull(retrieved.getJobExecution().getJobInstance()); + + } + + @Transactional + @Test + public void testSaveAndGetNonExistentExecution() { assertNull(dao.getStepExecution(jobExecution, "not-existing step")); } - @Transactional @Test + @Transactional + @Test public void testSaveAndFindExecution() { - + stepExecution.setStatus(BatchStatus.STARTED); stepExecution.setReadSkipCount(7); stepExecution.setWriteSkipCount(5); @@ -124,7 +134,8 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona assertStepExecutionsAreEqual(stepExecution, retrieved.get(0)); } - @Transactional @Test + @Transactional + @Test public void testGetForNotExistingJobExecution() { assertNull(dao.getStepExecution(new JobExecution(jobInstance, (long) 777), step.getName())); } @@ -132,7 +143,8 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona /** * To-be-saved execution must not already have an id. */ - @Transactional @Test + @Transactional + @Test public void testSaveExecutionWithIdAlreadySet() { stepExecution.setId((long) 7); try { @@ -147,7 +159,8 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona /** * To-be-saved execution must not already have a version. */ - @Transactional @Test + @Transactional + @Test public void testSaveExecutionWithVersionAlreadySet() { stepExecution.incrementVersion(); try { @@ -163,7 +176,8 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona * Update and retrieve updated StepExecution - make sure the update is * reflected as expected and version number has been incremented */ - @Transactional @Test + @Transactional + @Test public void testUpdateExecution() { stepExecution.setStatus(BatchStatus.STARTED); dao.saveStepExecution(stepExecution); @@ -184,7 +198,8 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona * Exception should be raised when the version of update argument doesn't * match the version of persisted entity. */ - @Transactional @Test + @Transactional + @Test public void testConcurrentModificationException() { step = new StepSupport("foo"); @@ -210,7 +225,7 @@ public abstract class AbstractStepExecutionDaoTests extends AbstractTransactiona } } - + private void assertStepExecutionsAreEqual(StepExecution expected, StepExecution actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getStartTime(), actual.getStartTime());