diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java index 8e84ad130..046228a23 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java @@ -16,6 +16,8 @@ package org.springframework.batch.core.job.flow.support.state; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; import org.springframework.batch.core.job.flow.State; @@ -72,8 +74,16 @@ public class EndState extends AbstractState { */ @Override public FlowExecutionStatus handle(FlowExecutor executor) throws Exception { + synchronized (executor) { + // Special case. If the last step execution could not complete we + // are in an unknown state (possibly unrecoverable). + StepExecution stepExecution = executor.getStepExecution(); + if (stepExecution != null && executor.getStepExecution().getStatus() == BatchStatus.UNKNOWN) { + return FlowExecutionStatus.UNKNOWN; + } + if (status.isStop()) { if (!executor.isRestart()) { /* @@ -104,7 +114,9 @@ public class EndState extends AbstractState { } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.springframework.batch.core.job.flow.State#isEndState() */ public boolean isEndState() { diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql index 6f34c3646..238ea9e07 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , - JOB_NAME VARCHAR(100) NOT NULL, - JOB_KEY VARCHAR(32) NOT NULL, - constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) -) ENGINE=InnoDB; - -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , - JOB_INSTANCE_ID BIGINT NOT NULL, - CREATE_TIME DATETIME NOT NULL, - START_TIME DATETIME DEFAULT NULL , - END_TIME DATETIME DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(20) , - EXIT_MESSAGE VARCHAR(2500) , - LAST_UPDATED DATETIME, - constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) - references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) -) ENGINE=InnoDB; - -CREATE TABLE BATCH_JOB_PARAMS ( - JOB_INSTANCE_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL DATETIME DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - constraint JOB_INST_PARAMS_FK foreign key (JOB_INSTANCE_ID) - references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) -) ENGINE=InnoDB; - -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT NOT NULL, - STEP_NAME VARCHAR(100) NOT NULL, - JOB_EXECUTION_ID BIGINT NOT NULL, - START_TIME DATETIME NOT NULL , - END_TIME DATETIME DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(20) , - EXIT_MESSAGE VARCHAR(2500) , - LAST_UPDATED DATETIME, - constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) - references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) -) ENGINE=InnoDB; +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_NAME VARCHAR(100) NOT NULL, + JOB_KEY VARCHAR(32) NOT NULL, + constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) +) ENGINE=InnoDB; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, - SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , - constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) - references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) -) ENGINE=InnoDB; +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT , + JOB_INSTANCE_ID BIGINT NOT NULL, + CREATE_TIME DATETIME NOT NULL, + START_TIME DATETIME DEFAULT NULL , + END_TIME DATETIME DEFAULT NULL , + STATUS VARCHAR(10) , + EXIT_CODE VARCHAR(20) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME, + constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) + references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_PARAMS ( + JOB_INSTANCE_ID BIGINT NOT NULL , + TYPE_CD VARCHAR(6) NOT NULL , + KEY_NAME VARCHAR(100) NOT NULL , + STRING_VAL VARCHAR(250) , + DATE_VAL DATETIME DEFAULT NULL , + LONG_VAL BIGINT , + DOUBLE_VAL DOUBLE PRECISION , + constraint JOB_INST_PARAMS_FK foreign key (JOB_INSTANCE_ID) + references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , + VERSION BIGINT NOT NULL, + STEP_NAME VARCHAR(100) NOT NULL, + JOB_EXECUTION_ID BIGINT NOT NULL, + START_TIME DATETIME NOT NULL , + END_TIME DATETIME DEFAULT NULL , + STATUS VARCHAR(10) , + COMMIT_COUNT BIGINT , + READ_COUNT BIGINT , + FILTER_COUNT BIGINT , + WRITE_COUNT BIGINT , + READ_SKIP_COUNT BIGINT , + WRITE_SKIP_COUNT BIGINT , + PROCESS_SKIP_COUNT BIGINT , + ROLLBACK_COUNT BIGINT , + EXIT_CODE VARCHAR(20) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED DATETIME, + constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, - SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , - constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) - references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) -) ENGINE=InnoDB; +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) + references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) +) ENGINE=InnoDB; + +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT , + constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) + references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) +) ENGINE=InnoDB; CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) ENGINE=MYISAM; INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java index 6d24492f7..55a53ab1c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java @@ -1,19 +1,61 @@ package org.springframework.batch.core.job; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Before; import org.junit.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInterruptedException; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.core.step.StepSupport; /** * Test suite for various failure scenarios during job processing. * * @author Lucas Ward - * + * @author Dave Syer + * */ public class SimpleJobFailureTests { - - @Test - public void testStepFailure(){ - - + private SimpleJob job = new SimpleJob("job"); + + private JobExecution execution; + + @Before + public void init() throws Exception { + JobRepository jobRepository = new MapJobRepositoryFactoryBean().getJobRepository(); + job.setJobRepository(jobRepository); + execution = jobRepository.createJobExecution("job", new JobParameters()); } + + @Test + public void testStepFailure() throws Exception { + job.setSteps(Arrays. asList(new StepSupport("step"))); + job.execute(execution); + assertEquals(BatchStatus.FAILED, execution.getStatus()); + } + + @Test + public void testStepStatusUnknown() throws Exception { + job.setSteps(Arrays. asList(new StepSupport("step") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + // This is what happens if the repository meta-data cannot be updated + stepExecution.setStatus(BatchStatus.UNKNOWN); + } + })); + job.execute(execution); + assertEquals(BatchStatus.UNKNOWN, execution.getStatus()); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java new file mode 100644 index 000000000..5f93182fb --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java @@ -0,0 +1,86 @@ +package org.springframework.batch.core.job.flow; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +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.JobInterruptedException; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.flow.support.SimpleFlow; +import org.springframework.batch.core.job.flow.support.StateTransition; +import org.springframework.batch.core.job.flow.support.state.EndState; +import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.core.step.StepSupport; + +/** + * Test suite for various failure scenarios during job processing. + * + * @author Lucas Ward + * @author Dave Syer + * + */ +public class FlowJobFailureTests { + + private FlowJob job = new FlowJob(); + + private JobExecution execution; + + @Before + public void init() throws Exception { + JobRepository jobRepository = new MapJobRepositoryFactoryBean().getJobRepository(); + job.setJobRepository(jobRepository); + execution = jobRepository.createJobExecution("job", new JobParameters()); + } + + @Test + public void testStepFailure() throws Exception { + SimpleFlow flow = new SimpleFlow("job"); + List transitions = new ArrayList(); + StepState step = new StepState(new StepSupport("step")); + transitions.add(StateTransition.createStateTransition(step, ExitStatus.FAILED.getExitCode(), "end0")); + transitions.add(StateTransition.createStateTransition(step, ExitStatus.COMPLETED.getExitCode(), "end1")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0"))); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1"))); + flow.setStateTransitions(transitions); + job.setFlow(flow); + job.afterPropertiesSet(); + job.execute(execution); + assertEquals(BatchStatus.FAILED, execution.getStatus()); + } + + @Test + public void testStepStatusUnknown() throws Exception { + SimpleFlow flow = new SimpleFlow("job"); + List transitions = new ArrayList(); + StepState step = new StepState(new StepSupport("step") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException, + UnexpectedJobExecutionException { + // This is what happens if the repository meta-data cannot be + // updated + stepExecution.setExitStatus(ExitStatus.UNKNOWN); + stepExecution.setStatus(BatchStatus.UNKNOWN); + } + }); + transitions.add(StateTransition.createStateTransition(step, ExitStatus.FAILED.getExitCode(), "end0")); + transitions.add(StateTransition.createStateTransition(step, "*", "end1")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.FAILED, "end0"))); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1"))); + flow.setStateTransitions(transitions); + job.setFlow(flow); + job.afterPropertiesSet(); + job.execute(execution); + assertEquals(BatchStatus.UNKNOWN, execution.getStatus()); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java index 3c7336260..61849125d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -222,7 +222,7 @@ public class TaskletStepExceptionTests { } @Test - public void testRepositoryError() throws Exception { + public void testRepositoryErrorOnExecutionContext() throws Exception { taskletStep.setTasklet(new Tasklet() { @@ -232,7 +232,7 @@ public class TaskletStepExceptionTests { }); - jobRepository.setFailOnUpdate(true); + jobRepository.setFailOnUpdateExecutionContext(true); taskletStep.execute(stepExecution); assertEquals(UNKNOWN, stepExecution.getStatus()); Throwable e = stepExecution.getFailureExceptions().get(0); @@ -240,6 +240,25 @@ public class TaskletStepExceptionTests { } + @Test + public void testRepositoryErrorOnUpdateStepExecution() throws Exception { + + taskletStep.setTasklet(new Tasklet() { + + public RepeatStatus execute(StepContribution contribution, ChunkContext attributes) throws Exception { + return RepeatStatus.FINISHED; + } + + }); + + jobRepository.setFailOnUpdateStepExecution(1); + taskletStep.execute(stepExecution); + assertEquals(UNKNOWN, stepExecution.getStatus()); + Throwable e = stepExecution.getFailureExceptions().get(0); + assertEquals("Expected exception in step execution persistence", e.getMessage()); + + } + @Test public void testRepositoryErrorOnFailure() throws Exception { @@ -251,7 +270,7 @@ public class TaskletStepExceptionTests { }); - jobRepository.setFailOnUpdate(true); + jobRepository.setFailOnUpdateExecutionContext(true); taskletStep.execute(stepExecution); assertEquals(UNKNOWN, stepExecution.getStatus()); Throwable e = stepExecution.getFailureExceptions().get(0); @@ -301,10 +320,16 @@ public class TaskletStepExceptionTests { private int updateCount = 0; - private boolean failOnUpdate = false; + private boolean failOnUpdateContext = false; - public void setFailOnUpdate(boolean failOnUpdate) { - this.failOnUpdate = failOnUpdate; + private int failOnUpdateExecution = -1; + + public void setFailOnUpdateExecutionContext(boolean failOnUpdate) { + this.failOnUpdateContext = failOnUpdate; + } + + public void setFailOnUpdateStepExecution(int failOnUpdate) { + this.failOnUpdateExecution = failOnUpdate; } public void add(StepExecution stepExecution) { @@ -331,11 +356,14 @@ public class TaskletStepExceptionTests { } public void update(StepExecution stepExecution) { + if (updateCount==failOnUpdateExecution) { + throw new RuntimeException("Expected exception in step execution persistence"); + } updateCount++; } public void updateExecutionContext(StepExecution stepExecution) { - if (failOnUpdate) { + if (failOnUpdateContext) { throw new RuntimeException("Expected exception in step execution context persistence"); } } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/PartitionStepParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/PartitionStepParserTests-context.xml index de8eb241f..114514d5a 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/PartitionStepParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/PartitionStepParserTests-context.xml @@ -29,7 +29,7 @@ + class="org.springframework.core.task.SyncTaskExecutor" />