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 13f1b333f..775467152 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 @@ -254,7 +254,7 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In // The job was already stopped before we even got this far. Deal // with it in the same way as any other interruption. - execution.setStatus(BatchStatus.FAILED); + execution.setStatus(BatchStatus.STOPPED); execution.setExitStatus(ExitStatus.COMPLETED); } @@ -346,7 +346,7 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In jobRepository.updateExecutionContext(execution); - if (currentStepExecution.getStatus() == BatchStatus.STOPPING) { + if (currentStepExecution.getStatus() == BatchStatus.STOPPING || currentStepExecution.getStatus() == BatchStatus.STOPPED) { throw new JobInterruptedException("Job interrupted by step execution"); } 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 869b4f229..eaea2ec25 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 @@ -341,26 +341,13 @@ public class SimpleJobTests { "no steps configured") >= 0); } - // public void testNoStepsExecuted() throws Exception { - // StepExecution completedExecution = new - // StepExecution("completedExecution", jobExecution); - // completedExecution.setStatus(BatchStatus.COMPLETED); - // - // job.execute(jobExecution); - // ExitStatus exitStatus = jobExecution.getExitStatus(); - // assertEquals(ExitStatus.NOOP.getExitCode(), exitStatus.getExitCode()); - // assertTrue("Wrong message in execution: " + exitStatus, - // exitStatus.getExitDescription().contains( - // "steps already completed")); - // } - @Test public void testNotExecutedIfAlreadyStopped() throws Exception { jobExecution.stop(); job.execute(jobExecution); assertEquals(0, list.size()); - checkRepository(BatchStatus.FAILED, ExitStatus.NOOP); + checkRepository(BatchStatus.STOPPED, ExitStatus.NOOP); ExitStatus exitStatus = jobExecution.getExitStatus(); assertEquals(ExitStatus.NOOP.getExitCode(), exitStatus.getExitCode()); } @@ -454,7 +441,7 @@ public class SimpleJobTests { assertNull("Second step was not supposed to be executed", step2.passedInStepContext); } - + @Test public void testGetStepExists() { step1 = new StubStep("step1", jobRepository); @@ -546,7 +533,7 @@ public class SimpleJobTests { stepExecution.setExitStatus(ExitStatus.FAILED); stepExecution.setStatus(BatchStatus.FAILED); stepExecution.addFailureException(exception); - throw (JobInterruptedException)exception; + throw (JobInterruptedException) exception; } if (exception instanceof RuntimeException) { stepExecution.setExitStatus(ExitStatus.FAILED); 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 8f3875c9b..721b51eac 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 @@ -28,6 +28,7 @@ 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.JobInterruptedException; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; @@ -38,7 +39,13 @@ import org.springframework.batch.core.job.flow.support.state.DecisionState; 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.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.MapExecutionContextDao; +import org.springframework.batch.core.repository.dao.MapJobExecutionDao; +import org.springframework.batch.core.repository.dao.MapJobInstanceDao; +import org.springframework.batch.core.repository.dao.MapStepExecutionDao; import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.SimpleJobRepository; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; @@ -53,15 +60,19 @@ public class FlowJobTests { private JobExecution jobExecution; private JobRepository jobRepository; - + private boolean fail = false; + private JobExecutionDao jobExecutionDao; + @Before public void setUp() throws Exception { MapJobRepositoryFactoryBean.clear(); MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(); factory.setTransactionManager(new ResourcelessTransactionManager()); factory.afterPropertiesSet(); + jobExecutionDao = new MapJobExecutionDao(); + jobRepository = new SimpleJobRepository(new MapJobInstanceDao(), jobExecutionDao, new MapStepExecutionDao(), new MapExecutionContextDao()); jobRepository = (JobRepository) factory.getObject(); job.setJobRepository(jobRepository); jobExecution = jobRepository.createJobExecution("job", new JobParameters()); @@ -181,6 +192,29 @@ public class FlowJobTests { assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); } + @Test + public void testInterrupted() throws Exception { + SimpleFlow flow = new SimpleFlow("job"); + List transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1") { + @Override + public void execute(StepExecution stepExecution) throws JobInterruptedException { + stepExecution.setStatus(BatchStatus.STOPPING); + jobRepository.update(stepExecution); + } + }), "end0")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0"))); + flow.setStateTransitions(transitions); + flow.afterPropertiesSet(); + job.setFlow(flow); + job.afterPropertiesSet(); + job.execute(jobExecution); + assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); + checkRepository(BatchStatus.STOPPED, ExitStatus.STOPPED); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(JobInterruptedException.class, jobExecution.getFailureExceptions().get(0).getClass()); + } + @Test public void testEndStateStopped() throws Exception { SimpleFlow flow = new SimpleFlow("job"); @@ -418,4 +452,15 @@ public class FlowJobTests { return null; } + private void checkRepository(BatchStatus status, ExitStatus exitStatus) { + // because map dao stores in memory, it can be checked directly + JobInstance jobInstance = jobExecution.getJobInstance(); + JobExecution other = (JobExecution) jobExecutionDao.findJobExecutions(jobInstance).get(0); + assertEquals(jobInstance.getId(), other.getJobId()); + assertEquals(status, other.getStatus()); + if (exitStatus != null) { + assertEquals(exitStatus.getExitCode(), other.getExitStatus().getExitCode()); + } + } + } diff --git a/spring-batch-infrastructure/src/main/resources/META-INF/MANIFEST.MF b/spring-batch-infrastructure/src/main/resources/META-INF/MANIFEST.MF index 473a33311..c20e3dc09 100644 --- a/spring-batch-infrastructure/src/main/resources/META-INF/MANIFEST.MF +++ b/spring-batch-infrastructure/src/main/resources/META-INF/MANIFEST.MF @@ -115,4 +115,3 @@ Export-Package: org.springframework.batch.item;version="0",org.springf support",org.springframework.batch.support.transaction;version="0";us es:="org.aopalliance.intercept,org.springframework.transaction,org.sp ringframework.transaction.support" - diff --git a/spring-batch-samples/src/main/resources/data-source-context-init.xml b/spring-batch-samples/src/main/resources/data-source-context-init.xml index d6103fd9c..f38b88e12 100644 --- a/spring-batch-samples/src/main/resources/data-source-context-init.xml +++ b/spring-batch-samples/src/main/resources/data-source-context-init.xml @@ -7,6 +7,7 @@ + ${batch.drop.script} ${batch.schema.script} ${batch.business.schema.script} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java index bf97d09e2..6f1a0b3e1 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java @@ -16,6 +16,7 @@ package org.springframework.batch.sample.launch; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -83,7 +84,7 @@ public class RemoteLauncherTests { assertTrue(launcher.getJobNames().contains("loopJob")); } - //@Test + @Test public void testPauseJob() throws Exception { final int SLEEP_INTERVAL = 600; assertTrue(isConnected()); @@ -100,18 +101,19 @@ public class RemoteLauncherTests { // assertEquals(0, launcher.getRunningExecutions("loopJob").size()); logger.debug(launcher.getSummary(executionId)); long resumedId = launcher.restart(executionId); - assertEquals("Picked up the same execution after pause and resume", executionId, resumedId); + assertNotSame("Picked up the same execution after pause and resume", executionId, resumedId); Thread.sleep(SLEEP_INTERVAL); - launcher.stop(executionId); + launcher.stop(resumedId); Thread.sleep(SLEEP_INTERVAL); // assertEquals(0, launcher.getRunningExecutions("loopJob").size()); - logger.debug(launcher.getSummary(executionId)); - long resumeId2 = launcher.restart(executionId); - assertEquals("Picked up the same execution after pause and resume", executionId, resumeId2); + logger.debug(launcher.getSummary(resumedId)); + long resumeId2 = launcher.restart(resumedId); + assertNotSame("Picked up the same execution after pause and resume", executionId, resumeId2); - launcher.stop(executionId); + Thread.sleep(SLEEP_INTERVAL); + launcher.stop(resumeId2); } diff --git a/src/site/apt/migration/2.0-rc1-rc2.apt b/src/site/apt/migration/2.0-rc1-rc2.apt new file mode 100644 index 000000000..da3cf5b40 --- /dev/null +++ b/src/site/apt/migration/2.0-rc1-rc2.apt @@ -0,0 +1,152 @@ +Spring Batch 2.0.0.RC2 Release Notes + +* Issues + + * {{{http://jira.springframework.org/browse/BATCH-1165}BATCH-1165}} - Allow 'id' and 'ref' to exist together on <*-listener/> + + * {{{http://jira.springframework.org/browse/BATCH-1164}BATCH-1164}} - Putting scope="step" on a listener causes failure + + * {{{http://jira.springframework.org/browse/BATCH-1163}BATCH-1163}} - In Batch xsd, elements in should be unordered + + * {{{http://jira.springframework.org/browse/BATCH-1162}BATCH-1162}} - In Batch xsd, elements in should be unordered + + * {{{http://jira.springframework.org/browse/BATCH-1161}BATCH-1161}} - Throw error if a flow has no steps + + * {{{http://jira.springframework.org/browse/BATCH-1160}BATCH-1160}} - In Batch xsd, stepType and flowStepType should be unordered + + * {{{http://jira.springframework.org/browse/BATCH-1159}BATCH-1159}} - PROCESS_SKIP_COUNT seems not to ever get written to database + + * {{{http://jira.springframework.org/browse/BATCH-1157}BATCH-1157}} - Add max count parameter to counting item readers + + * {{{http://jira.springframework.org/browse/BATCH-1154}BATCH-1154}} - TaskletElementParser can't predict which StepFactoryBean to use + + * {{{http://jira.springframework.org/browse/BATCH-1153}BATCH-1153}} - listener element should only have an "id" when defined at the top level + + * {{{http://jira.springframework.org/browse/BATCH-1152}BATCH-1152}} - Allow comma and newline as delimiters in exception lists in namespace + + * {{{http://jira.springframework.org/browse/BATCH-1151}BATCH-1151}} - Add IDE support to in xsd + + * {{{http://jira.springframework.org/browse/BATCH-1147}BATCH-1147}} - StepExecution getFilterCount always return 0 + + * {{{http://jira.springframework.org/browse/BATCH-1145}BATCH-1145}} - Conflicts if both and have a "parent" attribute + + * {{{http://jira.springframework.org/browse/BATCH-1144}BATCH-1144}} - Create top-level element + + * {{{http://jira.springframework.org/browse/BATCH-1143}BATCH-1143}} - Standalone should not be allowed to have "tasklet" attribute and together + + * {{{http://jira.springframework.org/browse/BATCH-1142}BATCH-1142}} - In the xsd, should be moved from "flowType" to "job" + + * {{{http://jira.springframework.org/browse/BATCH-1141}BATCH-1141}} - Rename CompositeExecutionJobListener to CompositeJobExecutionListener + + * {{{http://jira.springframework.org/browse/BATCH-1139}BATCH-1139}} - Add "parent" attribute to + + * {{{http://jira.springframework.org/browse/BATCH-1138}BATCH-1138}} - Add "parent" attribute to + + * {{{http://jira.springframework.org/browse/BATCH-1136}BATCH-1136}} - Add setListeners(StepExecutionListener[]) to TaskletStep + + * {{{http://jira.springframework.org/browse/BATCH-1135}BATCH-1135}} - Create top-level element + + * {{{http://jira.springframework.org/browse/BATCH-1134}BATCH-1134}} - Create a superclass for StepListenerParser and JobExecutionListenerParser + + * {{{http://jira.springframework.org/browse/BATCH-1132}BATCH-1132}} - Add "parent" attribute to + + * {{{http://jira.springframework.org/browse/BATCH-1131}BATCH-1131}} - It is not possible to set transaction-attributes for a tasklet step + + * {{{http://jira.springframework.org/browse/BATCH-1130}BATCH-1130}} - Ensure Ordered is respected by generated listeners + + * {{{http://jira.springframework.org/browse/BATCH-1129}BATCH-1129}} - Problems with exception classifications + + * {{{http://jira.springframework.org/browse/BATCH-1128}BATCH-1128}} - Make sure WRITE_COUNT and ROLLBACK_COUNT are being updated correctly + + * {{{http://jira.springframework.org/browse/BATCH-1127}BATCH-1127}} - Add contextual line number information for exceptions thrown by FlatFileItemReader + + * {{{http://jira.springframework.org/browse/BATCH-1126}BATCH-1126}} - StepScope does not apply to twice nested inner beans + + * {{{http://jira.springframework.org/browse/BATCH-1125}BATCH-1125}} - NoWorkFoundStepExecutionListener doesn't fail the step + + * {{{http://jira.springframework.org/browse/BATCH-1124}BATCH-1124}} - Fix error message that occurs when the same annotation is used twice on one method + + * {{{http://jira.springframework.org/browse/BATCH-1123}BATCH-1123}} - ExecutionContextPromotionListener may perform promotion multiple times + + * {{{http://jira.springframework.org/browse/BATCH-1122}BATCH-1122}} - StaxEventWriter.startDocument() needs to be protected + + * {{{http://jira.springframework.org/browse/BATCH-1121}BATCH-1121}} - Documentation should cover the 'no work found' scenario + + * {{{http://jira.springframework.org/browse/BATCH-1120}BATCH-1120}} - Allow completion policy to be set on step + + * {{{http://jira.springframework.org/browse/BATCH-1119}BATCH-1119}} - afterWrite() will only be called if an exception is raised during throttling + + * {{{http://jira.springframework.org/browse/BATCH-1118}BATCH-1118}} - Listener annotation with wrong signature fails too late and too quietly + + * {{{http://jira.springframework.org/browse/BATCH-1117}BATCH-1117}} - onWriteError should be called with only the bad item + + * {{{http://jira.springframework.org/browse/BATCH-1116}BATCH-1116}} - Create section on MetaDataInstanceFactory in testing chapter + + * {{{http://jira.springframework.org/browse/BATCH-1113}BATCH-1113}} - query for JdbcJobExecutionDao.findRunningJobExecutions is broken + + * {{{http://jira.springframework.org/browse/BATCH-1112}BATCH-1112}} - Remove cycle in infrastructure database/support + + * {{{http://jira.springframework.org/browse/BATCH-1111}BATCH-1111}} - ChunkListener called before WriteListener + + * {{{http://jira.springframework.org/browse/BATCH-1110}BATCH-1110}} - Defer EntityManager flushing and clearing in JpaPagingItemReader + + * {{{http://jira.springframework.org/browse/BATCH-1109}BATCH-1109}} - Generalise PrefixMatching* to PatternMatching* + + * {{{http://jira.springframework.org/browse/BATCH-1108}BATCH-1108}} - Add composite ItemWriter/Processor based on Classifier + + * {{{http://jira.springframework.org/browse/BATCH-1107}BATCH-1107}} - Fix Date conversion in PlaceholderTargetSource + + * {{{http://jira.springframework.org/browse/BATCH-1106}BATCH-1106}} - SqlPagingQueryProviderFactoryBean ascending should default to true + + * {{{http://jira.springframework.org/browse/BATCH-1105}BATCH-1105}} - Namespace error in documentation + + * {{{http://jira.springframework.org/browse/BATCH-1104}BATCH-1104}} - Wrong stax version in spring-batch-parent-2.0.0.RC1.pom, is 1.2 should be 1.2.0 + + * {{{http://jira.springframework.org/browse/BATCH-1103}BATCH-1103}} - Add partioning sample + + * {{{http://jira.springframework.org/browse/BATCH-1102}BATCH-1102}} - Classes with "listener" annotations should be auto-registered + + * {{{http://jira.springframework.org/browse/BATCH-1101}BATCH-1101}} - Remove StepScope bean definition from samples that don't need it + + * {{{http://jira.springframework.org/browse/BATCH-1099}BATCH-1099}} - Make writer in skip sample stateful + + * {{{http://jira.springframework.org/browse/BATCH-1098}BATCH-1098}} - Check @AfterWrite is only called once per item + + * {{{http://jira.springframework.org/browse/BATCH-1097}BATCH-1097}} - Add late binding to some io samples + + * {{{http://jira.springframework.org/browse/BATCH-1096}BATCH-1096}} - Add late binding to some io samples + + * {{{http://jira.springframework.org/browse/BATCH-1093}BATCH-1093}} - Make AbstractJobTests.makeUniqueJobParameters() public + + * {{{http://jira.springframework.org/browse/BATCH-1092}BATCH-1092}} - Fix naming conventions for exit status in + + * {{{http://jira.springframework.org/browse/BATCH-1091}BATCH-1091}} - Add strict flag to file readers (flat and XML). + + * {{{http://jira.springframework.org/browse/BATCH-1072}BATCH-1072}} - Update docs to reflect attribute change in step element + + * {{{http://jira.springframework.org/browse/BATCH-1065}BATCH-1065}} - "BATCH-1011 + + * {{{http://jira.springframework.org/browse/Update documentation to explain end/fail/pause transitions" + * {{{http://jira.springframework.org/browse/BATCH-976}BATCH-976}} - Provide automatic batch database schema installation utility + + * {{{http://jira.springframework.org/browse/BATCH-937}BATCH-937}} - Make sure JobRepository can be proxied + + * {{{http://jira.springframework.org/browse/BATCH-935}BATCH-935}} - Use NumberFormat when parsing real numbers + + * {{{http://jira.springframework.org/browse/BATCH-918}BATCH-918}} - Duplicate jar files in *-with-dependencies.zip + + * {{{http://jira.springframework.org/browse/BATCH-902}BATCH-902}} - Revise FAQ on web site + + * {{{http://jira.springframework.org/browse/BATCH-794}BATCH-794}} - Align with SpringSource Enterprise Repository + + * {{{http://jira.springframework.org/browse/BATCH-632}BATCH-632}} - Move Tasklet interface from core to infrastructure + + * {{{http://jira.springframework.org/browse/BATCH-573}BATCH-573}} - Delegating streams and listeners (e.g. HibernateAwareItemWriter) should delegate those interfaces to their delegates. + + * {{{http://jira.springframework.org/browse/BATCH-501}BATCH-501}} - Core and Infrastructure still have circular dependency + + * {{{http://jira.springframework.org/browse/BATCH-470}BATCH-470}} - RFC: Can all ExceptionHandlers be replaced by a combination of making Repeat/RetryListeners more robust and rewriting as Listeners? + + * {{{http://jira.springframework.org/browse/BATCH-394}BATCH-394}} - If FieldSet is an interface it needs a factory, otherwise existing clients are tied to specific implementations + + * {{{http://jira.springframework.org/browse/BATCH-22}BATCH-22}} - Find alternative to ThreadLocal for RepeatSynchronizationManager