IN PROGRESS - issue BATCH-159: JobExecutor should return a JobExecution (which itself contains the ExitStatus)

http://opensource.atlassian.com/projects/spring/browse/BATCH-159

APIs all pretty much in place now - JobLauncher returns JobExecution from the run methods.  There is some polishing and test coverage work to do.
This commit is contained in:
dsyer
2007-11-03 11:18:00 +00:00
parent 3e72e7b565
commit 0cf5cb4bb2
32 changed files with 463 additions and 163 deletions

View File

@@ -81,11 +81,11 @@ public class Entity implements Serializable {
if (!(other instanceof Entity)) {
return false;
}
Entity step = (Entity) other;
if (id == null || step.getId() == null) {
return step == this;
Entity entity = (Entity) other;
if (id == null || entity.getId() == null) {
return entity == this;
}
return id.equals(step.getId());
return id.equals(entity.getId());
}
/**

View File

@@ -0,0 +1,31 @@
/*
* 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.executor;
/**
* @author Dave Syer
*
*/
public class JobExecutionException extends Exception {
public JobExecutionException(String msg) {
super(msg);
}
public JobExecutionException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -29,7 +29,7 @@ import org.springframework.batch.io.exception.BatchCriticalException;
* @author Dave Syer
*
*/
public class StepInterruptedException extends Exception {
public class StepInterruptedException extends JobExecutionException {
public StepInterruptedException(String msg) {
super(msg);

View File

@@ -57,6 +57,29 @@ public class EntityTests extends TestCase {
assertTrue(job.toString().indexOf("id=null") >= 0);
}
/**
* Test method for {@link org.springframework.batch.core.domain.Entity#equals(java.lang.Object)}.
*/
public void testEqualsSelf() {
assertEquals(entity, entity);
}
/**
* Test method for {@link org.springframework.batch.core.domain.Entity#equals(java.lang.Object)}.
*/
public void testEqualsSelfWithNullId() {
entity = new Entity(null);
assertEquals(entity, entity);
}
/**
* Test method for {@link org.springframework.batch.core.domain.Entity#equals(java.lang.Object)}.
*/
public void testEqualsEntityWithNullId() {
entity = new Entity(null);
assertNotSame(entity, new Entity(null));
}
/**
* Test method for {@link org.springframework.batch.core.domain.Entity#equals(java.lang.Object)}.
*/

View File

@@ -0,0 +1,40 @@
/*
* 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.executor;
import org.springframework.batch.core.AbstractExceptionTests;
/**
* @author Dave Syer
*
*/
public class JobExecutionExceptionTests extends AbstractExceptionTests {
/* (non-Javadoc)
* @see org.springframework.batch.io.exception.AbstractExceptionTests#getException(java.lang.String)
*/
public Exception getException(String msg) throws Exception {
return new JobExecutionException(msg);
}
/* (non-Javadoc)
* @see org.springframework.batch.io.exception.AbstractExceptionTests#getException(java.lang.String, java.lang.Throwable)
*/
public Exception getException(String msg, Throwable t) throws Exception {
return new JobExecutionException(msg, t);
}
}

View File

@@ -209,9 +209,9 @@ public class BatchCommandLineLauncher {
if (!launcher.isRunning()) {
if (jobName == null) {
status = launcher.run();
status = launcher.run().getExitStatus();
} else {
status = launcher.run(jobName);
status = launcher.run(jobName).getExitStatus();
}
}
} catch (NoSuchJobConfigurationException e) {

View File

@@ -60,9 +60,9 @@ public class ThreadInterruptJobExecutionListener extends
* Interrupt the thread that is running the job if the {@link ExitStatus}
* indicates that it is still running.
*
* @see org.springframework.batch.execution.launch.JobExecutionListenerSupport#stop(org.springframework.batch.core.domain.JobExecution)
* @see org.springframework.batch.execution.launch.JobExecutionListenerSupport#onStop(org.springframework.batch.core.domain.JobExecution)
*/
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
if (execution==null || execution.getExitStatus().isRunning()) {
processingThread.interrupt();
}

View File

@@ -0,0 +1,41 @@
/*
* 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.execution.launch;
import org.springframework.batch.core.executor.JobExecutionException;
/**
* @author Dave Syer
*
*/
public class JobExecutionAlreadyRunningException extends JobExecutionException {
/**
* @param msg
*/
public JobExecutionAlreadyRunningException(String msg) {
super(msg);
}
/**
* @param msg
* @param cause
*/
public JobExecutionAlreadyRunningException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -46,5 +46,5 @@ public interface JobExecutionListener {
*
* @param execution
*/
void stop(JobExecution execution);
void onStop(JobExecution execution);
}

View File

@@ -47,9 +47,9 @@ public class JobExecutionListenerSupport implements JobExecutionListener {
/**
* No-op for subclasses to extend.
*
* @see org.springframework.batch.execution.launch.JobExecutionListener#stop(org.springframework.batch.core.domain.JobExecution)
* @see org.springframework.batch.execution.launch.JobExecutionListener#onStop(org.springframework.batch.core.domain.JobExecution)
*/
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
// no-op
}

View File

@@ -17,8 +17,8 @@
package org.springframework.batch.execution.launch;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.repeat.ExitStatus;
/**
* Interface which defines a facade for running jobs. The interface is
@@ -31,18 +31,29 @@ import org.springframework.batch.repeat.ExitStatus;
interface JobExecutorFacade {
/**
* Start a job execution identifiable by the {@link JobIdentifier}.
* Implementations normally require a job configuration to be locatable
* corresponding to the {@link JobIdentifier}, preferably matching them at
* Prepare a job execution identifiable by the {@link JobIdentifier}. THis
* can then be used to run the job with the {@link #start(JobExecution)}
* method. Implementations normally require a job configuration to be
* locatable corresponding to the {@link JobIdentifier}, matching them at
* least by name.
*
* @param jobIdentifier
* the identifier of the job to start
*
* @throws NoSuchJobConfigurationException
*/
ExitStatus start(JobIdentifier jobIdentifier)
JobExecution createNewExecution(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException;
/**
* Start a job execution.
*
* @param execution
* the execution of the job to start
* @throws NoSuchJobConfigurationException
*/
void start(JobExecution execution) throws NoSuchJobConfigurationException;
/**
* Stop the job execution that was started with this runtime information.
*

View File

@@ -16,13 +16,12 @@
package org.springframework.batch.execution.launch;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.repeat.ExitStatus;
/**
* Simple interface for controlling jobs, including possible ad-hoc executions,
* based on different runtime identifiers. Implementations should concentrate on
* managing jobs and delegate the launching to a {@link JobExecutorFacade}.
* based on different runtime identifiers.
*
* @author Lucas Ward
* @author Dave Syer
@@ -36,9 +35,10 @@ public interface JobLauncher {
*
* @return the exit code from the job if it returns synchronously. If the
* implementation is asynchronous, the status might well be unknown.
* @throws JobExecutionAlreadyRunningException
*
*/
public ExitStatus run() throws NoSuchJobConfigurationException;
public JobExecution run() throws NoSuchJobConfigurationException, JobExecutionAlreadyRunningException;
/**
* Start a job execution with the given name and other runtime information
@@ -51,9 +51,10 @@ public interface JobLauncher {
* implementation is asynchronous, the status might well be unknown.
*
* @throws NoSuchJobConfigurationException
* @throws JobExecutionAlreadyRunningException
*/
public ExitStatus run(String jobName)
throws NoSuchJobConfigurationException;
public JobExecution run(String jobName)
throws NoSuchJobConfigurationException, JobExecutionAlreadyRunningException;
/**
* Start a job execution with the given runtime information.
@@ -63,8 +64,8 @@ public interface JobLauncher {
*
* @throws NoSuchJobConfigurationException
*/
public ExitStatus run(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException;
public JobExecution run(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException, JobExecutionAlreadyRunningException;
/**
* Stop the current job executions if there are any. If not, no action will

View File

@@ -33,9 +33,9 @@ import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.executor.JobExecutor;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.execution.job.DefaultJobExecutor;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
@@ -57,17 +57,17 @@ import org.springframework.util.Assert;
*
*/
class SimpleJobExecutorFacade implements JobExecutorFacade,
JobExecutionListener, StatisticsProvider {
JobExecutionListener, StatisticsProvider, InitializingBean {
private Map jobExecutionRegistry = new HashMap();
private JobExecutor jobExecutor = new DefaultJobExecutor();
private JobRepository jobRepository;
// there is no sensible default for this
private JobConfigurationLocator jobConfigurationLocator;
private List listeners = new ArrayList();
private int running = 0;
@@ -84,6 +84,18 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
this.listeners = listeners;
}
/**
* Check mandatory properties (jobConfigurationLocator,
* jobRepository).
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jobRepository, "JobRepository must be provided.");
Assert.notNull(jobConfigurationLocator,
"JobConfigurationLocator must be provided.");
}
/**
* Public accessor for the running property.
*
@@ -128,23 +140,18 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
* Locates a {@link JobConfiguration} by using the name of the provided
* {@link JobIdentifier} and the {@link JobConfigurationLocator}.
*
* @see org.springframework.batch.execution.launch.JobExecutorFacade#start(org.springframework.batch.execution.common.domain.JobConfiguration,
* org.springframework.batch.core.domain.JobIdentifier)
* @param jobConfiguration
*
* @throws IllegalArgumentException
* if the {@link JobIdentifier} is null or its name is null
* @throws IllegalStateException
* @throws NoSuchJobConfigurationException
* if the {@link JobConfigurationLocator} does not contain a
* {@link JobConfiguration} with the name provided.
* @throws IllegalStateException
* if the {@link JobExecutor} is null
* @throws IllegalStateException
* if the {@link JobConfigurationLocator} is null
*
* @see org.springframework.batch.execution.launch.JobExecutorFacade#createNewExecution(org.springframework.batch.core.domain.JobIdentifier)
*/
public ExitStatus start(JobIdentifier jobIdentifier)
public JobExecution createNewExecution(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException {
Assert.notNull(jobIdentifier, "JobIdentifier must not be null.");
Assert.notNull(jobIdentifier.getName(),
"JobIdentifier name must not be null.");
@@ -153,16 +160,32 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
.state(!jobExecutionRegistry.containsKey(jobIdentifier),
"A job with this JobRuntimeInformation is already executing in this container");
Assert.state(jobExecutor != null, "JobExecutor must be provided.");
Assert.state(jobConfigurationLocator != null,
"JobConfigurationLocator must be provided.");
JobConfiguration jobConfiguration = jobConfigurationLocator
.getJobConfiguration(jobIdentifier.getName());
JobInstance job = jobRepository.findOrCreateJob(jobConfiguration,
jobIdentifier);
JobExecution execution = new JobExecution(job);
return new JobExecution(job);
}
/**
* Starts a job execution that was previously acquired from the
* {@link #createNewExecution(JobIdentifier)} method.
*
* @see org.springframework.batch.execution.launch.JobExecutorFacade#start(JobExecution)
*
* @throws NoSuchJobConfigurationException
* if the {@link JobConfigurationLocator} does not contain a
* {@link JobConfiguration} with the name provided by the
* enclosed {@link JobIdentifier}.
*
*/
public void start(JobExecution execution)
throws NoSuchJobConfigurationException {
JobConfiguration jobConfiguration = jobConfigurationLocator
.getJobConfiguration(execution.getJob().getIdentifier()
.getName());
this.before(execution);
try {
@@ -171,7 +194,6 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
this.after(execution);
}
return execution.getExitStatus();
}
/**
@@ -180,11 +202,14 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
* order that they were given.
*
* @param execution
*
* @see JobExecutionListener#before(JobExecution)
*/
public void before(JobExecution execution) {
synchronized (mutex) {
running++;
jobExecutionRegistry.put(execution.getJob().getIdentifier(), execution);
jobExecutionRegistry.put(execution.getJob().getIdentifier(),
execution);
}
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
JobExecutionListener listener = (JobExecutionListener) iterator
@@ -197,12 +222,14 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
* Broadcast stop signal to all the registered listeners.
*
* @param execution
*
* @see JobExecutionListener#onStop(JobExecution)
*/
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
JobExecutionListener listener = (JobExecutionListener) iterator
.next();
listener.stop(execution);
listener.onStop(execution);
}
}
@@ -212,6 +239,8 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
* then finally dealing with internal housekeeping.
*
* @param execution
*
* @see JobExecutionListener#after(JobExecution)
*/
public void after(JobExecution execution) {
ArrayList reversed = new ArrayList(listeners);
@@ -232,9 +261,9 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
/**
* Send a stop signal to all the running executions by setting their
* {@link RepeatContext} to terminate only. Then call the
* {@link JobExecutionListener#stop(JobExecution)} method.
* {@link JobExecutionListener#onStop(JobExecution)} method.
*
* @see org.springframework.batch.container.BatchContainer#stop(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
* @see org.springframework.batch.container.BatchContainer#onStop(org.springframework.batch.container.common.runtime.JobRuntimeInformation)
*/
public void stop(JobIdentifier runtimeInformation)
throws NoSuchJobExecutionException {
@@ -254,10 +283,13 @@ class SimpleJobExecutorFacade implements JobExecutorFacade,
RepeatContext context = (RepeatContext) iter.next();
context.setTerminateOnly();
}
this.stop(execution);
this.onStop(execution);
}
/**
* Provides a snapshot of properties from running jobs (the ones that were
* launched from this {@link JobExecutorFacade).
*
* @return a read-only view of the state of the running jobs.
*/
public Properties getStatistics() {

View File

@@ -38,7 +38,6 @@ import org.springframework.batch.core.runtime.JobIdentifierFactory;
import org.springframework.batch.execution.job.DefaultJobExecutor;
import org.springframework.batch.execution.runtime.ScheduledJobIdentifierFactory;
import org.springframework.batch.io.exception.BatchConfigurationException;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.interceptor.RepeatOperationsApplicationEvent;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.InitializingBean;
@@ -52,8 +51,8 @@ import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
/**
* Base class for {@link JobLauncher} implementations making no choices about
* concurrent processing of jobs.
* Generic {@link JobLauncher} allowing choice of strategy for concurrent
* execution and .
*
* @see JobLauncher
* @author Dave Syer
@@ -65,19 +64,22 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
.getLog(SimpleJobLauncher.class);
private JobExecutor jobExecutor = new DefaultJobExecutor();
// there is no sensible default for this
private JobRepository jobRepository;
// there is no sensible default for this
private JobConfigurationLocator jobConfigurationLocator;
private List listeners = new ArrayList();
// this can be defaulted from some other properties (see
// afterPropertiesSet())
private JobExecutorFacade jobExecutorFacade;
private String jobConfigurationName;
private TaskExecutor taskExecutor = new SyncTaskExecutor();
private final Object monitor = new Object();
private List listeners = new ArrayList();
private String jobConfigurationName;
// Do not autostart by default - allow user to set job configuration
// later and then manually start:
@@ -85,12 +87,12 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
private JobIdentifierFactory jobIdentifierFactory = new ScheduledJobIdentifierFactory();
private final Object monitor = new Object();
// A private registry for keeping track of running jobs.
private volatile Map registry = new HashMap();
private TaskExecutor taskExecutor = new SyncTaskExecutor();
ApplicationEventPublisher applicationEventPublisher;
private ApplicationEventPublisher applicationEventPublisher;
/**
* Setter for {@link JobIdentifierFactory}.
@@ -122,7 +124,7 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
public void setAutoStart(boolean autoStart) {
this.autoStart = autoStart;
}
/**
* Public setter for the listeners property.
*
@@ -134,7 +136,8 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
}
/**
* Setter for injection of {@link JobConfigurationLocator}.
* Setter for injection of {@link JobConfigurationLocator}. Mandatory with
* no default.
*
* @param jobConfigurationLocator
* the jobConfigurationLocator to set
@@ -145,7 +148,7 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
}
/**
* Setter for {@link JobExecutor}.
* Setter for {@link JobExecutor}. Defaults to a {@link DefaultJobExecutor}.
*
* @param jobExecutor
*/
@@ -154,35 +157,38 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
}
/**
* Setter for {@link JobRepository}.
* Setter for {@link JobRepository}. Mandatory with no default.
*
* @param jobRepository
*/
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
}
/**
* Setter for {@link JobExecutorFacade}.
* Setter for {@link JobExecutorFacade}. Package private because it is only
* used for testing purposes.
*/
void setJobExecutorFacade(JobExecutorFacade jobExecutorFacade) {
this.jobExecutorFacade = jobExecutorFacade;
}
/**
* Check that mandatory properties are set.
* Check that mandatory properties are set and create a {@link JobExecutor}
* if one wasn't provided.
*
* @see #setJobExecutorFacade(JobExecutorFacade)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
if (jobExecutorFacade==null) {
if (jobExecutorFacade == null) {
logger.debug("Using SimpleJobExecutorFacade");
Assert.notNull(jobConfigurationLocator);
Assert.notNull(jobExecutor);
Assert.notNull(jobRepository);
SimpleJobExecutorFacade jobExecutorFacade = new SimpleJobExecutorFacade();
jobExecutorFacade.setJobConfigurationLocator(jobConfigurationLocator);
jobExecutorFacade
.setJobConfigurationLocator(jobConfigurationLocator);
jobExecutorFacade.setJobExecutionListeners(listeners);
jobExecutorFacade.setJobExecutor(jobExecutor);
jobExecutorFacade.setJobRepository(jobRepository);
@@ -208,7 +214,10 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
run();
} catch (NoSuchJobConfigurationException e) {
throw new BatchConfigurationException(
"Cannot start job on context refresh", e);
"Cannot start job on context refresh because it does not exist", e);
} catch (JobExecutionAlreadyRunningException e) {
throw new BatchConfigurationException(
"Cannot start job on context refresh because it is already running", e);
}
}
}
@@ -222,19 +231,20 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
* @return
* @throws NoSuchJobConfigurationException
*/
protected final ExitStatus runInternal(final JobIdentifier jobIdentifier)
protected final void runInternal(JobExecution execution)
throws NoSuchJobConfigurationException {
JobIdentifier jobIdentifier = execution.getJob().getIdentifier();
synchronized (monitor) {
if (isInternalRunning(jobIdentifier)) {
return ExitStatus.RUNNING;
return;
}
}
register(jobIdentifier);
register(execution);
try {
return jobExecutorFacade.start(jobIdentifier);
jobExecutorFacade.start(execution);
} finally {
unregister(jobIdentifier);
}
@@ -244,16 +254,30 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
/**
* Start the job using the task executor provided.
*
* @throws NoSuchJobConfigurationException
* if the identifier cannot be used to locate a
* {@link JobConfiguration}.
*
* @see org.springframework.batch.execution.launch.SimpleJobLauncher#run(org.springframework.batch.core.domain.JobIdentifier)
*/
public ExitStatus run(final JobIdentifier jobIdentifier) {
public JobExecution run(final JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException,
JobExecutionAlreadyRunningException {
Assert.state(taskExecutor != null, "TaskExecutor must be provided");
if (get(jobIdentifier) != null) {
throw new JobExecutionAlreadyRunningException(
"A job is already executing with this identifier: ["
+ jobIdentifier + "]");
}
final JobExecution execution = jobExecutorFacade
.createNewExecution(jobIdentifier);
// TODO: throw JobExecutionAlreadyRunningException if it is in a running
// state (someone else launched it)
taskExecutor.execute(new Runnable() {
public void run() {
try {
runInternal(jobIdentifier);
runInternal(execution);
} catch (NoSuchJobConfigurationException e) {
applicationEventPublisher
.publishEvent(new RepeatOperationsApplicationEvent(
@@ -266,7 +290,7 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
}
});
return ExitStatus.UNKNOWN;
return execution;
}
@@ -277,8 +301,9 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
* @param name
* the name to assign to the job
* @throws NoSuchJobConfigurationException
* @throws JobExecutionAlreadyRunningException
*/
public ExitStatus run(String name) throws NoSuchJobConfigurationException {
public JobExecution run(String name) throws NoSuchJobConfigurationException, JobExecutionAlreadyRunningException {
if (name == null) {
throw new NoSuchJobConfigurationException(
"Null job name cannot be located.");
@@ -297,11 +322,12 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
*
* @throws NoSuchJobConfigurationException
* if the job configuration cannot be located
* @throws JobExecutionAlreadyRunningException
*
* @see #setJobIdentifierFactory(JobIdentifierFactory)
* @see org.springframework.context.Lifecycle#start()
*/
public ExitStatus run() throws NoSuchJobConfigurationException {
public JobExecution run() throws NoSuchJobConfigurationException, JobExecutionAlreadyRunningException {
if (jobConfigurationName != null) {
return this.run(jobConfigurationName);
}
@@ -408,17 +434,30 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean,
}
/**
* Convenient synchronized accessor for the registry. Can be used by
* subclasses if necessary (but it isn't likely).
* Convenient synchronized accessor for the registry.
*
* @param jobIdentifier
*/
private void register(JobIdentifier jobIdentifier) {
private void register(JobExecution execution) {
synchronized (registry) {
registry.put(jobIdentifier, jobIdentifier);
registry.put(execution.getJob().getIdentifier(), execution);
}
}
/**
* Convenient synchronized accessor for the registry.
*
* @param jobIdentifier
*/
private JobExecution get(JobIdentifier jobIdentifier) {
synchronized (registry) {
if (registry.containsKey(jobIdentifier)) {
return (JobExecution) registry.get(jobIdentifier);
}
}
return null;
}
/**
* Convenient synchronized accessor for the registry. Must be used by
* subclasses to release the {@link JobIdentifier} when a job is finished

View File

@@ -17,6 +17,9 @@ package org.springframework.batch.execution.bootstrap.support;
import junit.framework.TestCase;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
@@ -81,7 +84,7 @@ public class BatchCommandLineLauncherTests extends TestCase {
assertNotNull(jobLauncher);
assertNotNull(systemExiter);
jobLauncher.setReturnValue(ExitStatus.FINISHED);
setReturnValue(ExitStatus.FINISHED);
BatchCommandLineLauncher.main(new String[0]);
@@ -91,12 +94,18 @@ public class BatchCommandLineLauncherTests extends TestCase {
StubJobLauncher.RUN_NO_ARGS);
}
/**
* Test method for
* {@link org.springframework.batch.execution.bootstrap.support.BatchCommandLineLauncher#main(java.lang.String[])}.
*
* @throws Exception
*/
public void testCustomJobName() {
buildContext(TEST_BATCH_ENVIRONMENT_KEY);
assertNotNull(jobLauncher);
assertNotNull(systemExiter);
jobLauncher.setReturnValue(ExitStatus.FINISHED);
setReturnValue(ExitStatus.FINISHED);
System.setProperty(JOB_NAME_KEY, "foo");
BatchCommandLineLauncher.main(new String[0]);
@@ -107,6 +116,12 @@ public class BatchCommandLineLauncherTests extends TestCase {
StubJobLauncher.RUN_JOB_NAME);
}
private void setReturnValue(ExitStatus status) {
JobExecution execution = new JobExecution(new JobInstance(new SimpleJobIdentifier("foo")));
execution.setExitStatus(status);
jobLauncher.setReturnValue(execution);
}
/**
* Test method for
* {@link org.springframework.batch.execution.bootstrap.support.BatchCommandLineLauncher#main(java.lang.String[])}.

View File

@@ -1,9 +1,9 @@
package org.springframework.batch.execution.bootstrap.support;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.execution.launch.JobLauncher;
import org.springframework.batch.repeat.ExitStatus;
/**
* Mock Job Launcher. Normally, something like EasyMock would
@@ -21,7 +21,7 @@ public class StubJobLauncher implements JobLauncher {
public static final int RUN_JOB_IDENTIFIER =2 ;
private int lastRunCalled = RUN_NO_ARGS;
private ExitStatus returnValue = ExitStatus.FINISHED;
private JobExecution returnValue = null;
private boolean isRunning = false;
@@ -29,18 +29,18 @@ public class StubJobLauncher implements JobLauncher {
return isRunning;
}
public ExitStatus run() throws NoSuchJobConfigurationException {
public JobExecution run() throws NoSuchJobConfigurationException {
lastRunCalled = RUN_NO_ARGS;
return returnValue;
}
public ExitStatus run(String jobName)
public JobExecution run(String jobName)
throws NoSuchJobConfigurationException {
lastRunCalled = RUN_JOB_NAME;
return returnValue;
}
public ExitStatus run(JobIdentifier jobIdentifier)
public JobExecution run(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException {
lastRunCalled = RUN_JOB_IDENTIFIER;
return returnValue;
@@ -50,7 +50,7 @@ public class StubJobLauncher implements JobLauncher {
}
public void setReturnValue(ExitStatus returnValue){
public void setReturnValue(JobExecution returnValue){
this.returnValue = returnValue;
}

View File

@@ -60,12 +60,13 @@ public class InterruptJobTests extends TestCase {
registry.register(new JobConfiguration("foo"));
final SimpleJobIdentifier identifier = new SimpleJobIdentifier("foo");
final JobExecution execution = facade.createNewExecution(identifier);
TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
Runnable launcherRunnable = new Runnable() {
public void run() {
try {
facade.start(identifier);
facade.start(execution);
} catch (NoSuchJobConfigurationException e) {
fail("Unexpected NoSuchJobConfigurationException");
}

View File

@@ -0,0 +1,40 @@
/*
* 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.execution.launch;
import org.springframework.batch.core.AbstractExceptionTests;
/**
* @author Dave Syer
*
*/
public class JobExecutionAlreadyRunningExceptionTests extends AbstractExceptionTests {
/* (non-Javadoc)
* @see org.springframework.batch.io.exception.AbstractExceptionTests#getException(java.lang.String)
*/
public Exception getException(String msg) throws Exception {
return new JobExecutionAlreadyRunningException(msg);
}
/* (non-Javadoc)
* @see org.springframework.batch.io.exception.AbstractExceptionTests#getException(java.lang.String, java.lang.Throwable)
*/
public Exception getException(String msg, Throwable t) throws Exception {
return new JobExecutionAlreadyRunningException(msg, t);
}
}

View File

@@ -70,13 +70,13 @@ public class JobExecutionListenerSupportTests extends TestCase {
*/
public void testStop() {
JobExecutionListener listener = new JobExecutionListenerSupport() {
public void stop(JobExecution execution) {
super.stop(execution);
public void onStop(JobExecution execution) {
super.onStop(execution);
list.add("stop");
}
};
listener.stop(null);
listener.onStop(null);
assertEquals(1, list.size());
}
}

View File

@@ -78,23 +78,32 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
jobExecutorFacade.setJobRepository(jobRepository);
}
public void testNormalStart() throws Exception {
public void testCreateNewExecution() throws Exception {
JobInstance job = setUpFacadeForNormalStart();
jobExecutorFacade.start(jobIdentifier);
jobExecution = jobExecutorFacade.createNewExecution(jobIdentifier);
assertEquals(job, jobExecution.getJob());
assertEquals("bar", job.getName());
jobRepositoryControl.verify();
}
private JobInstance setUpFacadeForNormalStart() {
public void testNormalStart() throws Exception {
JobInstance job = setUpFacadeForNormalStart();
jobExecution = jobExecutorFacade.createNewExecution(jobIdentifier);
jobExecutorFacade.start(jobExecution);
assertEquals(job, jobExecution.getJob());
assertEquals("bar", job.getName());
jobRepositoryControl.verify();
}
private JobInstance setUpFacadeForNormalStart() throws NoSuchJobConfigurationException {
jobIdentifier = new SimpleJobIdentifier("bar");
jobRepository.findOrCreateJob(jobConfiguration, jobIdentifier);
jobExecutor = new JobExecutor() {
public ExitStatus run(JobConfiguration configuration,
JobExecution execution)
throws BatchCriticalException {
JobExecution execution) throws BatchCriticalException {
jobExecution = execution;
return ExitStatus.FINISHED;
}
@@ -102,6 +111,7 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
jobExecutorFacade.setJobExecutor(jobExecutor);
JobInstance job = new JobInstance(jobIdentifier);
jobExecution = new JobExecution(job);
jobRepository.findOrCreateJob(jobConfiguration, jobIdentifier);
jobRepositoryControl.setReturnValue(job);
jobRepositoryControl.replay();
jobExecutorFacade
@@ -117,8 +127,7 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
public void testIsRunning() throws Exception {
jobExecutorFacade.setJobExecutor(new JobExecutor() {
public ExitStatus run(JobConfiguration configuration,
JobExecution execution)
throws BatchCriticalException {
JobExecution execution) throws BatchCriticalException {
while (running) {
try {
Thread.sleep(100L);
@@ -137,17 +146,12 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
return jobConfiguration;
}
});
final SimpleJobIdentifier jobIdentifier = new SimpleJobIdentifier("foo");
jobRepository.findOrCreateJob(jobConfiguration, jobIdentifier);
JobInstance job = new JobInstance(jobIdentifier);
jobRepositoryControl.setReturnValue(job);
jobRepositoryControl.replay();
running = true;
new Thread(new Runnable() {
public void run() {
try {
jobExecutorFacade.start(jobIdentifier);
jobExecutorFacade.start(jobExecution);
} catch (NoSuchJobConfigurationException e) {
System.err.println("Shouldn't happen");
}
@@ -162,17 +166,16 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
Thread.sleep(100L);
}
assertFalse(jobExecutorFacade.isRunning());
jobRepositoryControl.verify();
}
public void testInvalidState() throws Exception {
public void testInvalidInitialisation() throws Exception {
jobExecutorFacade.setJobExecutor(null);
jobExecutorFacade = new SimpleJobExecutorFacade();
try {
jobExecutorFacade.start(new SimpleJobIdentifier("TestJob"));
jobExecutorFacade.afterPropertiesSet();
fail("Expected IllegalStateException");
} catch (IllegalStateException ex) {
} catch (IllegalArgumentException ex) {
// expected
}
}
@@ -194,10 +197,10 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
"TestJob");
JobExecution execution = new JobExecution(new JobInstance(
runtimeInformation, new Long(0)));
List listeners = new ArrayList();
listeners.add(new JobExecutionListenerSupport() {
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
list.add("one");
}
});
@@ -236,17 +239,17 @@ public class SimpleJobExecutorFacadeTests extends TestCase {
public void testListenersCalledLastOnStop() throws Exception {
List listeners = new ArrayList();
listeners.add(new JobExecutionListenerSupport() {
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
list.add("one");
}
});
listeners.add(new JobExecutionListenerSupport() {
public void stop(JobExecution execution) {
public void onStop(JobExecution execution) {
list.add("two");
}
});
jobExecutorFacade.setJobExecutionListeners(listeners);
jobExecutorFacade.stop(jobExecution);
jobExecutorFacade.onStop(jobExecution);
assertEquals(2, list.size());
assertEquals("two", list.get(1));
}

View File

@@ -20,12 +20,10 @@ import junit.framework.TestCase;
import org.springframework.batch.core.configuration.JobConfiguration;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.runtime.SimpleJobIdentifierFactory;
import org.springframework.batch.execution.launch.JobExecutionListener;
import org.springframework.batch.execution.launch.JobExecutorFacade;
import org.springframework.batch.execution.launch.SimpleJobLauncher;
import org.springframework.batch.repeat.ExitStatus;
public class SimpleJobLauncherTests extends TestCase {
@@ -97,15 +95,14 @@ public class SimpleJobLauncherTests extends TestCase {
}
}
public ExitStatus start(JobIdentifier runtimeInformation) {
public void start(JobExecution execution)
throws NoSuchJobConfigurationException {
run();
return ExitStatus.FAILED;
}
public ExitStatus start(JobIdentifier jobIdentifier,
JobExecutionListener listener)
public JobExecution createNewExecution(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException {
throw new UnsupportedOperationException("Not implemented");
return new JobExecution(new JobInstance(jobIdentifier));
}
public void stop(JobIdentifier runtimeInformation) {

View File

@@ -25,13 +25,11 @@ import junit.framework.TestCase;
import org.easymock.MockControl;
import org.springframework.batch.core.configuration.JobConfiguration;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobIdentifier;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
import org.springframework.batch.core.runtime.SimpleJobIdentifierFactory;
import org.springframework.batch.execution.launch.JobExecutionListener;
import org.springframework.batch.execution.launch.JobExecutorFacade;
import org.springframework.batch.execution.launch.SimpleJobLauncher;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.batch.support.PropertiesConverter;
import org.springframework.context.ApplicationEvent;
@@ -44,8 +42,7 @@ public class TaskExecutorJobLauncherTests extends TestCase {
protected void setUp() throws Exception {
super.setUp();
launcher
.setJobIdentifierFactory(new SimpleJobIdentifierFactory());
launcher.setJobIdentifierFactory(new SimpleJobIdentifierFactory());
}
public void testStopContainer() throws Exception {
@@ -57,10 +54,37 @@ public class TaskExecutorJobLauncherTests extends TestCase {
launcher.setJobExecutorFacade(container);
launcher.setJobConfigurationName(new JobConfiguration("foo").getName());
launcher.run();
// give the thread some time to start up...
Thread.sleep(100);
assertTrue(launcher.isRunning());
launcher.stop();
// ...and to shut down:
Thread.sleep(400);
assertFalse(launcher.isRunning());
}
public void testRunTwice() throws Exception {
// Important (otherwise start() does not return!)
launcher.setTaskExecutor(new SimpleAsyncTaskExecutor());
InterruptibleContainer container = new InterruptibleContainer();
launcher.setJobExecutorFacade(container);
launcher.setJobConfigurationName(new JobConfiguration("foo").getName());
launcher.run();
// give the thread some time to start up:
Thread.sleep(100);
assertTrue(launcher.isRunning());
try {
launcher.run();
fail("Expected JobExecutionAlreadyRunningException");
} catch (JobExecutionAlreadyRunningException e) {
// expected
}
// give the thread some time to start up...
Thread.sleep(100);
launcher.stop();
// ...and to shut down:
Thread.sleep(400);
@@ -112,22 +136,21 @@ public class TaskExecutorJobLauncherTests extends TestCase {
// for interrupt to be called;
Thread.sleep(300);
} catch (InterruptedException ex) {
// thread intterrupted, allow to exit normally
// thread interrupted, allow to exit normally
}
}
}
public ExitStatus start(JobIdentifier jobIdentifier,
JobExecutionListener listener)
public void start(JobExecution execution)
throws NoSuchJobConfigurationException {
throw new UnsupportedOperationException("Not implemented");
start();
}
public ExitStatus start(JobIdentifier runtimeInformation) {
start();
return ExitStatus.FAILED;
public JobExecution createNewExecution(JobIdentifier jobIdentifier)
throws NoSuchJobConfigurationException {
return new JobExecution(new JobInstance(jobIdentifier));
}
public void stop(JobIdentifier runtimeInformation) {
running = false;
}
@@ -148,12 +171,15 @@ public class TaskExecutorJobLauncherTests extends TestCase {
MockControl control = MockControl
.createControl(JobExecutorFacade.class);
JobExecutorFacade batchContainer = (JobExecutorFacade) control
.getMock();
launcher.setJobExecutorFacade(batchContainer);
JobExecutorFacade facade = (JobExecutorFacade) control.getMock();
launcher.setJobExecutorFacade(facade);
SimpleJobIdentifier jobRuntimeInformation = new SimpleJobIdentifier(
"spam");
batchContainer.start(jobRuntimeInformation);
JobExecution execution = new JobExecution(new JobInstance(
jobRuntimeInformation));
control.expectAndReturn(facade
.createNewExecution(jobRuntimeInformation), execution);
facade.start(execution);
control.setThrowable(new NoSuchJobConfigurationException("SPAM"));
control.replay();

View File

@@ -13,7 +13,7 @@ import org.springframework.jdbc.core.RowMapper;
*
* @author Robert Kasanicky
*/
public abstract class AbstractCustomerCreditIncreaseTests extends AbstractLifecycleSpringContextTests {
public abstract class AbstractCustomerCreditIncreaseTests extends AbstractValidatingBatchLauncherTests {
private JdbcOperations jdbcTemplate;

View File

@@ -27,7 +27,7 @@ import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
* @author Lucas Ward
* @see AbstractDependencyInjectionSpringContextTests
*/
public abstract class AbstractLifecycleSpringContextTests extends AbstractBatchLauncherTests {
public abstract class AbstractValidatingBatchLauncherTests extends AbstractBatchLauncherTests {
public void testLaunchJob() throws Exception {
validatePreConditions();

View File

@@ -17,7 +17,7 @@
package org.springframework.batch.sample;
public class BeanWrapperMapperSampleJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class BeanWrapperMapperSampleJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
protected String[] getConfigLocations() {
return new String[]{"jobs/beanWrapperMapperSampleJob.xml"};

View File

@@ -16,7 +16,7 @@ import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowCallbackHandler;
public class CompositeProcessorSampleFunctionalTests extends AbstractLifecycleSpringContextTests {
public class CompositeProcessorSampleFunctionalTests extends AbstractValidatingBatchLauncherTests {
private static final String GET_TRADES = "SELECT isin, quantity, price, customer FROM trade order by isin";

View File

@@ -27,7 +27,7 @@ import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowCallbackHandler;
public class FixedLengthImportJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class FixedLengthImportJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
//expected line length in input file (sum of pattern lengths + 2, because the counter is appended twice)
private static final int LINE_LENGTH = 29;

View File

@@ -21,7 +21,7 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
public class MultilineJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class MultilineJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
private static final String EXPECTED_RESULT =
"[Trade: [isin=UK21341EAH45,quantity=978,price=98.34,customer=customer1], Trade: [isin=UK21341EAH46,quantity=112,price=18.12,customer=customer2]]" +

View File

@@ -23,7 +23,7 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
public class MultilineOrderJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class MultilineOrderJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
//private static final Log log = LogFactory.getLog(MultilineOrderJobFunctionalTests.class);
private static final String EXPECTED_OUTPUT =

View File

@@ -1,9 +1,9 @@
package org.springframework.batch.sample;
public class NflJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class NflJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
protected String[] getConfigLocations() {
return new String[] {"jobs/nfljob.xml"};
return new String[] {"jobs/nfljob.xmlXXX"};
}
protected void validatePostConditions() throws Exception {

View File

@@ -33,7 +33,7 @@ import org.springframework.jdbc.core.RowCallbackHandler;
public class TradeJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class TradeJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
private static final String GET_TRADES = "SELECT isin, quantity, price, customer FROM trade";
private static final String GET_CUSTOMERS = "SELECT name, credit FROM customer";

View File

@@ -22,7 +22,7 @@ import org.custommonkey.xmlunit.XMLAssert;
import org.custommonkey.xmlunit.XMLUnit;
public class XmlStaxJobFunctionalTests extends AbstractLifecycleSpringContextTests {
public class XmlStaxJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
private static final String OUTPUT_FILE = "20070918.testStream.xmlFileStep.output.xml";
private static final String EXPECTED_OUTPUT_FILE = "src/main/resources/data/staxJob/output/expected-output.xml";