diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java index e23766710..83432d3ed 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java @@ -34,7 +34,6 @@ package org.springframework.batch.support; import java.lang.reflect.Method; import java.util.Arrays; -import org.apache.commons.lang.builder.HashCodeBuilder; import org.springframework.aop.framework.Advised; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -145,6 +144,9 @@ public class SimpleMethodInvoker implements MethodInvoker { @Override public int hashCode() { - return new HashCodeBuilder(25, 37).append(object.hashCode()).append(method.hashCode()).toHashCode(); + int result = 25; + result = 31 * result + object.hashCode(); + result = 31 * result + method.hashCode(); + return result; } } diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java index edee01cf6..4083e0553 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/AbstractJobTests.java @@ -32,6 +32,7 @@ import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -81,14 +82,14 @@ public abstract class AbstractJobTests implements ApplicationContextAware { private StepRunner stepRunner; private ApplicationContext applicationContext; - + /** * {@inheritDoc} */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; + this.applicationContext = applicationContext; } - + /** * @return the applicationContext */ @@ -162,27 +163,58 @@ public abstract class AbstractJobTests implements ApplicationContextAware { } /** - * Launch just the specified step in the job. An IllegalStateException is - * thrown if there is no Step with the given name. + * Launch just the specified step in the job. A unique set of JobParameters + * will automatically be generated. An IllegalStateException is thrown if + * there is no Step with the given name. * - * @param stepName + * @param stepName The name of the step to launch * @return JobExecution */ public JobExecution launchStep(String stepName) { + return this.launchStep(stepName, this.getUniqueJobParameters(), null); + } + + /** + * Launch just the specified step in the job. A unique set of JobParameters + * will automatically be generated. An IllegalStateException is thrown if + * there is no Step with the given name. + * + * @param stepName The name of the step to launch + * @param jobExecutionContext An ExecutionContext whose values will be + * loaded into the Job ExecutionContext prior to launching the step. + * @return JobExecution + */ + public JobExecution launchStep(String stepName, ExecutionContext jobExecutionContext) { + return this.launchStep(stepName, this.getUniqueJobParameters(), jobExecutionContext); + } + + /** + * Launch just the specified step in the job. An IllegalStateException is + * thrown if there is no Step with the given name. + * + * @param stepName The name of the step to launch + * @param jobParameters The JobParameters to use during the launch + * @return JobExecution + */ + public JobExecution launchStep(String stepName, JobParameters jobParameters) { + return this.launchStep(stepName, jobParameters, null); + } + + /** + * Launch just the specified step in the job. An IllegalStateException is + * thrown if there is no Step with the given name. + * + * @param stepName The name of the step to launch + * @param jobParameters The JobParameters to use during the launch + * @param jobExecutionContext An ExecutionContext whose values will be + * loaded into the Job ExecutionContext prior to launching the step. + * @return JobExecution + */ + public JobExecution launchStep(String stepName, JobParameters jobParameters, ExecutionContext jobExecutionContext) { Step step = this.job.getStep(stepName); if (step == null) { throw new IllegalStateException("No Step found with name: [" + stepName + "]"); } - return getStepRunner().launchStep(step); - } - - /** - * Launch just the specified step in the job. - * - * @param stepName - * @param jobParameters - */ - public JobExecution launchStep(String stepName, JobParameters jobParameters) { - return getStepRunner().launchStep(this.job.getStep(stepName), jobParameters); + return getStepRunner().launchStep(step, jobParameters, jobExecutionContext); } } diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java index 3e239c1c8..47ee22b28 100755 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java @@ -26,66 +26,105 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.JobParameter; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.item.ExecutionContext; /** - * Utility class for executing steps outside of a {@link Job}. This is useful - * in end to end testing in order to allow for the testing of a step individually + * Utility class for executing steps outside of a {@link Job}. This is useful in + * end to end testing in order to allow for the testing of a step individually * without running every Step in a job. * * * - * It should be noted that any checked exceptions encountered while running the Step will wrapped with - * RuntimeException. Any checked exception thrown will be due to a framework error, not the logic of the - * step, and thus requiring a throws declaration in clients of this class is unnecessary. + * It should be noted that any checked exceptions encountered while running the + * Step will wrapped with RuntimeException. Any checked exception thrown will be + * due to a framework error, not the logic of the step, and thus requiring a + * throws declaration in clients of this class is unnecessary. * * @author Dan Garrette * @author Lucas Ward * @since 2.0 * @see SimpleJob */ -public class StepRunner{ +public class StepRunner { /** Logger */ protected final Log logger = LogFactory.getLog(getClass()); private JobLauncher launcher; + private JobRepository jobRepository; public StepRunner(JobLauncher launcher, JobRepository jobRepository) { this.launcher = launcher; this.jobRepository = jobRepository; } - + /** - * Launcher + * Launch just the specified step as its own job. A unique set of + * JobParameters will automatically be generated. An IllegalStateException + * is thrown if there is no Step with the given name. * - * @param stepName + * @param stepName The name of the step to launch + * @return JobExecution */ public JobExecution launchStep(Step step) { - return this.launchStep(step, this.makeUniqueJobParameters()); + return this.launchStep(step, this.makeUniqueJobParameters(), null); } /** - * Launch just the specified step in the job. + * Launch just the specified step as its own job. A unique set of + * JobParameters will automatically be generated. An IllegalStateException + * is thrown if there is no Step with the given name. * - * @param stepName - * @param jobParameters + * @param stepName The name of the step to launch + * @param jobExecutionContext An ExecutionContext whose values will be + * loaded into the Job ExecutionContext prior to launching the step. + * @return JobExecution + */ + public JobExecution launchStep(Step step, ExecutionContext jobExecutionContext) { + return this.launchStep(step, this.makeUniqueJobParameters(), jobExecutionContext); + } + + /** + * Launch just the specified step as its own job. An IllegalStateException + * is thrown if there is no Step with the given name. + * + * @param stepName The name of the step to launch + * @param jobParameters The JobParameters to use during the launch + * @return JobExecution */ public JobExecution launchStep(Step step, JobParameters jobParameters) { + return this.launchStep(step, jobParameters, null); + } + + /** + * Launch just the specified step as its own job. An IllegalStateException + * is thrown if there is no Step with the given name. + * + * @param stepName The name of the step to launch + * @param jobParameters The JobParameters to use during the launch + * @param jobExecutionContext An ExecutionContext whose values will be + * loaded into the Job ExecutionContext prior to launching the step. + * @return JobExecution + */ + public JobExecution launchStep(Step step, JobParameters jobParameters, final ExecutionContext jobExecutionContext) { // // Create a fake job // @@ -97,6 +136,20 @@ public class StepRunner{ stepsToExecute.add(step); job.setSteps(stepsToExecute); + // + // Dump the given Job ExecutionContext using a listener + // + if (jobExecutionContext != null && !jobExecutionContext.isEmpty()) { + job.setJobExecutionListeners(new JobExecutionListener[] { new JobExecutionListenerSupport() { + public void beforeJob(JobExecution jobExecution) { + ExecutionContext jobContext = jobExecution.getExecutionContext(); + for (Map.Entry entry : jobExecutionContext.entrySet()) { + jobContext.put(entry.getKey(), entry.getValue()); + } + } + } }); + } + // // Launch the job // @@ -112,18 +165,21 @@ public class StepRunner{ private JobExecution launchJob(Job job, JobParameters jobParameters) { try { return this.launcher.run(job, jobParameters); - } catch (JobExecutionAlreadyRunningException e) { + } + catch (JobExecutionAlreadyRunningException e) { throw new RuntimeException(e); - } catch (JobRestartException e) { + } + catch (JobRestartException e) { throw new RuntimeException(e); - } catch (JobInstanceAlreadyCompleteException e) { + } + catch (JobInstanceAlreadyCompleteException e) { throw new RuntimeException(e); } } /** * @return a new JobParameters object containing only a parameter for the - * current timestamp, to ensure that the job instance will be unique + * current timestamp, to ensure that the job instance will be unique */ private JobParameters makeUniqueJobParameters() { Map parameters = new HashMap(); diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java index b7f97e8ee..6200adcf5 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java @@ -1,12 +1,16 @@ package org.springframework.batch.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.test.sample.SampleTasklet; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; /** @@ -20,6 +24,10 @@ public abstract class AbstractSampleJobTests extends AbstractJobTests { private SimpleJdbcTemplate jdbcTemplate; + @Autowired + @Qualifier("tasklet2") + private SampleTasklet tasklet2; + @Autowired public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -28,6 +36,7 @@ public abstract class AbstractSampleJobTests extends AbstractJobTests { @Before public void setUp() { this.jdbcTemplate.update("create table TESTS (ID integer, NAME varchar(40))"); + tasklet2.jobContextEntryFound = false; } @After @@ -59,6 +68,15 @@ public abstract class AbstractSampleJobTests extends AbstractJobTests { this.verifyTasklet(2); } + @Test + public void testStepLaunchJobContextEntry() { + ExecutionContext jobContext = new ExecutionContext(); + jobContext.put("key1", "value1"); + assertEquals(BatchStatus.COMPLETED, this.launchStep("step2", jobContext).getStatus()); + this.verifyTasklet(2); + assertTrue(tasklet2.jobContextEntryFound); + } + private void verifyTasklet(int id) { assertEquals(id, jdbcTemplate.queryForInt("SELECT ID from TESTS where NAME = 'SampleTasklet" + id + "'")); } diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java index bc4f37414..440147a22 100755 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java @@ -20,20 +20,21 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/sample-steps.xml" }) -public class SampleStepTests implements ApplicationContextAware{ +public class SampleStepTests implements ApplicationContextAware { @Autowired private SimpleJdbcTemplate jdbcTemplate; - + private StepRunner stepRunner; + private ApplicationContext context; - + @Autowired private JobLauncher jobLauncher; - + @Autowired private JobRepository jobRepository; - + @Before public void setUp() { jdbcTemplate.update("create table TESTS (ID integer, NAME varchar(40))"); @@ -47,13 +48,12 @@ public class SampleStepTests implements ApplicationContextAware{ @Test public void testTasklet() { - Step step = (Step)context.getBean("step2"); + Step step = (Step) context.getBean("s2"); assertEquals(BatchStatus.COMPLETED, stepRunner.launchStep(step).getStatus()); assertEquals(2, jdbcTemplate.queryForInt("SELECT ID from TESTS where NAME = 'SampleTasklet2'")); } - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java index e155cbea3..b973d566f 100755 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java @@ -1,8 +1,12 @@ package org.springframework.batch.test.sample; +import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; @@ -12,14 +16,31 @@ public class SampleTasklet implements Tasklet { @Autowired private SimpleJdbcTemplate jdbcTemplate; + private JobExecution jobExecution; + private int id = 0; + public boolean jobContextEntryFound = false; + public SampleTasklet(int id) { this.id = id; } public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { this.jdbcTemplate.update("insert into TESTS(ID, NAME) values (?, 'SampleTasklet" + id + "')", id); + + if (jobExecution != null) { + ExecutionContext jobContext = jobExecution.getExecutionContext(); + if (jobContext.containsKey("key1")) { + jobContextEntryFound = true; + } + } + return RepeatStatus.FINISHED; } + + @BeforeStep + public void storeJobExecution(StepExecution stepExecution) { + this.jobExecution = stepExecution.getJobExecution(); + } } diff --git a/spring-batch-test/src/test/resources/jobs/sample-steps.xml b/spring-batch-test/src/test/resources/jobs/sample-steps.xml index 67460d000..f0939b0f0 100644 --- a/spring-batch-test/src/test/resources/jobs/sample-steps.xml +++ b/spring-batch-test/src/test/resources/jobs/sample-steps.xml @@ -1,26 +1,36 @@ + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd + http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd"> - - - - - - + + + + + + + + + + - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-test/src/test/resources/jobs/sampleFlowJob.xml b/spring-batch-test/src/test/resources/jobs/sampleFlowJob.xml index ced2f3512..425156109 100644 --- a/spring-batch-test/src/test/resources/jobs/sampleFlowJob.xml +++ b/spring-batch-test/src/test/resources/jobs/sampleFlowJob.xml @@ -12,25 +12,11 @@ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> + + - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-batch-test/src/test/resources/jobs/sampleSimpleJob.xml b/spring-batch-test/src/test/resources/jobs/sampleSimpleJob.xml index 613251c65..cd07407a8 100644 --- a/spring-batch-test/src/test/resources/jobs/sampleSimpleJob.xml +++ b/spring-batch-test/src/test/resources/jobs/sampleSimpleJob.xml @@ -12,8 +12,8 @@ - - + +