Added the concept of a StepRegistry that tracks the Step instance attached to a Job.

- Added a simple MapStepRegistry that keeps track of the step in a simple map per job
- MapJobRegistry implements StepRegistry as well using the delegate pattern
- DefaultJobLoader makes sure to initialize both registries now. Auto-detects that the JobRegistry implements StepRegistry
- JobSupport now implements StepLocator so that such job can be loaded by the DefaultJobLoader
- Also registering any "detached" step available for a particular job to that job if that wasn't done yet. Since a partition handler may link to any step by name, the step registry must be filled with any step that are available in the context of a job. If each job is located in its own application context, this is just fine. If more than one job is defined in the same context, the step registry may be able to return a step that is not related to that job but it shouldn't hurt as this happens only if a component is explicitly asking to execute a step by name for a particular job.
This commit is contained in:
Stéphane Nicoll
2012-11-24 10:43:35 +01:00
committed by Michael Minella
parent b345e1c072
commit 00ee45854e
11 changed files with 764 additions and 110 deletions

View File

@@ -16,82 +16,234 @@
package org.springframework.batch.core.configuration.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.JobParametersValidator;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
/**
* @author Dave Syer
*
* @author Stephane Nicoll
*/
public class DefaultJobLoaderTests {
private JobRegistry registry = new MapJobRegistry();
/**
* The name of the job as defined in the test context used in this test.
*/
private static final String TEST_JOB_NAME = "test-job";
private DefaultJobLoader jobLoader = new DefaultJobLoader(registry);
/**
* The name of the step as defined in the test context used in this test.
*/
private static final String TEST_STEP_NAME = "test-step";
private JobRegistry jobRegistry = new MapJobRegistry();
private StepRegistry stepRegistry = new MapStepRegistry();
private DefaultJobLoader jobLoader = new DefaultJobLoader(jobRegistry, stepRegistry);
@Test
public void testLoadWithExplicitName() throws Exception {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ByteArrayResource(
JOB_XML.getBytes()));
jobLoader.load(factory);
assertEquals(1, registry.getJobNames().size());
assertEquals(1, jobRegistry.getJobNames().size());
jobLoader.reload(factory);
assertEquals(1, registry.getJobNames().size());
assertEquals(1, jobRegistry.getJobNames().size());
}
@Test
public void testReload() throws Exception {
@Test
public void createWithBothRegistries() {
final DefaultJobLoader loader = new DefaultJobLoader();
loader.setJobRegistry(jobRegistry);
loader.setStepRegistry(stepRegistry);
loader.afterPropertiesSet();
}
@Test
public void createWithJobRegistryWhichIsAStepRegistry() {
final DefaultJobLoader loader = new DefaultJobLoader();
loader.setJobRegistry(jobRegistry);
loader.afterPropertiesSet();
}
@Test
public void createWithSimpleJobRegistry() {
final DefaultJobLoader loader = new DefaultJobLoader();
loader.setJobRegistry(new JobRegistryMock());
try {
loader.afterPropertiesSet();
fail("Should have failed to create job loader without a step registry (" +
"and the job registry could not fulfill that role)");
} catch (IllegalArgumentException e) {
// OK
}
}
@Test
public void testRegistryUpdated() throws DuplicateJobException {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ClassPathResource("trivial-context.xml", getClass()));
jobLoader.load(factory);
assertEquals(1, jobRegistry.getJobNames().size());
assertStepExist(TEST_JOB_NAME, TEST_STEP_NAME);
}
@Test
public void testMultipleJobsInTheSameContext() throws DuplicateJobException {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ClassPathResource("job-context-with-steps.xml", getClass()));
jobLoader.load(factory);
assertEquals(2, jobRegistry.getJobNames().size());
assertStepExist("job1", "step11", "step12");
assertStepDoNotExist("job1", "step21", "step22");
assertStepExist("job2", "step21", "step22");
assertStepDoNotExist("job2", "step11", "step12");
}
@Test
public void testMultipleJobsInTheSameContextWithSeparateSteps() throws DuplicateJobException {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ClassPathResource("job-context-with-separate-steps.xml", getClass()));
jobLoader.load(factory);
assertEquals(2, jobRegistry.getJobNames().size());
assertStepExist("job1", "step11", "step12", "genericStep1", "genericStep2");
assertStepDoNotExist("job1", "step21", "step22");
assertStepExist("job2", "step21", "step22", "genericStep1", "genericStep2");
assertStepDoNotExist("job2", "step11", "step12");
}
@Test
public void testReload() throws Exception {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ClassPathResource(
"trivial-context.xml", getClass()));
jobLoader.load(factory);
assertEquals(1, registry.getJobNames().size());
jobLoader.reload(factory);
assertEquals(1, registry.getJobNames().size());
}
jobLoader.load(factory);
assertEquals(1, jobRegistry.getJobNames().size());
assertStepExist(TEST_JOB_NAME, TEST_STEP_NAME);
jobLoader.reload(factory);
assertEquals(1, jobRegistry.getJobNames().size());
assertStepExist(TEST_JOB_NAME, TEST_STEP_NAME);
}
@Test
public void testReloadWithAutoRegister() throws Exception {
@Test
public void testReloadWithAutoRegister() throws Exception {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ClassPathResource(
"trivial-context-autoregister.xml", getClass()));
jobLoader.load(factory);
assertEquals(1, registry.getJobNames().size());
jobLoader.reload(factory);
assertEquals(1, registry.getJobNames().size());
}
jobLoader.load(factory);
assertEquals(1, jobRegistry.getJobNames().size());
assertStepExist(TEST_JOB_NAME, TEST_STEP_NAME);
jobLoader.reload(factory);
assertEquals(1, jobRegistry.getJobNames().size());
assertStepExist(TEST_JOB_NAME, TEST_STEP_NAME);
}
private static final String JOB_XML = String
.format("<beans xmlns='http://www.springframework.org/schema/beans' 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-3.1.xsd'><bean class='%s$StubJob'/></beans>",
DefaultJobLoaderTests.class.getName());
protected void assertStepExist(String jobName, String... stepNames) {
for (String stepName : stepNames) {
try {
stepRegistry.getStep(jobName, stepName);
} catch (NoSuchJobException e) {
fail("Job with name [" + jobName + "] should have been found.");
} catch (NoSuchStepException e) {
fail("Step with name [" + stepName + "] for job [" + jobName + "] should have been found.");
}
}
}
public static class StubJob implements Job {
protected void assertStepDoNotExist(String jobName, String... stepNames) {
for (String stepName : stepNames) {
try {
final Step step = stepRegistry.getStep(jobName, stepName);
fail("Step with name [" + stepName + "] for job [" + jobName + "] should " +
"not have been found but got [" + step + "]");
} catch (NoSuchJobException e) {
fail("Job with name [" + jobName + "] should have been found.");
} catch (NoSuchStepException e) {
// OK
}
}
}
private static final String JOB_XML = String
.format(
"<beans xmlns='http://www.springframework.org/schema/beans' 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.5.xsd'><bean class='%s$StubJob'/></beans>",
DefaultJobLoaderTests.class.getName());
public static class StubJob implements Job, StepLocator {
@Override
public void execute(JobExecution execution) {
}
}
@Override
public JobParametersIncrementer getJobParametersIncrementer() {
return null;
}
return null;
}
@Override
public String getName() {
return "job";
}
return "job";
}
@Override
public boolean isRestartable() {
return false;
}
return false;
}
@Override
public JobParametersValidator getJobParametersValidator() {
return null;
}
return null;
}
}
@Override
public Collection<String> getStepNames() {
return Collections.emptyList();
}
@Override
public Step getStep(String stepName) throws NoSuchStepException {
throw new NoSuchStepException("Step [" + stepName + "] does not exist");
}
}
private static class JobRegistryMock implements JobRegistry {
@Override
public void register(JobFactory jobFactory) throws DuplicateJobException {
// dummy
}
@Override
public void unregister(String jobName) {
// dummy
}
@Override
public Collection<String> getJobNames() {
return Collections.emptyList();
}
@Override
public Job getJob(String name) throws NoSuchJobException {
throw new NoSuchJobException("Mock implementation does not hold any job.");
}
}
}

View File

@@ -0,0 +1,234 @@
package org.springframework.batch.core.configuration.support;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
/**
* @author Sebastien Gerard
*/
public class MapStepRegistryTests {
private static final String EXCEPTION_NOT_THROWN_MSG = "An exception should have been thrown";
@Test
public void registerStepEmptyCollection() {
final StepRegistry stepRegistry = createRegistry();
launchRegisterGetRegistered(stepRegistry, "myJob", getStepCollection());
}
@Test
public void registerStepNullJobName() {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.register(null, new HashSet<Step>());
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void registerStepNullSteps() {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.register("fdsfsd", null);
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void registerStepGetStep() {
final StepRegistry stepRegistry = createRegistry();
launchRegisterGetRegistered(stepRegistry, "myJob",
getStepCollection(
createStep("myStep"),
createStep("myOtherStep"),
createStep("myThirdStep")
));
}
@Test
public void getJobNotRegistered() {
final StepRegistry stepRegistry = createRegistry();
final String aStepName = "myStep";
launchRegisterGetRegistered(stepRegistry, "myJob",
getStepCollection(
createStep(aStepName),
createStep("myOtherStep"),
createStep("myThirdStep")
));
assertJobNotRegistered(stepRegistry, "a ghost");
}
@Test
public void getJobNotRegisteredNoRegistration() {
final StepRegistry stepRegistry = createRegistry();
assertJobNotRegistered(stepRegistry, "a ghost");
}
@Test
public void getStepNotRegistered() {
final StepRegistry stepRegistry = createRegistry();
final String jobName = "myJob";
launchRegisterGetRegistered(stepRegistry, jobName,
getStepCollection(
createStep("myStep"),
createStep("myOtherStep"),
createStep("myThirdStep")
));
assertStepNameNotRegistered(stepRegistry, jobName, "fsdfsdfsdfsd");
}
@Test
public void registerRegisterAgainAndGet() {
final StepRegistry stepRegistry = createRegistry();
final String jobName = "myJob";
final Collection<Step> stepsFirstRegistration = getStepCollection(
createStep("myStep"),
createStep("myOtherStep"),
createStep("myThirdStep")
);
// first registration
launchRegisterGetRegistered(stepRegistry, jobName, stepsFirstRegistration);
// register again the job
launchRegisterGetRegistered(stepRegistry, jobName,
getStepCollection(
createStep("myFourthStep"),
createStep("lastOne")
));
assertStepsNotRegistered(stepRegistry, jobName, stepsFirstRegistration);
}
@Test
public void getStepNullJobName() throws NoSuchJobException {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.getStep(null, "a step");
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void getStepNullStepName() throws NoSuchJobException {
final StepRegistry stepRegistry = createRegistry();
final String stepName = "myStep";
launchRegisterGetRegistered(stepRegistry, "myJob", getStepCollection(createStep(stepName)));
try {
stepRegistry.getStep(null, stepName);
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void registerStepUnregisterJob() {
final StepRegistry stepRegistry = createRegistry();
final Collection<Step> steps = getStepCollection(
createStep("myStep"),
createStep("myOtherStep"),
createStep("myThirdStep")
);
final String jobName = "myJob";
launchRegisterGetRegistered(stepRegistry, jobName, steps);
stepRegistry.unregisterStepsFromJob(jobName);
assertJobNotRegistered(stepRegistry, jobName);
}
@Test
public void unregisterJobNameNull() {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.unregisterStepsFromJob(null);
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void unregisterNoRegistration() {
final StepRegistry stepRegistry = createRegistry();
assertJobNotRegistered(stepRegistry, "a job");
}
protected StepRegistry createRegistry() {
return new MapStepRegistry();
}
protected Step createStep(String stepName) {
return new TaskletStep(stepName);
}
protected Collection<Step> getStepCollection(Step... steps) {
return Arrays.asList(steps);
}
protected void launchRegisterGetRegistered(StepRegistry stepRegistry, String jobName, Collection<Step> steps) {
stepRegistry.register(jobName, steps);
assertStepsRegistered(stepRegistry, jobName, steps);
}
protected void assertJobNotRegistered(StepRegistry stepRegistry, String jobName) {
try {
stepRegistry.getStep(jobName, "a step");
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (NoSuchJobException e) {
}
}
protected void assertStepsRegistered(StepRegistry stepRegistry, String jobName, Collection<Step> steps) {
for (Step step : steps) {
try {
stepRegistry.getStep(jobName, step.getName());
} catch (NoSuchJobException e) {
Assert.fail("Unexpected exception " + e);
}
}
}
protected void assertStepsNotRegistered(StepRegistry stepRegistry, String jobName, Collection<Step> steps) {
for (Step step : steps) {
assertStepNameNotRegistered(stepRegistry, jobName, step.getName());
}
}
protected void assertStepNameNotRegistered(StepRegistry stepRegistry, String jobName, String stepName) {
try {
stepRegistry.getStep(jobName, stepName);
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (NoSuchJobException e) {
Assert.fail("Unexpected exception");
} catch (NoSuchStepException e) {
}
}
}

View File

@@ -16,8 +16,10 @@
package org.springframework.batch.core.job;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
@@ -25,6 +27,8 @@ import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.JobParametersValidator;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.util.ClassUtils;
@@ -37,9 +41,9 @@ import org.springframework.util.ClassUtils;
* @author Lucas Ward
* @author Dave Syer
*/
public class JobSupport implements BeanNameAware, Job {
public class JobSupport implements BeanNameAware, Job, StepLocator {
private List<Step> steps = new ArrayList<Step>();
private Map<String, Step> steps = new HashMap<String, Step>();
private String name;
@@ -110,11 +114,13 @@ public class JobSupport implements BeanNameAware, Job {
public void setSteps(List<Step> steps) {
this.steps.clear();
this.steps.addAll(steps);
for (Step step : steps) {
this.steps.put(step.getName(), step);
}
}
public void addStep(Step step) {
this.steps.add(step);
this.steps.put(step.getName(), step);
}
/*
@@ -172,4 +178,15 @@ public class JobSupport implements BeanNameAware, Job {
return jobParametersValidator;
}
public Collection<String> getStepNames() {
return steps.keySet();
}
public Step getStep(String stepName) throws NoSuchStepException {
final Step step = steps.get(stepName);
if (step == null) {
throw new NoSuchStepException("Step ["+stepName+"] does not exist for job with name ["+getName()+"]");
}
return step;
}
}