diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/AbstractJobLauncher.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/AbstractJobLauncher.java index d224747b4..9bdb1a746 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/AbstractJobLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/AbstractJobLauncher.java @@ -52,7 +52,7 @@ public abstract class AbstractJobLauncher implements JobLauncher, private static final Log logger = LogFactory .getLog(AbstractJobLauncher.class); - protected JobExecutorFacade batchContainer; + protected JobExecutorFacade jobExecutorFacade; private String jobConfigurationName; @@ -103,8 +103,8 @@ public abstract class AbstractJobLauncher implements JobLauncher, * * @param batchContainer */ - public void setJobExecutorFacade(JobExecutorFacade batchContainer) { - this.batchContainer = batchContainer; + public void setJobExecutorFacade(JobExecutorFacade jobExecutorFacade) { + this.jobExecutorFacade = jobExecutorFacade; } /** @@ -114,7 +114,7 @@ public abstract class AbstractJobLauncher implements JobLauncher, * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() throws Exception { - Assert.notNull(batchContainer); + Assert.notNull(jobExecutorFacade); } /** @@ -168,7 +168,7 @@ public abstract class AbstractJobLauncher implements JobLauncher, throws NoSuchJobConfigurationException { synchronized (monitor) { - if (isRunning(jobIdentifier)) { + if (isInternalRunning(jobIdentifier)) { return ExitStatus.RUNNING; } } @@ -181,10 +181,9 @@ public abstract class AbstractJobLauncher implements JobLauncher, }); /* - * Subclasses have to take care of unregistering the jobIdentifier - if - * we do it here and doRun() is implemented to return immediately - * without waiting for the job to finish, then we will have a job - * running that is not in the registry. + * Subclasses don't explicitly have to take care of unregistering the + * jobIdentifier - they just have to call the exitCallback when the job + * is finished. */ } @@ -285,8 +284,9 @@ public abstract class AbstractJobLauncher implements JobLauncher, this.stop(jobIdentifierFactory.getJobIdentifier(name)); } - /* - * (non-Javadoc) + /** + * Check each registered {@link JobIdentifier} to see if it is running (@see + * {@link #isRunning(JobIdentifier)}), and if any are, then return true. * * @see org.springframework.batch.container.bootstrap.BatchContainerLauncher#isRunning() */ @@ -294,19 +294,31 @@ public abstract class AbstractJobLauncher implements JobLauncher, Collection jobs = new HashSet(registry.keySet()); for (Iterator iter = jobs.iterator(); iter.hasNext();) { JobIdentifier jobIdentifier = (JobIdentifier) iter.next(); - if (!isRunning(jobIdentifier)) { - return false; + if (isInternalRunning(jobIdentifier)) { + return true; } } return !jobs.isEmpty(); } - protected boolean isRunning(JobIdentifier jobIdentifier) { - synchronized (registry) { - return registry.get(jobIdentifier) != null; + private boolean isInternalRunning(JobIdentifier jobIdentifier) { + synchronized(registry) { + return isRunning(jobIdentifier) && registry.containsKey(jobIdentifier); } } + /** + * Extension point for subclasses to check an individual + * {@link JobIdentifier} to see if it is running. As long as at least one + * job is running the launcher is deemed to be running. + * + * @param jobIdentifier a {@link JobIdentifier} + * @return always true. Subclasses can override and provide more accurate information. + */ + protected boolean isRunning(JobIdentifier jobIdentifier) { + return true; + } + /** * Convenient synchronized accessor for the registry. Can be used by * subclasses if necessary (but it isn't likely). diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncher.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncher.java index b6958073d..48c07d688 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncher.java @@ -16,12 +16,10 @@ package org.springframework.batch.execution.bootstrap; -import org.springframework.batch.core.configuration.JobConfiguration; import org.springframework.batch.core.configuration.NoSuchJobConfigurationException; import org.springframework.batch.core.domain.JobIdentifier; -import org.springframework.batch.core.runtime.JobIdentifierFactory; import org.springframework.batch.execution.facade.JobExecutorFacade; -import org.springframework.batch.execution.runtime.ScheduledJobIdentifierFactory; +import org.springframework.batch.execution.facade.NoSuchJobExecutionException; import org.springframework.batch.repeat.ExitStatus; import org.springframework.util.Assert; @@ -39,144 +37,65 @@ import org.springframework.util.Assert; * @author Dave Syer * @since 2.1 */ -public class SimpleJobLauncher implements JobLauncher { - +public class SimpleJobLauncher extends AbstractJobLauncher { + private volatile Thread processingThread; - - private volatile boolean running = false; - - private JobExecutorFacade jobExecutorFacade; - - private JobIdentifierFactory jobIdentifierFactory = new ScheduledJobIdentifierFactory();; - - private String jobConfigurationName; - - /** - * Check that mandatory properties are set. - * - * @see #setJobExecutorFacade(JobExecutorFacade) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception { - Assert.notNull(jobExecutorFacade); - } + private int running = 0; /** - * Return whether or not the container is currently running. This is done by + * Check whether or not the container is currently running. This is done by * checking the thread to see if it is still alive. */ - public boolean isRunning() { - return running && processingThread != null && processingThread.isAlive(); + protected boolean isRunning(JobIdentifier jobIdentifier) { + return processingThread != null && processingThread.isAlive(); } /** - * Start the provided facade. The current thread will first be saved. - * This may seem odd at first, however, this simple bootstrap requires that - * only one thread can kick off a container, and that the first thread that - * calls start is the 'processing thread'. If the container has already been + * Start the provided facade. The current thread will first be saved. This + * may seem odd at first, however, this simple bootstrap requires that only + * one thread can kick off a container, and that the first thread that calls + * start is the 'processing thread'. If the container has already been * started, no exception will be thrown. - * @throws NoSuchJobConfigurationException + * + * @throws NoSuchJobConfigurationException * @see Lifecycle#start(). * - * @throws IllegalStateException if JobConfiguration is null. + * @throws IllegalStateException + * if JobConfiguration is null. */ - public ExitStatus run(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException { + protected ExitStatus doRun(JobIdentifier jobIdentifier, Runnable exitCallback) + throws NoSuchJobConfigurationException { Assert.notNull(jobIdentifier, "JobIdentifier must not be null."); - Assert.isTrue(!isRunning(), "SynchronousLaunchers can run only one job at at time."); - + Assert.isTrue(running==0, + "This launcher can run only one job at at time."); + /* * There is no reason to kick off a new thread, since only one thread - * should be processing at once. However, a handle to the thread should - * be maintained to allow for interrupt + * should be processing at once. However, a handle to the thread is + * maintained to allow for interrupt */ processingThread = Thread.currentThread(); - - running = true; try { + running++; return jobExecutorFacade.start(jobIdentifier); - } - finally { - running = false; + } finally { + running--; + exitCallback.run(); } } - - /** - * Start a job execution with the given name. If a job is already running - * has no effect. - * - * @param name the name to assign to the job - * @throws NoSuchJobConfigurationException - */ - public ExitStatus run(String name) throws NoSuchJobConfigurationException { - if (name==null) { - throw new NoSuchJobConfigurationException("Null job name cannot be located."); - } - JobIdentifier runtimeInformation = jobIdentifierFactory.getJobIdentifier(name); - return this.run(runtimeInformation); - } - - /** - * Start a job execution with default name and other runtime information - * provided by the factory. If a job is already running has no effect. The - * default name is taken from the enclosed {@link JobConfiguration}. - * @throws NoSuchJobConfigurationException if the job configuration cannot be located - * - * @see #setJobIdentifierFactory(JobIdentifierFactory) - * @see org.springframework.context.Lifecycle#start() - */ - public ExitStatus run() throws NoSuchJobConfigurationException{ - if (jobConfigurationName==null) { - throw new NoSuchJobConfigurationException("Null default job name cannot be located."); - } - return this.run(jobConfigurationName); - } /** - * Stop the job if it is running by interrupting its thread. If no job is - * running, no action will be taken. + * Interrupt the thread that is running the job. * - * (non-Javadoc) - * @see org.springframework.context.Lifecycle#stop() + * @see org.springframework.batch.execution.bootstrap.AbstractJobLauncher#doStop(org.springframework.batch.core.domain.JobIdentifier) */ - public void stop() { - + protected void doStop(JobIdentifier runtimeInformation) + throws NoSuchJobExecutionException { if (isRunning()) { processingThread.interrupt(); - running = false; } } - - /** - * The facade to which job launching will be delegated. - * - * @param jobExecutorFacade a {@link JobExecutorFacade}. - */ - public void setJobExecutorFacade(JobExecutorFacade jobExecutorFacade) { - this.jobExecutorFacade = jobExecutorFacade; - } - /** - * Public setter for injecting {@link JobIdentifierFactory}. When a job is - * launched by name the factory needs to be used to create a new identifier - * for it. Defaults to a {@link ScheduledJobIdentifierFactory}. - * - * @param jobIdentifierFactory - * the {@link JobIdentifierFactory} to use when constructing - * identifiers for jobs. - */ - public void setJobIdentifierFactory( - JobIdentifierFactory jobIdentifierFactory) { - this.jobIdentifierFactory = jobIdentifierFactory; - } - - /** - * Public setter for the default job configuration name to launch if none is specified. - * - * @param jobConfigurationName the name of a {@link JobConfiguration} in the registry. - */ - public void setJobConfigurationName(String jobConfigurationName) { - this.jobConfigurationName = jobConfigurationName; - } } diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/TaskExecutorJobLauncher.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/TaskExecutorJobLauncher.java index 52b1c77b2..a1608defa 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/TaskExecutorJobLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/TaskExecutorJobLauncher.java @@ -111,7 +111,7 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements taskExecutor.execute(new Runnable() { public void run() { try { - batchContainer.start(runtimeInformation); + jobExecutorFacade.start(runtimeInformation); } catch (NoSuchJobConfigurationException e) { applicationEventPublisher .publishEvent(new RepeatOperationsApplicationEvent( @@ -141,7 +141,7 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements */ protected void doStop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException { - batchContainer.stop(runtimeInformation); + jobExecutorFacade.stop(runtimeInformation); // TODO: wait for the jobs to stop? } @@ -181,8 +181,8 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements * jobs running it will be empty. */ public Properties getStatistics() { - if (batchContainer instanceof StatisticsProvider) { - return ((StatisticsProvider) batchContainer).getStatistics(); + if (jobExecutorFacade instanceof StatisticsProvider) { + return ((StatisticsProvider) jobExecutorFacade).getStatistics(); } else { return new Properties(); } diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncherTests.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncherTests.java index 60cbe6371..1dde4fd85 100644 --- a/execution/src/test/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncherTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/SimpleJobLauncherTests.java @@ -60,6 +60,7 @@ public class SimpleJobLauncherTests extends TestCase { launcher.setJobExecutorFacade(jobExecutorFacade); launcher.setJobConfigurationName(new JobConfiguration("foo").getName()); launcher.run(); + assertFalse(launcher.isRunning()); launcher.run(); // Both jobs finished running because they were not launched in a new // Thread