Skip to content

Commit 7f3630e

Browse files
committed
Refine contribution #4944
- Log warnings instead of throwing exceptions when recovering already recovered job executions - Update tests - Update Javadocs
1 parent 6090a80 commit 7f3630e

File tree

4 files changed

+31
-29
lines changed

4 files changed

+31
-29
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,7 @@ JobExecution startNextInstance(Job job) throws JobRestartException, JobExecution
256256
* for restart by updating its execution context with the flag {@code recovered=true}.
257257
* @param jobExecution the {@link JobExecution} to recover
258258
* @return the {@link JobExecution} after it has been marked as recovered
259-
* @throws UnexpectedJobExecutionException if the job execution is already complete or
260-
* abandoned
259+
* @since 6.0
261260
*/
262261
JobExecution recover(JobExecution jobExecution);
263262

spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737

3838
/**
3939
* A command-line utility to operate Spring Batch jobs using the {@link JobOperator}. It
40-
* allows starting, stopping, restarting, and abandoning jobs from the command line.
40+
* allows starting, stopping, restarting, abandoning and recovering jobs from the command
41+
* line.
4142
* <p>
4243
* This utility requires a Spring application context to be set up with the necessary
4344
* batch infrastructure, including a {@link JobOperator}, a {@link JobRepository}, and a
@@ -247,6 +248,7 @@ public int recover(long jobExecutionId) {
247248
* <li>restart jobExecutionId</li>
248249
* <li>stop jobExecutionId</li>
249250
* <li>abandon jobExecutionId</li>
251+
* <li>recover jobExecutionId</li>
250252
* </ul>
251253
* <p>
252254
* and <code>jobParameters</code> are key-value pairs in the form name=value,type,identifying.
@@ -269,6 +271,7 @@ public static void main(String[] args) {
269271
- restart jobExecutionId
270272
- stop jobExecutionId
271273
- abandon jobExecutionId
274+
- recover jobExecutionId
272275
and jobParameters are key-value pairs in the form name=value,type,identifying.
273276
""";
274277
System.err.printf(String.format(usage, CommandLineJobOperator.class.getName()));

spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -395,18 +395,22 @@ public JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlread
395395
@Override
396396
public JobExecution recover(JobExecution jobExecution) {
397397
Assert.notNull(jobExecution, "JobExecution must not be null");
398-
399398
if (jobExecution.getExecutionContext().containsKey("recovered")) {
400-
if (logger.isInfoEnabled()) {
401-
logger.info("already recovered job execution: " + jobExecution);
399+
if (logger.isWarnEnabled()) {
400+
logger.warn("Job execution already recovered: " + jobExecution);
402401
}
403-
throw new UnexpectedJobExecutionException("JobExecution is already recovered");
402+
return jobExecution;
404403
}
405404

406405
BatchStatus jobStatus = jobExecution.getStatus();
407-
if (jobStatus == BatchStatus.COMPLETED || jobStatus == BatchStatus.ABANDONED) {
408-
throw new UnexpectedJobExecutionException(
409-
"JobExecution is already complete or abandoned and therefore cannot be recovered: " + jobExecution);
406+
if (jobStatus == BatchStatus.COMPLETED || jobStatus == BatchStatus.ABANDONED
407+
|| jobStatus == BatchStatus.UNKNOWN) {
408+
if (logger.isWarnEnabled()) {
409+
logger.warn(
410+
"JobExecution is already complete or abandoned or in an unknown state, and therefore cannot be recovered: "
411+
+ jobExecution);
412+
}
413+
return jobExecution;
410414
}
411415

412416
if (logger.isInfoEnabled()) {
@@ -415,7 +419,7 @@ public JobExecution recover(JobExecution jobExecution) {
415419

416420
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
417421
BatchStatus stepStatus = stepExecution.getStatus();
418-
if (stepStatus.isRunning() || stepStatus == BatchStatus.STOPPING) {
422+
if (stepStatus.isRunning()) {
419423
stepExecution.setStatus(BatchStatus.FAILED);
420424
stepExecution.setEndTime(LocalDateTime.now());
421425
stepExecution.getExecutionContext().put("recovered", true);

spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.batch.core.job.JobExecution;
3333
import org.springframework.batch.core.job.JobExecutionException;
3434
import org.springframework.batch.core.job.JobInstance;
35-
import org.springframework.batch.core.job.UnexpectedJobExecutionException;
3635
import org.springframework.batch.core.job.parameters.JobParameters;
3736
import org.springframework.batch.core.job.parameters.JobParametersIncrementer;
3837
import org.springframework.batch.core.step.Step;
@@ -56,14 +55,9 @@
5655
import org.springframework.batch.support.PropertiesConverter;
5756
import org.springframework.lang.Nullable;
5857

59-
import static org.junit.jupiter.api.Assertions.assertEquals;
60-
import static org.junit.jupiter.api.Assertions.assertNotNull;
61-
import static org.junit.jupiter.api.Assertions.assertThrows;
62-
import static org.junit.jupiter.api.Assertions.assertTrue;
58+
import static org.junit.jupiter.api.Assertions.*;
6359
import static org.mockito.ArgumentMatchers.any;
64-
import static org.mockito.Mockito.mock;
65-
import static org.mockito.Mockito.verify;
66-
import static org.mockito.Mockito.when;
60+
import static org.mockito.Mockito.*;
6761

6862
/**
6963
* @author Dave Syer
@@ -429,49 +423,51 @@ void testAbortNonStopping() {
429423
}
430424

431425
@Test
432-
void testRecover() {
426+
void testRecoverStartedJob() {
433427
JobInstance jobInstance = new JobInstance(123L, job.getName());
434428
JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters);
435429
jobExecution.setStatus(BatchStatus.STARTED);
436430
jobExecution.createStepExecution("step1").setStatus(BatchStatus.STARTED);
437431
when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution);
438-
when(jobRepository.getLastJobExecution(jobInstance)).thenReturn(jobExecution);
439432
JobExecution recover = jobOperator.recover(jobExecution);
440433
assertEquals(BatchStatus.FAILED, recover.getStatus());
434+
assertNotNull(recover.getEndTime());
441435
assertTrue(recover.getExecutionContext().containsKey("recovered"));
442436
}
443437

444438
@Test
445-
void testRecoverStepStopping() {
439+
void testRecoverStoppingStep() {
446440
JobInstance jobInstance = new JobInstance(123L, job.getName());
447441
JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters);
448442
jobExecution.setStatus(BatchStatus.STARTED);
449443
jobExecution.createStepExecution("step1").setStatus(BatchStatus.STOPPING);
450444
when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution);
451-
when(jobRepository.getLastJobExecution(jobInstance)).thenReturn(jobExecution);
452445
JobExecution recover = jobOperator.recover(jobExecution);
453446
assertEquals(BatchStatus.FAILED, recover.getStatus());
447+
assertNotNull(recover.getEndTime());
454448
assertTrue(recover.getExecutionContext().containsKey("recovered"));
455449
}
456450

457451
@Test
458-
void testRecoverJobAbandon() {
452+
void testRecoverAbandonedJob() {
459453
JobInstance jobInstance = new JobInstance(123L, job.getName());
460454
JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters);
461455
jobExecution.setStatus(BatchStatus.ABANDONED);
462456
when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution);
463-
when(jobRepository.getLastJobExecution(jobInstance)).thenReturn(jobExecution);
464-
assertThrows(UnexpectedJobExecutionException.class, () -> jobOperator.recover(jobExecution));
457+
JobExecution recover = jobOperator.recover(jobExecution);
458+
assertSame(recover, jobExecution);
459+
verifyNoInteractions(jobRepository);
465460
}
466461

467462
@Test
468-
void testRecoverJobCompleted() {
463+
void testRecoverCompletedJob() {
469464
JobInstance jobInstance = new JobInstance(123L, job.getName());
470465
JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters);
471466
jobExecution.setStatus(BatchStatus.COMPLETED);
472467
when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution);
473-
when(jobRepository.getLastJobExecution(jobInstance)).thenReturn(jobExecution);
474-
assertThrows(UnexpectedJobExecutionException.class, () -> jobOperator.recover(jobExecution));
468+
JobExecution recover = jobOperator.recover(jobExecution);
469+
assertSame(recover, jobExecution);
470+
verifyNoInteractions(jobRepository);
475471
}
476472

477473
static class MockJob extends AbstractJob {

0 commit comments

Comments
 (0)