BATCH-1295:

*Provide ability to pass job's ExecutionContext into AbstractJobTests.launchStep() and StepRunner.launchStep()
 *Modified SimpleMethodInvoker.hashCode() so that it is not dependent on commons-lang
This commit is contained in:
dhgarrette
2009-06-21 15:55:22 +00:00
parent 1a92c5479a
commit 026bb0665e
9 changed files with 204 additions and 79 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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.
*
* <ul>
* <li><b>launchStep(Step step)</b>: Launch the step with new parameters each time. (The current system
* time will be used)
* <li><b>launchStep(Step step, JobParameters jobParameters)</b>: Launch the specified step with the provided
* JobParameters. This may be useful if your step requires a certain parameter during runtime.
* <li><b>launchStep(Step step)</b>: Launch the step with new parameters each
* time. (The current system time will be used)
* <li><b>launchStep(Step step, JobParameters jobParameters)</b>: Launch the
* specified step with the provided JobParameters. This may be useful if your
* step requires a certain parameter during runtime.
* </ul>
*
* 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<String, Object> 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<String, JobParameter> parameters = new HashMap<String, JobParameter>();

View File

@@ -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 + "'"));
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -1,26 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
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">
<bean id="step1" parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="1" />
</bean>
</property>
<batch:step id="s1" parent="taskletStep">
<batch:tasklet ref="tasklet1">
<batch:listeners>
<batch:listener ref="tasklet1"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<bean id="tasklet1" class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="1" />
</bean>
<bean id="step2" parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="2" />
</bean>
</property>
<batch:step id="s2" parent="taskletStep">
<batch:tasklet ref="tasklet2">
<batch:listeners>
<batch:listener ref="tasklet2"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<bean id="tasklet2" class="org.springframework.batch.test.sample.SampleTasklet">
<constructor-arg value="2" />
</bean>
</beans>

View File

@@ -12,25 +12,11 @@
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<beans:import resource="sample-steps.xml" />
<job id="sampleFlowJob">
<step id="step1" parent="s1" next="step2"/>
<step id="step2" parent="s2"/>
</job>
<beans:bean id="s1" parent="taskletStep">
<beans:property name="tasklet">
<beans:bean class="org.springframework.batch.test.sample.SampleTasklet">
<beans:constructor-arg value="1" />
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="s2" parent="taskletStep">
<beans:property name="tasklet">
<beans:bean class="org.springframework.batch.test.sample.SampleTasklet">
<beans:constructor-arg value="2" />
</beans:bean>
</beans:property>
</beans:bean>
</beans:beans>

View File

@@ -12,8 +12,8 @@
<bean id="sampleJob" parent="simpleJob">
<property name="steps">
<list>
<ref bean="step1" />
<ref bean="step2" />
<bean id="step1" parent="s1"/>
<bean id="step2" parent="s2"/>
</list>
</property>
</bean>