Tweak the JobOperator slightly and add Javadocs.

This commit is contained in:
dsyer
2008-12-31 16:23:11 +00:00
parent d8c3020176
commit bd3ab51c60
11 changed files with 230 additions and 43 deletions

View File

@@ -29,11 +29,15 @@ import org.springframework.batch.core.StepExecution;
public interface JobExplorer {
/**
* Fetch {@link JobInstance} values in descending order of creation (and
* therefore usually of first execution).
*
* @param jobName the name of the job to query
* @param start the start index of the instances to return
* @param count the maximum number of instances to return
* @return the latest {@link JobInstance} values up to a maximum of count values
* @return the {@link JobInstance} values up to a maximum of count values
*/
List<JobInstance> getLastJobInstances(String jobName, int count);
List<JobInstance> getJobInstances(String jobName, int start, int count);
/**
* @param executionId the job execution id
@@ -43,7 +47,8 @@ public interface JobExplorer {
/**
* @param jobExecutionId the parent job execution id
* @param stepName the step name identifier for the required {@link StepExecution}
* @param stepName the step name identifier for the required
* {@link StepExecution}
* @return the {@link StepExecution} with this id, or null if not found
*/
StepExecution getStepExecution(Long jobExecutionId, String stepName);
@@ -58,7 +63,7 @@ public interface JobExplorer {
* @param jobInstance the {@link JobInstance} to query
* @return the set of all executions for the specified {@link JobInstance}
*/
List<JobExecution> findJobExecutions(JobInstance jobInstance);
List<JobExecution> getJobExecutions(JobInstance jobInstance);
/**
* @param jobName the name of the job

View File

@@ -67,7 +67,7 @@ public class SimpleJobExplorer implements JobExplorer {
/* (non-Javadoc)
* @see org.springframework.batch.core.explore.JobExplorer#findJobExecutions(org.springframework.batch.core.JobInstance)
*/
public List<JobExecution> findJobExecutions(JobInstance jobInstance) {
public List<JobExecution> getJobExecutions(JobInstance jobInstance) {
List<JobExecution> executions = jobExecutionDao.findJobExecutions(jobInstance);
for(JobExecution jobExecution:executions){
getJobExecutionDependencies(jobExecution);
@@ -119,7 +119,7 @@ public class SimpleJobExplorer implements JobExplorer {
/* (non-Javadoc)
* @see org.springframework.batch.core.explore.JobExplorer#getLastJobInstances(java.lang.String, int)
*/
public List<JobInstance> getLastJobInstances(String jobName, int count) {
public List<JobInstance> getJobInstances(String jobName, int start, int count) {
return jobInstanceDao.getLastJobInstances(jobName, count);
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
/**
* @author Dave Syer
*
*/
public class JobExecutionNotRunningException extends JobExecutionException {
/**
* Create a {@link JobExecutionNotRunningException} with a message.
*
* @param msg the message to signal cause of failure with details about the job execution
*/
public JobExecutionNotRunningException(String msg) {
super(msg);
}
}

View File

@@ -19,9 +19,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
@@ -38,18 +41,84 @@ import org.springframework.batch.core.repository.JobRestartException;
*/
public interface JobOperator {
/**
* List the {@link JobExecution JobExecutions} associated with a particular
* {@link JobInstance}, in reverse order of creation (and therefore usually
* of execution).
*
* @param instanceId the id of a {@link JobInstance}
* @return the id values of all the {@link JobExecution JobExecutions}
* associated with this instance
* @throws NoSuchJobInstanceException
*/
List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException;
List<Long> getLastInstances(String jobName, int count) throws NoSuchJobException;
/**
* List the {@link JobInstance JobInstances} for a given job name, in
* reverse order of creation (and therefore usually of first execution).
*
* @param jobName the job name that all the instances have
* @param start the start index of the instances
* @param count the maximum number of values to return
* @return the id values of the {@link JobInstance JobInstances}
* @throws NoSuchJobException
*/
List<Long> getJobInstances(String jobName, int start, int count) throws NoSuchJobException;
/**
* Get the id values of all the running {@link JobExecution JobExecutions}
* with the given job name.
*
* @param jobName the name of the job to search under
* @return the id values of the running {@link JobExecution} instances
* @throws NoSuchJobException if there are no {@link JobExecution
* JobExecutions} with that job name
*/
Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException;
/**
* Get the {@link JobParameters} as an easily readable String.
*
* @param executionId the id of an existing {@link JobExecution}
* @return the job parameters that were used to launch the associated
* instance
* @throws NoSuchJobExecutionException if the id was not associated with any
* {@link JobExecution}
*/
String getParameters(long executionId) throws NoSuchJobExecutionException;
Long start(String jobName, String parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException,
JobRestartException;
/**
* Start a new instance of a job with the parameters specified.
*
* @param jobName the name of the {@link Job} to launch
* @param parameters the parameters to launch it with (comma or newline
* separated name=value pairs)
* @return the id of the {@link JobExecution} that is launched
* @throws NoSuchJobException if there is no {@link Job} with the specified
* name
* @throws JobInstanceAlreadyExistsException if a job instance with this
* name and parameters already exists
*/
Long start(String jobName, String parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException;
Long resume(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
/**
* Restart a failed or stopped {@link JobExecution}. Fails with an exception
* if the id provided does not exist or corresponds to a {@link JobInstance}
* that in normal circumstances already completed successfully.
*
* @param executionId the id of a failed or stopped {@link JobExecution}
* @return the id of the {@link JobExecution} that was started
*
* @throws JobInstanceAlreadyCompleteException if the job was already
* successfully completed
* @throws NoSuchJobExecutionException if the id was not associated with any
* {@link JobExecution}
* @throws NoSuchJobException if the {@link JobExecution} was found, but its
* corresponding {@link Job} is no longer available for launching
* @throws JobRestartException if there is a non-specific error with the
* restart (e.g. corrupt or inconsistent restart data)
*/
Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
NoSuchJobException, JobRestartException;
/**
@@ -57,7 +126,8 @@ public interface JobOperator {
* {@link JobParametersIncrementer} attached to the specified job. If the
* previous instance is still in a failed state, this method should still
* create a new instance and run it with different parameters (as long as
* the {@link JobParametersIncrementer} is working).<br/><br/>
* the {@link JobParametersIncrementer} is working).<br/>
* <br/>
*
* The last three exception described below should be extremely unlikely,
* but cannot be ruled out entirely. It points to some other thread or
@@ -73,12 +143,52 @@ public interface JobOperator {
Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersNotFoundException,
JobRestartException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;
boolean stop(long executionId) throws NoSuchJobExecutionException;
/**
* Send a stop signal to the {@link JobExecution} with the supplied id. The
* signal is successfully sent if this method returns true, but that doesn't
* mean that the job has stopped. The only way to be sure of that is to poll
* the job execution status.
*
* @param executionId the id of a running {@link JobExecution}
* @return true if the message was successfully sent (does not guarantee
* that the job has stopped)
* @throws NoSuchJobExecutionException if there is no {@link JobExecution}
* with the id supplied
* @throws JobExecutionNotRunningException if the {@link JobExecution} is
* not running (so cannot be stopped)
*/
boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException;
/**
* Summarise the {@link JobExecution} with the supplied id, giving details
* of status, start and end times etc.
*
* @param executionId the id of an existing {@link JobExecution}
* @return a String summarising the state of the job execution
* @throws NoSuchJobExecutionException if there is no {@link JobExecution}
* with the supplied id
*/
String getSummary(long executionId) throws NoSuchJobExecutionException;
/**
* Summarise the {@link StepExecution} instances belonging to the
* {@link JobExecution} with the supplied id, giving details of status,
* start and end times etc.
*
* @param executionId the id of an existing {@link JobExecution}
* @return a map of step execution id to String summarising the state of the
* execution
* @throws NoSuchJobExecutionException if there is no {@link JobExecution}
* with the supplied id
*/
Map<Long, String> getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException;
/**
* List the available job names that can be launched with
* {@link #start(String, String)}.
*
* @return a set of job names
*/
Set<String> getJobNames();
}

View File

@@ -37,6 +37,7 @@ import org.springframework.batch.core.configuration.ListableJobRegistry;
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.JobExecutionNotRunningException;
import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
@@ -139,7 +140,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
throw new NoSuchJobInstanceException(String.format("No job instance with id=%d", instanceId));
}
List<Long> list = new ArrayList<Long>();
for (JobExecution jobExecution : jobExplorer.findJobExecutions(jobInstance)) {
for (JobExecution jobExecution : jobExplorer.getJobExecutions(jobInstance)) {
list.add(jobExecution.getId());
}
return list;
@@ -157,13 +158,11 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
/*
* (non-Javadoc)
*
* @see
* org.springframework.batch.core.launch.JobOperator#getLastInstances(java
* .lang.String, int)
* @see JobOperator#getLastInstances(String, int, int)
*/
public List<Long> getLastInstances(String jobName, int count) throws NoSuchJobException {
public List<Long> getJobInstances(String jobName, int start, int count) throws NoSuchJobException {
List<Long> list = new ArrayList<Long>();
for (JobInstance jobInstance : jobExplorer.getLastJobInstances(jobName, count)) {
for (JobInstance jobInstance : jobExplorer.getJobInstances(jobName, start, count)) {
list.add(jobInstance.getId());
}
if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) {
@@ -239,7 +238,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
* @see
* org.springframework.batch.core.launch.JobOperator#resume(java.lang.Long)
*/
public Long resume(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
public Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
NoSuchJobException, JobRestartException {
logger.info("Checking status of job execution with id=" + executionId);
@@ -313,7 +312,7 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
logger.info("Locating parameters for next instance of job with name=" + jobName);
Job job = jobRegistry.getJob(jobName);
List<JobInstance> lastInstances = jobExplorer.getLastJobInstances(jobName, 1);
List<JobInstance> lastInstances = jobExplorer.getJobInstances(jobName, 0, 1);
JobParametersIncrementer incrementer = job.getJobParametersIncrementer();
if (incrementer == null) {
@@ -357,15 +356,16 @@ public class SimpleJobOperator implements JobOperator, InitializingBean {
* org.springframework.batch.core.launch.JobOperator#stop(java.lang.Long)
*/
@Transactional
public boolean stop(long executionId) throws NoSuchJobExecutionException {
public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException {
JobExecution jobExecution = findExecutionById(executionId);
// Indicate the execution should be stopped by setting it's status to
// 'STOPPING'. It is assumed that
// the step implementation will check this status at chunk boundaries.
BatchStatus status = jobExecution.getStatus();
Assert.state(status == BatchStatus.STARTED || status == BatchStatus.STARTING,
"JobExecution must be running so that it can be stopped");
if (!(status == BatchStatus.STARTED || status == BatchStatus.STARTING)) {
throw new JobExecutionNotRunningException("JobExecution must be running so that it can be stopped: "+jobExecution);
}
jobExecution.setStatus(BatchStatus.STOPPING);
jobRepository.update(jobExecution);

View File

@@ -16,7 +16,10 @@
package org.springframework.batch.core.exlore.support;
import static org.easymock.EasyMock.*;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import java.util.Collections;
@@ -106,7 +109,7 @@ public class SimpleJobExplorerTests extends TestCase {
expect(jobInstanceDao.getJobInstance(jobExecution)).andReturn(jobInstance);
expect(stepExecutionDao.getStepExecutions(jobExecution)).andReturn(null);
replay(jobExecutionDao, jobInstanceDao, stepExecutionDao);
jobExplorer.findJobExecutions(jobInstance);
jobExplorer.getJobExecutions(jobInstance);
verify(jobExecutionDao, jobInstanceDao, stepExecutionDao);
}
@@ -124,7 +127,7 @@ public class SimpleJobExplorerTests extends TestCase {
jobInstanceDao.getLastJobInstances("foo", 1);
EasyMock.expectLastCall().andReturn(Collections.singletonList(jobInstance));
replay(jobExecutionDao, jobInstanceDao, stepExecutionDao);
jobExplorer.getLastJobInstances("foo", 1);
jobExplorer.getJobInstances("foo", 0, 1);
verify(jobExecutionDao, jobInstanceDao, stepExecutionDao);
}

View File

@@ -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 JobExecutionNotRunningExceptionTests {
@Test
public void testExceptionString() throws Exception {
Exception exception = new JobExecutionNotFailedException("foo");
assertEquals("foo", exception.getMessage());
}
}

View File

@@ -157,7 +157,7 @@ public class SimpleJobOperatorTests {
@Test
public void testStartNextInstanceSunnyDay() throws Exception {
final JobParameters jobParameters = new JobParameters();
jobExplorer.getLastJobInstances("foo", 1);
jobExplorer.getJobInstances("foo", 0, 1);
EasyMock.expectLastCall().andReturn(Collections.singletonList(new JobInstance(321L, jobParameters, "foo")));
EasyMock.replay(jobExplorer);
Long value = jobOperator.startNextInstance("foo");
@@ -199,7 +199,7 @@ public class SimpleJobOperatorTests {
EasyMock.expectLastCall()
.andReturn(new JobExecution(new JobInstance(123L, jobParameters, job.getName()), 111L));
EasyMock.replay(jobExplorer);
Long value = jobOperator.resume(111L);
Long value = jobOperator.restart(111L);
assertEquals(999, value.longValue());
EasyMock.verify(jobExplorer);
}
@@ -317,11 +317,11 @@ public class SimpleJobOperatorTests {
@Test
public void testGetLastInstancesSunnyDay() throws Exception {
jobParameters = new JobParameters();
jobExplorer.getLastJobInstances("foo", 2);
jobExplorer.getJobInstances("foo", 0, 2);
JobInstance jobInstance = new JobInstance(123L, jobParameters, job.getName());
EasyMock.expectLastCall().andReturn(Collections.singletonList(jobInstance));
EasyMock.replay(jobExplorer);
List<Long> value = jobOperator.getLastInstances("foo", 2);
List<Long> value = jobOperator.getJobInstances("foo", 0, 2);
assertEquals(123L, value.get(0).longValue());
EasyMock.verify(jobExplorer);
}
@@ -329,11 +329,11 @@ public class SimpleJobOperatorTests {
@Test
public void testGetLastInstancesNoSuchJob() throws Exception {
jobParameters = new JobParameters();
jobExplorer.getLastJobInstances("no-such-job", 2);
jobExplorer.getJobInstances("no-such-job", 0, 2);
EasyMock.expectLastCall().andReturn(Collections.emptyList());
EasyMock.replay(jobExplorer);
try {
jobOperator.getLastInstances("no-such-job", 2);
jobOperator.getJobInstances("no-such-job", 0, 2);
fail("Expected NoSuchJobException");
}
catch (NoSuchJobException e) {
@@ -355,7 +355,7 @@ public class SimpleJobOperatorTests {
jobExplorer.getJobInstance(123L);
EasyMock.expectLastCall().andReturn(jobInstance);
JobExecution jobExecution = new JobExecution(jobInstance, 111L);
jobExplorer.findJobExecutions(jobInstance);
jobExplorer.getJobExecutions(jobInstance);
EasyMock.expectLastCall().andReturn(Collections.singletonList(jobExecution));
EasyMock.replay(jobExplorer);
List<Long> value = jobOperator.getExecutions(123L);

View File

@@ -285,13 +285,14 @@ public class FlatFileItemWriter<T> extends ExecutionContextUserSupport implement
// Returns object representing state.
private OutputState getOutputState() {
if (state == null) {
File file;
try {
File file = resource.getFile();
Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
file = resource.getFile();
}
catch (IOException e) {
throw new ItemStreamException("Could not test resource for writable status.", e);
throw new ItemStreamException("Could not convert resource to file: [" + resource + "]", e);
}
Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
state = new OutputState();
state.setDeleteIfExists(shouldDeleteIfExists);
state.setEncoding(encoding);
@@ -308,7 +309,7 @@ public class FlatFileItemWriter<T> extends ExecutionContextUserSupport implement
private static final String DEFAULT_CHARSET = "UTF-8";
private FileOutputStream os;
// The bufferedWriter over the file channel that is actually written
Writer outputBufferedWriter;
@@ -327,7 +328,6 @@ public class FlatFileItemWriter<T> extends ExecutionContextUserSupport implement
boolean shouldDeleteIfExists = true;
boolean initialized = false;
/**
* Return the byte offset position of the cursor in the output file as a

View File

@@ -54,11 +54,11 @@ public class JobOperatorFunctionalTests {
assertEquals(params, tested.getParameters(executionId));
stopAndCheckStatus(executionId);
long resumedExecutionId = tested.resume(executionId);
long resumedExecutionId = tested.restart(executionId);
assertEquals(params, tested.getParameters(resumedExecutionId));
stopAndCheckStatus(resumedExecutionId);
List<Long> instances = tested.getLastInstances(job.getName(), 1);
List<Long> instances = tested.getJobInstances(job.getName(), 0, 1);
assertEquals(1, instances.size());
long instanceId = instances.get(0);

View File

@@ -99,7 +99,7 @@ public class RemoteLauncherTests {
Thread.sleep(SLEEP_INTERVAL);
// assertEquals(0, launcher.getRunningExecutions("loopJob").size());
logger.debug(launcher.getSummary(executionId));
long resumedId = launcher.resume(executionId);
long resumedId = launcher.restart(executionId);
assertEquals("Picked up the same execution after pause and resume", executionId, resumedId);
Thread.sleep(SLEEP_INTERVAL);
@@ -108,7 +108,7 @@ public class RemoteLauncherTests {
// assertEquals(0, launcher.getRunningExecutions("loopJob").size());
logger.debug(launcher.getSummary(executionId));
long resumeId2 = launcher.resume(executionId);
long resumeId2 = launcher.restart(executionId);
assertEquals("Picked up the same execution after pause and resume", executionId, resumeId2);
launcher.stop(executionId);