diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java new file mode 100644 index 000000000..2afba836d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006-2007 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.launch; + +import org.springframework.batch.core.JobExecutionException; + +/** + * Checked exception to indicate that user asked for a job execution to be + * aborted when hasn't been stopped. + * + * @author Dave Syer + * + */ +public class JobExecutionNotStoppedException extends JobExecutionException { + + /** + * Create an exception with the given message. + */ + public JobExecutionNotStoppedException(String msg) { + super(msg); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java index ab9f178e6..803d77f05 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java @@ -39,8 +39,11 @@ import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobExecutionNotFailedException; +import org.springframework.batch.core.launch.JobExecutionNotRunningException; +import org.springframework.batch.core.launch.JobExecutionNotStoppedException; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobParametersNotFoundException; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; @@ -98,9 +101,12 @@ import org.springframework.util.StringUtils; * *

@@ -160,6 +166,8 @@ public class CommandLineJobRunner { private JobExplorer jobExplorer; + private JobRepository jobRepository; + /** * Injection setter for the {@link JobLauncher}. * @@ -169,6 +177,13 @@ public class CommandLineJobRunner { this.launcher = launcher; } + /** + * @param jobRepository the jobRepository to set + */ + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; + } + /** * Injection setter for {@link JobExplorer}. * @@ -273,10 +288,35 @@ public class CommandLineJobRunner { "Invalid JobParameters " + Arrays.asList(parameters) + ". If parameters are provided they should be in the form name=value (no whitespace)."); + if (opts.contains("-stop")) { + List jobExecutions = getRunningJobExecutions(jobIdentifier); + if (jobExecutions == null) { + throw new JobExecutionNotRunningException("No running execution found for job=" + jobIdentifier); + } + for (JobExecution jobExecution : jobExecutions) { + jobExecution.setStatus(BatchStatus.STOPPING); + jobRepository.update(jobExecution); + } + return exitCodeMapper.intValue(ExitStatus.COMPLETED.getExitCode()); + } + + if (opts.contains("-abandon")) { + List jobExecutions = getStoppedJobExecutions(jobIdentifier); + if (jobExecutions == null) { + throw new JobExecutionNotStoppedException("No stopped execution found for job=" + jobIdentifier); + } + for (JobExecution jobExecution : jobExecutions) { + jobExecution.setStatus(BatchStatus.ABANDONED); + jobRepository.update(jobExecution); + } + return exitCodeMapper.intValue(ExitStatus.COMPLETED.getExitCode()); + } + if (opts.contains("-restart")) { JobExecution jobExecution = getLastFailedJobExecution(jobIdentifier); if (jobExecution == null) { - throw new JobExecutionNotFailedException("No failed or stopped execution found for job=" + jobIdentifier); + throw new JobExecutionNotFailedException("No failed or stopped execution found for job=" + + jobIdentifier); } jobParameters = jobExecution.getJobInstance().getJobParameters(); jobName = jobExecution.getJobInstance().getJobName(); @@ -315,22 +355,23 @@ public class CommandLineJobRunner { } /** - * @param jobIdentifier + * @param jobIdentifier a job execution id or job name + * @param minStatus the highest status to exclude from the result * @return - * @throws JobParametersNotFoundException */ - private JobExecution getLastFailedJobExecution(String jobIdentifier) { + private List getJobExecutionsWithStatusGreaterThan(String jobIdentifier, BatchStatus minStatus) { Long executionId = getLongIdentifier(jobIdentifier); if (executionId != null) { JobExecution jobExecution = jobExplorer.getJobExecution(executionId); - if (jobExecution.getStatus().isGreaterThan(BatchStatus.STOPPING)) { - return jobExecution; + if (jobExecution.getStatus().isGreaterThan(minStatus)) { + return Arrays.asList(jobExecution); } } int start = 0; int count = 100; + List executions = new ArrayList(); List lastInstances = jobExplorer.getJobInstances(jobIdentifier, start, count); while (!lastInstances.isEmpty()) { @@ -341,8 +382,8 @@ public class CommandLineJobRunner { continue; } JobExecution jobExecution = jobExecutions.get(jobExecutions.size() - 1); - if (jobExecution.getStatus().isGreaterThan(BatchStatus.STOPPING)) { - return jobExecution; + if (jobExecution.getStatus().isGreaterThan(minStatus)) { + executions.add(jobExecution); } } @@ -351,10 +392,46 @@ public class CommandLineJobRunner { } - return null; + return executions; } + private JobExecution getLastFailedJobExecution(String jobIdentifier) { + List jobExecutions = getJobExecutionsWithStatusGreaterThan(jobIdentifier, BatchStatus.STOPPING); + if (jobExecutions.isEmpty()) { + return null; + } + return jobExecutions.get(0); + } + + private List getStoppedJobExecutions(String jobIdentifier) { + List jobExecutions = getJobExecutionsWithStatusGreaterThan(jobIdentifier, BatchStatus.STARTED); + if (jobExecutions.isEmpty()) { + return null; + } + List result = new ArrayList(); + for (JobExecution jobExecution : jobExecutions) { + if (jobExecution.getStatus() != BatchStatus.ABANDONED) { + result.add(jobExecution); + } + } + return result.isEmpty() ? null : result; + } + + private List getRunningJobExecutions(String jobIdentifier) { + List jobExecutions = getJobExecutionsWithStatusGreaterThan(jobIdentifier, BatchStatus.COMPLETED); + if (jobExecutions.isEmpty()) { + return null; + } + List result = new ArrayList(); + for (JobExecution jobExecution : jobExecutions) { + if (jobExecution.isRunning()) { + result.add(jobExecution); + } + } + return result.isEmpty() ? null : result; + } + private Long getLongIdentifier(String jobIdentifier) { try { return new Long(jobIdentifier); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotRunningExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotRunningExceptionTests.java index 11be030b6..29a732165 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotRunningExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotRunningExceptionTests.java @@ -27,7 +27,7 @@ public class JobExecutionNotRunningExceptionTests { @Test public void testExceptionString() throws Exception { - Exception exception = new JobExecutionNotFailedException("foo"); + Exception exception = new JobExecutionNotRunningException("foo"); assertEquals("foo", exception.getMessage()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotStoppedExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotStoppedExceptionTests.java new file mode 100644 index 000000000..db6e99f1d --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobExecutionNotStoppedExceptionTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006-2007 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.launch; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * @author Dave Syer + * + */ +public class JobExecutionNotStoppedExceptionTests { + + @Test + public void testExceptionString() throws Exception { + Exception exception = new JobExecutionNotStoppedException("foo"); + assertEquals("foo", exception.getMessage()); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java index 684610cad..26594e976 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java @@ -41,6 +41,7 @@ import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.util.ClassUtils; /** @@ -126,8 +127,52 @@ public class CommandLineJobRunnerTests { CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); String errorMessage = CommandLineJobRunner.getErrorMessage(); - assertTrue("Wrong error message: " + errorMessage, errorMessage - .contains("in the form name=value")); + assertTrue("Wrong error message: " + errorMessage, errorMessage.contains("in the form name=value")); + } + + @Test + public void testStop() throws Throwable { + String[] args = new String[] { jobPath, "-stop", jobName }; + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(3L, jobParameters, jobName)); + CommandLineJobRunner.main(args); + assertEquals(0, StubSystemExiter.status); + } + + @Test + public void testStopFailed() throws Throwable { + String[] args = new String[] { jobPath, "-stop", jobName }; + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(0L, jobParameters, jobName)); + CommandLineJobRunner.main(args); + assertEquals(1, StubSystemExiter.status); + } + + @Test + public void testAbandon() throws Throwable { + String[] args = new String[] { jobPath, "-abandon", jobName }; + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(2L, jobParameters, jobName)); + CommandLineJobRunner.main(args); + assertEquals(0, StubSystemExiter.status); + } + + @Test + public void testAbandonRunning() throws Throwable { + String[] args = new String[] { jobPath, "-abandon", jobName }; + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(3L, jobParameters, jobName)); + CommandLineJobRunner.main(args); + assertEquals(1, StubSystemExiter.status); + } + + @Test + public void testAbandonAbandoned() throws Throwable { + String[] args = new String[] { jobPath, "-abandon", jobName }; + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(4L, jobParameters, jobName)); + CommandLineJobRunner.main(args); + assertEquals(1, StubSystemExiter.status); } @Test @@ -156,7 +201,7 @@ public class CommandLineJobRunnerTests { public void testRestartNotFailed() throws Throwable { String[] args = new String[] { jobPath, "-restart", jobName }; JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(2L, jobParameters, jobName)); + StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(123L, jobParameters, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); String errorMessage = CommandLineJobRunner.getErrorMessage(); @@ -253,10 +298,14 @@ public class CommandLineJobRunnerTests { destroyed = false; } } + + public static class StubJobRepository extends JobRepositorySupport { + } public static class StubJobExplorer implements JobExplorer { static List jobInstances = new ArrayList(); + static JobExecution jobExecution; public Set findRunningJobExecutions(String jobName) { @@ -264,7 +313,7 @@ public class CommandLineJobRunnerTests { } public JobExecution getJobExecution(Long executionId) { - if (jobExecution!=null) { + if (jobExecution != null) { return jobExecution; } throw new UnsupportedOperationException(); @@ -277,6 +326,15 @@ public class CommandLineJobRunnerTests { if (jobInstance.getId() == 1) { return null; } + if (jobInstance.getId() == 2) { + return Arrays.asList(createJobInstance(jobInstance, BatchStatus.STOPPED)); + } + if (jobInstance.getId() == 3) { + return Arrays.asList(createJobInstance(jobInstance, BatchStatus.STARTED)); + } + if (jobInstance.getId() == 4) { + return Arrays.asList(createJobInstance(jobInstance, BatchStatus.ABANDONED)); + } return Arrays.asList(createJobInstance(jobInstance, BatchStatus.COMPLETED)); } @@ -284,7 +342,9 @@ public class CommandLineJobRunnerTests { JobExecution jobExecution = new JobExecution(jobInstance, 1L); jobExecution.setStatus(status); jobExecution.setStartTime(new Date()); - jobExecution.setEndTime(new Date()); + if (status != BatchStatus.STARTED) { + jobExecution.setEndTime(new Date()); + } return jobExecution; } @@ -304,7 +364,7 @@ public class CommandLineJobRunnerTests { public StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId) { throw new UnsupportedOperationException(); } - + public List getJobNames() { throw new UnsupportedOperationException(); } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml index 8f8372932..08b9811ec 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml @@ -7,9 +7,11 @@ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> - + +