Refactored launching to allow exit codes to be returned to the launcher.
This commit is contained in:
@@ -18,6 +18,7 @@ package org.springframework.batch.execution;
|
||||
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* Interface which defines a facade for running jobs. The interface is
|
||||
@@ -40,7 +41,7 @@ public interface JobExecutorFacade {
|
||||
*
|
||||
* @throws NoSuchJobConfigurationException
|
||||
*/
|
||||
void start(JobIdentifier runtimeInformation) throws NoSuchJobConfigurationException;
|
||||
ExitStatus start(JobIdentifier runtimeInformation) throws NoSuchJobConfigurationException;
|
||||
|
||||
/**
|
||||
* Stop the job execution that was started with this runtime information.
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BatchCommandLineLauncher {
|
||||
|
||||
private ConfigurableApplicationContext parent;
|
||||
|
||||
private JobLauncher launcher;
|
||||
private SynchronousJobLauncher launcher;
|
||||
|
||||
/**
|
||||
* Default constructor for the launcher. Sets up the parent context to use
|
||||
@@ -51,7 +51,7 @@ public class BatchCommandLineLauncher {
|
||||
*
|
||||
* @param launcher the launcher to set
|
||||
*/
|
||||
public void setLauncher(JobLauncher launcher) {
|
||||
public void setLauncher(SynchronousJobLauncher launcher) {
|
||||
this.launcher = launcher;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ public class BatchCommandLineLauncher {
|
||||
try {
|
||||
if (!launcher.isRunning()) {
|
||||
if (jobName == null) {
|
||||
launcher.start();
|
||||
launcher.run();
|
||||
}
|
||||
else {
|
||||
launcher.start(jobName);
|
||||
launcher.run(jobName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,17 @@
|
||||
|
||||
package org.springframework.batch.execution.bootstrap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.configuration.JobConfiguration;
|
||||
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
|
||||
import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.core.runtime.JobIdentifierFactory;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.runtime.ScheduledJobIdentifierFactory;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple bootstrapping mechanism for running a single job execution in a
|
||||
@@ -36,11 +43,29 @@ import org.springframework.context.Lifecycle;
|
||||
* @author Dave Syer
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SimpleJobLauncher extends AbstractJobLauncher {
|
||||
public class SimpleJobLauncher implements SynchronousJobLauncher {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SimpleJobLauncher.class);
|
||||
|
||||
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 #setBatchContainer(JobExecutorFacade)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(jobExecutorFacade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the container is currently running. This is done by
|
||||
@@ -51,7 +76,7 @@ public class SimpleJobLauncher extends AbstractJobLauncher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the provided container. The current thread will first be saved.
|
||||
* 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
|
||||
@@ -61,26 +86,64 @@ public class SimpleJobLauncher extends AbstractJobLauncher {
|
||||
*
|
||||
* @throws IllegalStateException if JobConfiguration is null.
|
||||
*/
|
||||
protected void doStart(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException {
|
||||
public ExitStatus run(JobIdentifier jobIdentifier) throws NoSuchJobConfigurationException {
|
||||
|
||||
Assert.notNull(jobIdentifier, "JobIdentifier must not be null.");
|
||||
Assert.isTrue(!isRunning(), "SynchronousLaunchers 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
|
||||
*/
|
||||
processingThread = Thread.currentThread();
|
||||
// TODO: push this out to a method call in parent inside synchronized
|
||||
// block?
|
||||
|
||||
running = true;
|
||||
try {
|
||||
batchContainer.start(jobIdentifier);
|
||||
return jobExecutorFacade.start(jobIdentifier);
|
||||
}
|
||||
finally {
|
||||
running = false;
|
||||
unregister(jobIdentifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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 #setJobRuntimeInformationFactory(JobIdentifierFactory)
|
||||
* @see org.springframework.context.Lifecycle#start()
|
||||
*/
|
||||
public ExitStatus run(){
|
||||
if (jobConfigurationName==null) {
|
||||
return new ExitStatus(false, "JOB_CONFIGURATION_NOT_PROVIDED", "No JobConfiguration was " +
|
||||
"provided to the launcher.");
|
||||
}
|
||||
try {
|
||||
return this.run(jobConfigurationName);
|
||||
}
|
||||
catch (NoSuchJobConfigurationException e) {
|
||||
logger.error("JobExecutorFacade failed to find a JobConfiguration" +
|
||||
" for the provided JobIdentifier", e);
|
||||
return new ExitStatus(false, "NO_SUCH_JOB_CONFIGURATION", "JobExecutor Facade failed" +
|
||||
"to find a JobConfiguration for the provided JobIdentifier");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the job if it is running by interrupting its thread. If no job is
|
||||
@@ -89,22 +152,24 @@ public class SimpleJobLauncher extends AbstractJobLauncher {
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
protected void doStop() {
|
||||
public void stop() {
|
||||
|
||||
if (isRunning()) {
|
||||
processingThread.interrupt();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #doStop()}. Since there is only one job running in
|
||||
* this launcher this is OK.
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.Lifecycle#stop()
|
||||
*/
|
||||
protected void doStop(JobIdentifier runtimeInformation) {
|
||||
doStop();
|
||||
|
||||
public void setJobExecutorFacade(JobExecutorFacade jobExecutorFacade) {
|
||||
this.jobExecutorFacade = jobExecutorFacade;
|
||||
}
|
||||
|
||||
public void setJobIdentifierFactory(
|
||||
JobIdentifierFactory jobIdentifierFactory) {
|
||||
this.jobIdentifierFactory = jobIdentifierFactory;
|
||||
}
|
||||
|
||||
public void setJobConfigurationName(String jobConfigurationName) {
|
||||
this.jobConfigurationName = jobConfigurationName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.batch.core.runtime.JobIdentifier;
|
||||
import org.springframework.batch.execution.JobExecutorFacade;
|
||||
import org.springframework.batch.execution.NoSuchJobExecutionException;
|
||||
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.util.Assert;
|
||||
@@ -113,7 +114,7 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, StatisticsPro
|
||||
* null
|
||||
*
|
||||
*/
|
||||
public void start(JobIdentifier jobRuntimeInformation) throws NoSuchJobConfigurationException {
|
||||
public ExitStatus start(JobIdentifier jobRuntimeInformation) throws NoSuchJobConfigurationException {
|
||||
|
||||
Assert.notNull(jobRuntimeInformation, "JobRuntimeInformation must not be null.");
|
||||
Assert.notNull(jobRuntimeInformation.getName(), "JobRuntimeInformation name must not be null.");
|
||||
@@ -129,11 +130,13 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, StatisticsPro
|
||||
|
||||
final JobInstance job = jobRepository.findOrCreateJob(jobConfiguration, jobRuntimeInformation);
|
||||
JobExecutionContext jobExecutionContext = jobExecutionRegistry.register(jobRuntimeInformation, job);
|
||||
|
||||
ExitStatus exitStatus = ExitStatus.FAILED;
|
||||
try {
|
||||
synchronized (mutex) {
|
||||
running++;
|
||||
}
|
||||
jobExecutor.run(jobConfiguration, jobExecutionContext);
|
||||
exitStatus = jobExecutor.run(jobConfiguration, jobExecutionContext);
|
||||
}
|
||||
finally {
|
||||
synchronized (mutex) {
|
||||
@@ -144,6 +147,7 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, StatisticsPro
|
||||
jobExecutionRegistry.unregister(jobRuntimeInformation);
|
||||
}
|
||||
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -51,7 +51,7 @@ public class DefaultJobExecutor implements JobExecutor {
|
||||
|
||||
private StepExecutorFactory stepExecutorResolver = new DefaultStepExecutorFactory();
|
||||
|
||||
public void run(JobConfiguration configuration, JobExecutionContext jobExecutionContext)
|
||||
public ExitStatus run(JobConfiguration configuration, JobExecutionContext jobExecutionContext)
|
||||
throws BatchCriticalException {
|
||||
|
||||
JobInstance job = jobExecutionContext.getJob();
|
||||
@@ -90,6 +90,8 @@ public class DefaultJobExecutor implements JobExecutor {
|
||||
jobExecution.setExitCode(status.getExitCode());
|
||||
jobRepository.saveOrUpdate(jobExecution);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private void updateStatus(JobExecutionContext jobExecutionContext, BatchStatus status) {
|
||||
|
||||
@@ -227,7 +227,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
|
||||
Object[] parameters = new Object[] { stepExecution.getId(), new Long(0), stepExecution.getStepId(), stepExecution.getJobExecutionId(),
|
||||
stepExecution.getStartTime(), stepExecution.getEndTime(), stepExecution.getStatus().toString(),
|
||||
stepExecution.getCommitCount(), stepExecution.getTaskCount(),
|
||||
PropertiesConverter.propertiesToString(stepExecution.getStatistics()), new Integer(stepExecution.getExitCode()) };
|
||||
PropertiesConverter.propertiesToString(stepExecution.getStatistics()), stepExecution.getExitCode() };
|
||||
jdbcTemplate.update(SAVE_STEP_EXECUTION, parameters);
|
||||
|
||||
}
|
||||
@@ -252,7 +252,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
|
||||
Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
|
||||
stepExecution.getStatus().toString(), stepExecution.getCommitCount(),
|
||||
stepExecution.getTaskCount(), PropertiesConverter.propertiesToString(stepExecution.getStatistics()),
|
||||
new Integer(stepExecution.getExitCode()),
|
||||
stepExecution.getExitCode(),
|
||||
stepExecution.getId() };
|
||||
jdbcTemplate.update(UPDATE_STEP_EXECUTION, parameters);
|
||||
|
||||
@@ -292,7 +292,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
|
||||
stepExecution.setCommitCount(rs.getInt(6));
|
||||
stepExecution.setTaskCount(rs.getInt(7));
|
||||
stepExecution.setStatistics(PropertiesConverter.stringToProperties(rs.getString(8)));
|
||||
stepExecution.setExitCode(rs.getInt(9));
|
||||
stepExecution.setExitCode(rs.getString(9));
|
||||
return stepExecution;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.core.tasklet.Recoverable;
|
||||
import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
@@ -50,13 +51,13 @@ public class DefaultStepExecutor extends SimpleStepExecutor {
|
||||
* @see org.springframework.batch.execution.step.simple.SimpleStepExecutor#doTaskletProcessing(org.springframework.batch.core.tasklet.Tasklet,
|
||||
* org.springframework.batch.core.domain.StepInstance)
|
||||
*/
|
||||
protected boolean doTaskletProcessing(Tasklet module, final StepInstance step) throws Exception {
|
||||
protected ExitStatus doTaskletProcessing(Tasklet module, final StepInstance step) throws Exception {
|
||||
|
||||
boolean result = true;
|
||||
ExitStatus exitStatus = ExitStatus.CONTINUABLE;
|
||||
|
||||
try {
|
||||
|
||||
result = super.doTaskletProcessing(module, step);
|
||||
exitStatus = super.doTaskletProcessing(module, step);
|
||||
|
||||
}
|
||||
catch (final Exception e) {
|
||||
@@ -80,7 +81,7 @@ public class DefaultStepExecutor extends SimpleStepExecutor {
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -247,10 +247,13 @@ public class SimpleStepExecutor implements StepExecutor {
|
||||
});
|
||||
|
||||
stepExecution.setExitCode(status.getExitCode());
|
||||
stepExecution.setExitDescription(status.getExitDescription());
|
||||
updateStatus(stepExecutionContext, BatchStatus.COMPLETED);
|
||||
return status;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
|
||||
stepExecution.setException(e);
|
||||
if (e.getCause() instanceof StepInterruptedException) {
|
||||
updateStatus(stepExecutionContext, BatchStatus.STOPPED);
|
||||
throw (StepInterruptedException) e.getCause();
|
||||
@@ -320,11 +323,11 @@ public class SimpleStepExecutor implements StepExecutor {
|
||||
});
|
||||
// check for interruption before each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
boolean result = doTaskletProcessing(configuration.getTasklet(), stepExecutionContext.getStep());
|
||||
ExitStatus exitStatus = doTaskletProcessing(configuration.getTasklet(), stepExecutionContext.getStep());
|
||||
stepExecutionContext.getStepExecution().incrementTaskCount();
|
||||
// check for interruption after each item as well
|
||||
interruptionPolicy.checkInterrupted(context);
|
||||
return new ExitStatus(result);
|
||||
return exitStatus;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -338,7 +341,7 @@ public class SimpleStepExecutor implements StepExecutor {
|
||||
* @return boolean if there is more processing to do
|
||||
* @throws Exception if there is an error
|
||||
*/
|
||||
protected boolean doTaskletProcessing(Tasklet tasklet, StepInstance step) throws Exception {
|
||||
protected ExitStatus doTaskletProcessing(Tasklet tasklet, StepInstance step) throws Exception {
|
||||
return tasklet.execute();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.io.Skippable;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.retry.RetryOperations;
|
||||
@@ -132,14 +133,14 @@ public class ItemProviderProcessTasklet implements Tasklet, Recoverable, Skippab
|
||||
*
|
||||
* @see org.springframework.batch.core.tasklet.Tasklet#execute()
|
||||
*/
|
||||
public boolean execute() throws Exception {
|
||||
public ExitStatus execute() throws Exception {
|
||||
if (retryOperations != null) {
|
||||
return retryOperations.execute(new ItemProviderRetryCallback(itemProvider, itemProcessor)) != null;
|
||||
return new ExitStatus(retryOperations.execute(new ItemProviderRetryCallback(itemProvider, itemProcessor)) != null);
|
||||
}
|
||||
else {
|
||||
Object data = itemProvider.next();
|
||||
if (data == null) {
|
||||
return false;
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
RepeatContext context = RepeatSynchronizationManager.getContext();
|
||||
Assert.state(context != null,
|
||||
@@ -149,7 +150,7 @@ public class ItemProviderProcessTasklet implements Tasklet, Recoverable, Skippab
|
||||
// No exception so clear context (we can't recover directly because
|
||||
// the current transaction is going to roll back)
|
||||
context.removeAttribute(ITEM_KEY);
|
||||
return true;
|
||||
return ExitStatus.CONTINUABLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.batch.execution.tasklet;
|
||||
|
||||
import org.springframework.batch.core.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.ExitStatus;
|
||||
|
||||
/**
|
||||
* Provides the basic batch module for reading and processing data.
|
||||
@@ -39,12 +40,12 @@ public abstract class ReadProcessTasklet implements Tasklet {
|
||||
* from the abstract read method will be returned to the {@link Tasklet}, to indicate
|
||||
* whether or not processing should continue.
|
||||
*/
|
||||
public final boolean execute() throws Exception {
|
||||
public final ExitStatus execute() throws Exception {
|
||||
if (!read()) {
|
||||
return false;
|
||||
return ExitStatus.FINISHED;
|
||||
}
|
||||
process();
|
||||
return true;
|
||||
return ExitStatus.CONTINUABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user