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 3ee9db131..8038004fc 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 @@ -32,6 +32,50 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.Assert; /** + *

Basic Launcher for starting jobs from the command line. In general, + * it is assumed that this launcher will primarily be used to start + * a job via a script from an Enterprise Scheduler. Therefore, exit codes + * are mapped to integers so that schedulers can use the returned values to + * determine the next course of action. The returned values can also + * be useful to operations teams in determining what should happen upon failure. + * For example, a returned code of 5 might mean that some resource wasn't available + * and the job should be restarted. However, a code of 10 might mean that something + * critical has happened and the issue should be escalated.

+ * + *

With any launch of a batch job within Spring Batch, a minimum of two contexts + * must be loaded. One is the context containing the JobConfiguration, the other + * contains the 'Execution Environment'. That is, the JobExecutorFacade (which + * contains all the executors, plus the repository), the JobIdentifierFactory, and + * a normal JobLauncher. This command line launcher loads these application contexts + * by first loading the execution environment context via a + * {@link ContextSingletonBeanFactoryLocator}, which will search for the default + * key from classpath*:beanRefContext.xml to return the context. This will then + * be used as the parent to the JobConfiguration context. All required dependencies + * of the launcher will then be satisfied by autowiring by type from the combined + * application context. Default values are provided for all fields except the JobLauncher. + * Therefore, if autowiring fails to set it (it should be noted that dependency checking + * is disabled because most of the fields have default values and thus don't require + * dependencies to be fulfilled via autowiring) then an exception will be thrown. + * It should be noted that even if an exception is thrown by this class, it will be + * mapped to an integer and returned.

+ * + *

One odd field might be noticed in the launcher, SystemExiter. This class is + * used to exit from the main method, rather than calling System.exit directly. This + * is because unit testing a class the calls System.exit() is impossible without + * kicking off the test within a new Jvm, which it is possible to do, however it is a + * complex solution, much more so than strategizing the exiter.

+ * + *

VM Arguments vs. Program arguments: Because all of the arguments to the main + * method are optional, VM arguments are used: + * + *

+ * * @author Dave Syer * @author Lucas Ward * @since 2.1 @@ -113,6 +157,8 @@ public class BatchCommandLineLauncher { * the path to a Spring context configuration for this job * @param jobName * the name of the job execution to use + * @parm parentKey the key to be loaded by ContextSingletonBeanFactoryLocator and + * used as the parent context. * @throws NoSuchJobConfigurationException * @throws IllegalStateException * if JobLauncher is not autowired by the ApplicationContext @@ -171,18 +217,17 @@ public class BatchCommandLineLauncher { /** * Launch a batch job using a {@link BatchCommandLineLauncher}. Creates a * new Spring context for the job execution, and uses a common parent for - * all such contexts. + * all such contexts. No exception should be thrown from this method, rather + * exceptions should be logged and an integer returned. * * @param args - *
    - *
  1. classpath location of resource to load job configuration - * context (default "job-configuration.xml");
  2. - *
  3. runtime name of Job (default "job-execution-id").
  4. - *
  5. parent context key for use in pulling the correct - * beanFactory from the beanRefContext.xml (@see - * ContextSingletonBeanFactoryLocator)
  6. - *
- * @throws NoSuchJobConfigurationException + * */ public static void main(String[] args) { diff --git a/execution/src/main/java/org/springframework/batch/execution/repository/SimpleJobRepository.java b/execution/src/main/java/org/springframework/batch/execution/repository/SimpleJobRepository.java index 6444b5c0c..3b063924b 100644 --- a/execution/src/main/java/org/springframework/batch/execution/repository/SimpleJobRepository.java +++ b/execution/src/main/java/org/springframework/batch/execution/repository/SimpleJobRepository.java @@ -19,6 +19,7 @@ package org.springframework.batch.execution.repository; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Properties; import org.springframework.batch.core.configuration.JobConfiguration; import org.springframework.batch.core.configuration.StepConfiguration; @@ -32,21 +33,22 @@ import org.springframework.batch.core.repository.NoSuchBatchDomainObjectExceptio import org.springframework.batch.core.runtime.JobIdentifier; import org.springframework.batch.execution.repository.dao.JobDao; import org.springframework.batch.execution.repository.dao.StepDao; +import org.springframework.batch.restart.GenericRestartData; import org.springframework.util.Assert; /** - * + * *

* Simple Job Repository that stores Jobs, JobExecutions, Steps, and * StepExecutions using the provided JobDao and StepDao. *

- * + * * @author Lucas Ward * @author Dave Syer * @see JobRepository * @see StepDao * @see JobDao - * + * */ public class SimpleJobRepository implements JobRepository { @@ -62,15 +64,15 @@ public class SimpleJobRepository implements JobRepository { /** *

- * Find or Create a Job(@link Job) based on the passed in RuntimeInformation - * and Configuration. JobRuntimeInformation contains the following fields - * which logically identify a job: JobName, JobStream, JobRun, and Schedule - * Date. However, unique identification of a job can only come from the - * database, and therefore must come from JobDao by either creating a new - * job or finding an existing one, which will ensure that the id field of - * the job is populated with the correct value. + * Find or Create a JobInstance(@link JobInstance) based on the passed in + * RuntimeInformation and Configuration. JobRuntimeInformation contains the + * following fields which logically identify a job: JobName, JobStream, + * JobRun, and Schedule Date. However, unique identification of a job can + * only come from the database, and therefore must come from JobDao by + * either creating a new job or finding an existing one, which will ensure + * that the id field of the job is populated with the correct value. *

- * + * *

* There are two ways in which the method determines if a job should be * created or an existing one should be returned. The first is @@ -81,9 +83,8 @@ public class SimpleJobRepository implements JobRepository { * (there must be at least 1) and it will be returned. If no job is found, a * new one will be created based on the configuration. *

- * - * @see JobRepository#findOrCreateJob(JobConfiguration, - * JobIdentifier) + * + * @see JobRepository#findOrCreateJob(JobConfiguration, JobIdentifier) */ public JobInstance findOrCreateJob(JobConfiguration jobConfiguration, JobIdentifier runtimeInformation) { @@ -126,7 +127,7 @@ public class SimpleJobRepository implements JobRepository { * identifer, because it must be updatable separately. If an id isn't found, * a new JobExecution is created, if one is found, the current row is * updated. - * + * * @param JobExecution to be stored. * @throws IllegalArgumentException if jobExecution is null. */ @@ -149,7 +150,7 @@ public class SimpleJobRepository implements JobRepository { * Update an existing job. A job must have been obtained from the * findOrCreateJob method, otherwise it is likely that the id is incorrect * or non-existant. - * + * * @param job to be updated. * @throws IllegalArgumentException if Job or it's Id is null. */ @@ -167,7 +168,7 @@ public class SimpleJobRepository implements JobRepository { * saved and an id will be set, otherwise it will be updated. It should be * noted that assigning an ID randomly will likely cause an exception * depending on the StepDao implementation. - * + * * @param StepExecution to be saved. * @throws IllegalArgumentException if stepExecution is null. */ @@ -188,7 +189,7 @@ public class SimpleJobRepository implements JobRepository { /** * Update the given step. - * + * * @param StepInstance to be updated. * @throws IllegalArgumentException if step or it's id is null. */ @@ -224,6 +225,10 @@ public class SimpleJobRepository implements JobRepository { while (i.hasNext()) { StepConfiguration stepConfiguration = (StepConfiguration) i.next(); StepInstance step = stepDao.createStep(job, stepConfiguration.getName()); + //Ensure valid restart data is being returned. + if(step.getRestartData() == null || step.getRestartData().getProperties() == null){ + step.setRestartData(new GenericRestartData(new Properties())); + } steps.add(step); } @@ -243,7 +248,10 @@ public class SimpleJobRepository implements JobRepository { if (step != null) { step.setStepExecutionCount(stepDao.getStepExecutionCount(step.getId())); - + //Ensure valid restart data is being returned. + if(step.getRestartData() == null || step.getRestartData().getProperties() == null){ + step.setRestartData(new GenericRestartData(new Properties())); + } steps.add(step); } } diff --git a/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java b/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java index 18bca55fb..694b81caa 100644 --- a/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java +++ b/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java @@ -61,15 +61,15 @@ import org.springframework.util.Assert; * operations should not do any concurrent execution. N.B. usually that means * that the chunk operations should be a {@link RepeatTemplate} (which is the * default).
- * + * * Clients can use interceptors in the step operations to intercept or listen to * the iteration on a step-wide basis, for instance to get a callback when the * step is complete. Those that want callbacks at the level of an individual * tasks, can specify interceptors for the chunk operations. - * + * * @author Dave Syer * @author Lucas Ward - * + * */ public class SimpleStepExecutor implements StepExecutor { @@ -95,7 +95,7 @@ public class SimpleStepExecutor implements StepExecutor { private RepeatOperations stepOperations = new RepeatTemplate(); private JobRepository jobRepository; - + private ExitCodeExceptionClassifier exceptionClassifier = new SimpleExitCodeExceptionClassifier(); // default to checking current thread for interruption. @@ -155,6 +155,7 @@ public class SimpleStepExecutor implements StepExecutor { throws BatchCriticalException, StepInterruptedException { final StepInstance step = stepExecutionContext.getStep(); + boolean isRestart = step.getStepExecutionCount() > 0 ? true : false; Assert.notNull(step); final StepExecution stepExecution = stepExecutionContext.getStepExecution(); @@ -171,7 +172,7 @@ public class SimpleStepExecutor implements StepExecutor { final boolean saveRestartData = ((AbstractStepConfiguration) configuration).isSaveRestartData(); - if (saveRestartData) { + if (saveRestartData && isRestart) { restoreFromRestartData(module, step.getRestartData()); } @@ -253,7 +254,7 @@ public class SimpleStepExecutor implements StepExecutor { return status; } catch (RuntimeException e) { - + //classify exception so an exit code can be stored. status = exceptionClassifier.classifyForExitCode(e); stepExecution.setException(e); @@ -265,7 +266,7 @@ public class SimpleStepExecutor implements StepExecutor { updateStatus(stepExecutionContext, BatchStatus.FAILED); throw e; } - + } finally { stepExecution.setExitCode(status.getExitCode()); @@ -311,7 +312,7 @@ public class SimpleStepExecutor implements StepExecutor { * transaction. The transaction is programmatically started and stopped * outside this method, so subclasses that override do not need to create a * transaction. - * + * * @param configuration the current step configuration * @param stepExecutionContext the current step, containing the * {@link Tasklet} with the business logic. @@ -384,11 +385,11 @@ public class SimpleStepExecutor implements StepExecutor { public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) { this.interruptionPolicy = interruptionPolicy; } - + /** * Setter for the {@link ExitCodeExceptionClassifier} that will be used * to classify any exception that causes a job to fail. - * + * * @param exceptionClassifier */ public void setExceptionClassifier( diff --git a/execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java b/execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java index 433026d36..0d430c8d2 100644 --- a/execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/repository/SimpleJobRepositoryTests.java @@ -33,12 +33,13 @@ import org.springframework.batch.core.runtime.SimpleJobIdentifier; import org.springframework.batch.core.tasklet.Tasklet; import org.springframework.batch.execution.repository.dao.JobDao; import org.springframework.batch.execution.repository.dao.StepDao; +import org.springframework.batch.restart.GenericRestartData; /* * Test SimpleJobRepository. The majority of test cases are tested using EasyMock, - * however, there were some issues with using it for the stepDao when testing finding - * or creating steps, so an actual mock class had to be written. - * + * however, there were some issues with using it for the stepDao when testing finding + * or creating steps, so an actual mock class had to be written. + * * @author Lucas Ward * */ @@ -69,14 +70,14 @@ public class SimpleJobRepositoryTests extends TestCase { StepInstance databaseStep1; StepInstance databaseStep2; - + List steps; public void setUp() throws Exception { jobDao = (JobDao) jobDaoControl.getMock(); stepDao = (StepDao) stepDaoControl.getMock(); - + jobRepository = new SimpleJobRepository(jobDao, stepDao); jobRuntimeInformation = new SimpleJobIdentifier("RepositoryTest"); @@ -99,14 +100,17 @@ public class SimpleJobRepositoryTests extends TestCase { databaseStep1 = new StepInstance(new Long(1)); databaseStep2 = new StepInstance(new Long(2)); - + steps = new ArrayList(); steps.add(databaseStep1); steps.add(databaseStep2); } - + + /* + * Test a restartable job, that has not been run before. + */ public void testCreateRestartableJob(){ - + List jobs = new ArrayList(); jobDao.findJobs(jobRuntimeInformation); @@ -128,7 +132,7 @@ public class SimpleJobRepositoryTests extends TestCase { step = (StepInstance) it.next(); assertTrue(step.equals(databaseStep2)); } - + public void testRestartedJob(){ List jobs = new ArrayList(); jobDao.findJobs(jobRuntimeInformation); @@ -157,9 +161,9 @@ public class SimpleJobRepositoryTests extends TestCase { assertTrue(step.equals(databaseStep2)); assertTrue(step.getStepExecutionCount() == 1); } - + public void testCreateNonRestartableJob(){ - + List jobs = new ArrayList(); jobDao.findJobs(jobRuntimeInformation); @@ -247,7 +251,7 @@ public class SimpleJobRepositoryTests extends TestCase { stepDaoControl.replay(); jobRepository.update(step); } - + public void testUpdateStepExecution(){ StepExecution stepExecution = new StepExecution(new Long(10), null); stepExecution.setId(new Long(11)); @@ -256,10 +260,10 @@ public class SimpleJobRepositoryTests extends TestCase { jobRepository.saveOrUpdate(stepExecution); stepDaoControl.verify(); } - + public void testSaveStepExecution(){ StepExecution stepExecution = new StepExecution(new Long(10), null); - //TODO: Not sure why, but calling save on the EasyMock stepDao causes a NullPointerException + //TODO: Not sure why, but calling save on the EasyMock stepDao causes a NullPointerException // stepDao.save(stepExecution); // stepDaoControl.replay(); jobRepository.saveOrUpdate(stepExecution); @@ -280,6 +284,68 @@ public class SimpleJobRepositoryTests extends TestCase { } } + /* + * Test to ensure that if a StepDao returns invalid + * restart data, it is corrected. + */ + public void testCreateStepsFixesInvalidRestartData(){ + + List jobs = new ArrayList(); + + jobDao.findJobs(jobRuntimeInformation); + jobDaoControl.setReturnValue(jobs); + jobDao.createJob(jobRuntimeInformation); + jobDaoControl.setReturnValue(databaseJob); + stepDao.createStep(databaseJob, "TestStep1"); + databaseStep1.setRestartData(null); + stepDaoControl.setReturnValue(databaseStep1); + stepDao.createStep(databaseJob, "TestStep2"); + databaseStep2.setRestartData(new GenericRestartData(null)); + stepDaoControl.setReturnValue(databaseStep2); + stepDaoControl.replay(); + jobDaoControl.replay(); + JobInstance job = jobRepository.findOrCreateJob(jobConfiguration, jobRuntimeInformation); + List jobSteps = job.getSteps(); + Iterator it = jobSteps.iterator(); + StepInstance step = (StepInstance) it.next(); + assertTrue(step.equals(databaseStep1)); + assertTrue(step.getRestartData().getProperties().isEmpty()); + step = (StepInstance) it.next(); + assertTrue(step.equals(databaseStep2)); + assertTrue(step.getRestartData().getProperties().isEmpty()); + } + + public void testFindStepsFixesInvalidRestartData(){ + List jobs = new ArrayList(); + jobDao.findJobs(jobRuntimeInformation); + jobs.add(databaseJob); + jobDaoControl.setReturnValue(jobs); + stepDao.findStep(databaseJob, "TestStep1"); + databaseStep1.setRestartData(null); + stepDaoControl.setReturnValue(databaseStep1); + stepDao.getStepExecutionCount(databaseStep1.getId()); + stepDaoControl.setReturnValue(1); + stepDao.findStep(databaseJob, "TestStep2"); + databaseStep2.setRestartData(new GenericRestartData(null)); + stepDaoControl.setReturnValue(databaseStep2); + stepDao.getStepExecutionCount(databaseStep2.getId()); + stepDaoControl.setReturnValue(1); + stepDaoControl.replay(); + jobDao.getJobExecutionCount(databaseJob.getId()); + jobDaoControl.setReturnValue(1); + jobDaoControl.replay(); + JobInstance job = jobRepository.findOrCreateJob(jobConfiguration, jobRuntimeInformation); + assertTrue(job.equals(databaseJob)); + List jobSteps = job.getSteps(); + Iterator it = jobSteps.iterator(); + StepInstance step = (StepInstance) it.next(); + assertTrue(step.equals(databaseStep1)); + assertTrue(step.getRestartData().getProperties().isEmpty()); + step = (StepInstance) it.next(); + assertTrue(step.getRestartData().getProperties().isEmpty()); + assertTrue(step.equals(databaseStep2)); + } + /** * @author Dave Syer * diff --git a/execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java b/execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java index 6551aa28c..f28c6dfcb 100644 --- a/execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/step/simple/DefaultStepExecutorTests.java @@ -42,6 +42,8 @@ import org.springframework.batch.repeat.ExitStatus; import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.batch.repeat.support.RepeatTemplate; +import org.springframework.batch.restart.RestartData; +import org.springframework.batch.restart.Restartable; public class DefaultStepExecutorTests extends TestCase { @@ -60,7 +62,7 @@ public class DefaultStepExecutorTests extends TestCase { private ItemProvider getProvider(String[] args) { return new ListItemProvider(Arrays.asList(args)); } - + /** * @param strings * @return @@ -131,7 +133,7 @@ public class DefaultStepExecutorTests extends TestCase { step.setStepExecution(new StepExecution(new Long(1),new Long(1))); final JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); final StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); - + stepConfiguration.setTasklet(new Tasklet() { public ExitStatus execute() throws Exception { assertEquals(step, stepExecutionContext.getStep()); @@ -159,66 +161,66 @@ public class DefaultStepExecutorTests extends TestCase { JobInstance job = new JobInstance(new Long(1)); job.setIdentifier(new SimpleJobIdentifier("foo_bar")); - + stepExecutor.process(stepConfiguration, stepExecutionContext); assertEquals(1, processed.size()); // assertEquals(1, repository.findJobs(job.?).size()); } - + public void testIncrementRollbackCount(){ - + Tasklet tasklet = new Tasklet(){ public ExitStatus execute() throws Exception { int counter = 0; counter++; - + if(counter == 1){ throw new Exception(); } - + return ExitStatus.CONTINUABLE; } - + }; - + StepInstance step = new StepInstance(new Long(1)); stepConfiguration.setTasklet(tasklet); JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); - + try{ stepExecutor.process(stepConfiguration, stepExecutionContext); } catch(Exception ex){ assertEquals(step.getStepExecution().getRollbackCount(), new Integer(1)); } - + } - + public void testExitCodeDefaultClassification(){ - + Tasklet tasklet = new Tasklet(){ public ExitStatus execute() throws Exception { int counter = 0; counter++; - + if(counter == 1){ throw new RuntimeException(); } - + return ExitStatus.CONTINUABLE; } - + }; - + StepInstance step = new StepInstance(new Long(1)); stepConfiguration.setTasklet(tasklet); JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); - + try{ stepExecutor.process(stepConfiguration, stepExecutionContext); } @@ -228,44 +230,163 @@ public class DefaultStepExecutorTests extends TestCase { } } + + /* + * make sure a job that has never been executed before, but does have + * saveRestartData = true, doesn't have restoreFrom called on it. + */ + public void testNonRestartedJob(){ + StepInstance step = new StepInstance(new Long(1)); + MockRestartableTasklet tasklet = new MockRestartableTasklet(); + stepConfiguration.setTasklet(tasklet); + stepConfiguration.setSaveRestartData(true); + JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); + StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); + + try{ + stepExecutor.process(stepConfiguration, stepExecutionContext); + }catch(Throwable t){ + fail(); + } + + assertFalse(tasklet.isRestoreFromCalled()); + assertTrue(tasklet.isGetRestartDataCalled()); + } + + /* + * make sure a job that has been executed before, and is therefore being restarted, + * is restored. + */ + public void testRestartedJob(){ + StepInstance step = new StepInstance(new Long(1)); + step.setStepExecutionCount(1); + MockRestartableTasklet tasklet = new MockRestartableTasklet(); + stepConfiguration.setTasklet(tasklet); + stepConfiguration.setSaveRestartData(true); + JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); + StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); + + try{ + stepExecutor.process(stepConfiguration, stepExecutionContext); + }catch(Throwable t){ + fail(); + } + + assertTrue(tasklet.isRestoreFromCalled()); + assertTrue(tasklet.isGetRestartDataCalled()); + } + + /* + * Test that a job that is being restarted, but has saveRestartData + * set to false, doesn't have restore or getRestartData called on it. + */ + public void testNoSaveRestartDataRestartableJob(){ + StepInstance step = new StepInstance(new Long(1)); + step.setStepExecutionCount(1); + MockRestartableTasklet tasklet = new MockRestartableTasklet(); + stepConfiguration.setTasklet(tasklet); + stepConfiguration.setSaveRestartData(false); + JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); + StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); + + try{ + stepExecutor.process(stepConfiguration, stepExecutionContext); + }catch(Throwable t){ + fail(); + } + + assertFalse(tasklet.isRestoreFromCalled()); + assertFalse(tasklet.isGetRestartDataCalled()); + } + + /* + * Even though the job is restarted, and saveRestartData is true, + * nothing will be restored because the Tasklet does not implement + * Restartable. + */ + public void testRestartJobOnNonRestartableTasklet(){ + StepInstance step = new StepInstance(new Long(1)); + step.setStepExecutionCount(1); + stepConfiguration.setTasklet(new Tasklet(){ + public ExitStatus execute() throws Exception { + return ExitStatus.FINISHED; + }}); + stepConfiguration.setSaveRestartData(true); + JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); + StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); + + try{ + stepExecutor.process(stepConfiguration, stepExecutionContext); + }catch(Throwable t){ + fail(); + } + } + + private class MockRestartableTasklet implements Tasklet, Restartable{ + + private boolean getRestartDataCalled = false; + private boolean restoreFromCalled = false; + + public ExitStatus execute() throws Exception { + return ExitStatus.FINISHED; + } + + public RestartData getRestartData() { + getRestartDataCalled = true; + return null; + } + + public void restoreFrom(RestartData data) { + restoreFromCalled = true; + } + + public boolean isGetRestartDataCalled() { + return getRestartDataCalled; + } + + public boolean isRestoreFromCalled() { + return restoreFromCalled; + } + } + /* * StepExecutor will never pass StepInterruptedException to the exceptionClassifier. - * This may or may not stay the same, so the test will remain commented out + * This may or may not stay the same, so the test will remain commented out * for reference purposes. */ /* public void testExitCodeInterruptedClassification(){ - + StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy(){ public void checkInterrupted(RepeatContext context) throws StepInterruptedException { throw new StepInterruptedException(""); } - + }; - + stepExecutor.setInterruptionPolicy(interruptionPolicy); - + Tasklet tasklet = new Tasklet(){ public ExitStatus execute() throws Exception { int counter = 0; counter++; - + if(counter == 1){ throw new StepInterruptedException(""); } - + return ExitStatus.CONTINUABLE; } - + }; - + StepInstance step = new StepInstance(new Long(1)); stepConfiguration.setTasklet(tasklet); JobExecutionContext jobExecutionContext = new JobExecutionContext(new SimpleJobIdentifier("FOO"), new JobInstance(new Long(3))); StepExecutionContext stepExecutionContext = new StepExecutionContext(jobExecutionContext, step); - + try{ stepExecutor.process(stepConfiguration, stepExecutionContext); }