diff --git a/core/src/main/java/org/springframework/batch/core/domain/Entity.java b/core/src/main/java/org/springframework/batch/core/domain/Entity.java index 8a4d34aad..cd9cbfadf 100644 --- a/core/src/main/java/org/springframework/batch/core/domain/Entity.java +++ b/core/src/main/java/org/springframework/batch/core/domain/Entity.java @@ -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()); } /** diff --git a/core/src/main/java/org/springframework/batch/core/executor/JobExecutionException.java b/core/src/main/java/org/springframework/batch/core/executor/JobExecutionException.java new file mode 100644 index 000000000..66782e6fa --- /dev/null +++ b/core/src/main/java/org/springframework/batch/core/executor/JobExecutionException.java @@ -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); + } +} diff --git a/core/src/main/java/org/springframework/batch/core/executor/StepInterruptedException.java b/core/src/main/java/org/springframework/batch/core/executor/StepInterruptedException.java index 7d84f6a1a..83b2451ae 100644 --- a/core/src/main/java/org/springframework/batch/core/executor/StepInterruptedException.java +++ b/core/src/main/java/org/springframework/batch/core/executor/StepInterruptedException.java @@ -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); diff --git a/core/src/test/java/org/springframework/batch/core/domain/EntityTests.java b/core/src/test/java/org/springframework/batch/core/domain/EntityTests.java index 9ecc84e2f..8667c1e7b 100644 --- a/core/src/test/java/org/springframework/batch/core/domain/EntityTests.java +++ b/core/src/test/java/org/springframework/batch/core/domain/EntityTests.java @@ -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)}. */ diff --git a/core/src/test/java/org/springframework/batch/core/executor/JobExecutionExceptionTests.java b/core/src/test/java/org/springframework/batch/core/executor/JobExecutionExceptionTests.java new file mode 100644 index 000000000..7f50c5802 --- /dev/null +++ b/core/src/test/java/org/springframework/batch/core/executor/JobExecutionExceptionTests.java @@ -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); + } + +} diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncher.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncher.java index 3c81959d9..50642da19 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncher.java @@ -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) { diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ThreadInterruptJobExecutionListener.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ThreadInterruptJobExecutionListener.java index 97e12007b..19f847a0b 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ThreadInterruptJobExecutionListener.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/support/ThreadInterruptJobExecutionListener.java @@ -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(); } diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningException.java b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningException.java new file mode 100644 index 000000000..18e538e2a --- /dev/null +++ b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningException.java @@ -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); + } + +} diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListener.java b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListener.java index 65c41e06c..c440ca1eb 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListener.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListener.java @@ -46,5 +46,5 @@ public interface JobExecutionListener { * * @param execution */ - void stop(JobExecution execution); + void onStop(JobExecution execution); } diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListenerSupport.java b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListenerSupport.java index 52dfcb22e..0cb1bf0d1 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListenerSupport.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutionListenerSupport.java @@ -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 } diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutorFacade.java b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutorFacade.java index ac20266fb..8bd32ad94 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutorFacade.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/JobExecutorFacade.java @@ -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. * diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/JobLauncher.java b/execution/src/main/java/org/springframework/batch/execution/launch/JobLauncher.java index e27070c3f..09e7fa6c1 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/JobLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/JobLauncher.java @@ -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 diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacade.java b/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacade.java index 804dff00f..436e79a60 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacade.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacade.java @@ -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() { diff --git a/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobLauncher.java b/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobLauncher.java index afa003e41..1d1cee752 100644 --- a/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/launch/SimpleJobLauncher.java @@ -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 diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncherTests.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncherTests.java index 816fd3278..3aba37af1 100644 --- a/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncherTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/BatchCommandLineLauncherTests.java @@ -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[])}. diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/StubJobLauncher.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/StubJobLauncher.java index ee6a9cfe2..72dfebc78 100644 --- a/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/StubJobLauncher.java +++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/StubJobLauncher.java @@ -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; } diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/InterruptJobTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/InterruptJobTests.java index 243b60d29..1fe20a728 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/InterruptJobTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/InterruptJobTests.java @@ -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"); } diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningExceptionTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningExceptionTests.java new file mode 100644 index 000000000..6e769f06b --- /dev/null +++ b/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionAlreadyRunningExceptionTests.java @@ -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); + } + +} diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionListenerSupportTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionListenerSupportTests.java index 1065194ef..22032ecc6 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionListenerSupportTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/JobExecutionListenerSupportTests.java @@ -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()); } } diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java index 7a9a6a531..711937f5b 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobExecutorFacadeTests.java @@ -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)); } diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobLauncherTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobLauncherTests.java index b834749be..6c91f6271 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobLauncherTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/SimpleJobLauncherTests.java @@ -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) { diff --git a/execution/src/test/java/org/springframework/batch/execution/launch/TaskExecutorJobLauncherTests.java b/execution/src/test/java/org/springframework/batch/execution/launch/TaskExecutorJobLauncherTests.java index 8bebe89e4..acc1b7117 100644 --- a/execution/src/test/java/org/springframework/batch/execution/launch/TaskExecutorJobLauncherTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/launch/TaskExecutorJobLauncherTests.java @@ -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(); diff --git a/samples/src/test/java/org/springframework/batch/sample/AbstractCustomerCreditIncreaseTests.java b/samples/src/test/java/org/springframework/batch/sample/AbstractCustomerCreditIncreaseTests.java index 7e1523aee..d7b3646e1 100644 --- a/samples/src/test/java/org/springframework/batch/sample/AbstractCustomerCreditIncreaseTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/AbstractCustomerCreditIncreaseTests.java @@ -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; diff --git a/samples/src/test/java/org/springframework/batch/sample/AbstractLifecycleSpringContextTests.java b/samples/src/test/java/org/springframework/batch/sample/AbstractValidatingBatchLauncherTests.java similarity index 93% rename from samples/src/test/java/org/springframework/batch/sample/AbstractLifecycleSpringContextTests.java rename to samples/src/test/java/org/springframework/batch/sample/AbstractValidatingBatchLauncherTests.java index 65e07d39c..02395567b 100644 --- a/samples/src/test/java/org/springframework/batch/sample/AbstractLifecycleSpringContextTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/AbstractValidatingBatchLauncherTests.java @@ -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(); diff --git a/samples/src/test/java/org/springframework/batch/sample/BeanWrapperMapperSampleJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/BeanWrapperMapperSampleJobFunctionalTests.java index 32c8fff6a..a05a5f130 100644 --- a/samples/src/test/java/org/springframework/batch/sample/BeanWrapperMapperSampleJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/BeanWrapperMapperSampleJobFunctionalTests.java @@ -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"}; diff --git a/samples/src/test/java/org/springframework/batch/sample/CompositeProcessorSampleFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/CompositeProcessorSampleFunctionalTests.java index 178ce0fa8..0580c8ec3 100644 --- a/samples/src/test/java/org/springframework/batch/sample/CompositeProcessorSampleFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/CompositeProcessorSampleFunctionalTests.java @@ -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"; diff --git a/samples/src/test/java/org/springframework/batch/sample/FixedLengthImportJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/FixedLengthImportJobFunctionalTests.java index 0a7680e6d..217c4f75d 100644 --- a/samples/src/test/java/org/springframework/batch/sample/FixedLengthImportJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/FixedLengthImportJobFunctionalTests.java @@ -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; diff --git a/samples/src/test/java/org/springframework/batch/sample/MultilineJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/MultilineJobFunctionalTests.java index 4e172c071..93262f607 100644 --- a/samples/src/test/java/org/springframework/batch/sample/MultilineJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/MultilineJobFunctionalTests.java @@ -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]]" + diff --git a/samples/src/test/java/org/springframework/batch/sample/MultilineOrderJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/MultilineOrderJobFunctionalTests.java index 5715b3a52..a3151a624 100644 --- a/samples/src/test/java/org/springframework/batch/sample/MultilineOrderJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/MultilineOrderJobFunctionalTests.java @@ -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 = diff --git a/samples/src/test/java/org/springframework/batch/sample/NflJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/NflJobFunctionalTests.java index ed248bc0c..35b00f469 100644 --- a/samples/src/test/java/org/springframework/batch/sample/NflJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/NflJobFunctionalTests.java @@ -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 { diff --git a/samples/src/test/java/org/springframework/batch/sample/TradeJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/TradeJobFunctionalTests.java index 7bea42d1b..f9f941da4 100644 --- a/samples/src/test/java/org/springframework/batch/sample/TradeJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/TradeJobFunctionalTests.java @@ -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"; diff --git a/samples/src/test/java/org/springframework/batch/sample/XmlStaxJobFunctionalTests.java b/samples/src/test/java/org/springframework/batch/sample/XmlStaxJobFunctionalTests.java index 9b2f2e1cf..24bdc95bd 100644 --- a/samples/src/test/java/org/springframework/batch/sample/XmlStaxJobFunctionalTests.java +++ b/samples/src/test/java/org/springframework/batch/sample/XmlStaxJobFunctionalTests.java @@ -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";