Merge pull request #74 from snicoll/BATCH-1911

* BATCH-1911:
  BATCH-1911 - Fixed merge conflicts and schema version issue
  StepRegistry is now a first-class (optional) component: - MapJobRegistry no longer implements StepRegistry - DefaultJobLoader updates the stepRegistry when it is available - Added more tests to cover the cases where the step registry is not available
  registering twice the same jobName on the step registry is not allowed (consistency with JobRegistry)
  delegating synchronization to ConcurrentHashMap instead of  dealing that ourselves.
  Added the concept of a StepRegistry that tracks the Step instance attached to a Job.
This commit is contained in:
Michael Minella
2012-12-17 16:32:05 -06:00
12 changed files with 812 additions and 118 deletions

View File

@@ -0,0 +1,48 @@
package org.springframework.batch.core.configuration;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.step.NoSuchStepException;
import java.util.Collection;
/**
* Registry keeping track of all the {@link Step} defined in a
* {@link org.springframework.batch.core.Job}.
*
* @author Sebastien Gerard
* @author Stephane Nicoll
*/
public interface StepRegistry {
/**
* Registers all the step of the given job. If the job is already registered,
* the method {@link #unregisterStepsFromJob(String)} is called before registering
* the given steps.
*
* @param jobName the give job name
* @param steps the job steps
* @throws DuplicateJobException if a job with the same job name has already been registered.
*/
void register(String jobName, Collection<Step> steps) throws DuplicateJobException;
/**
* Unregisters all the steps of the given job. If the job is not registered,
* nothing happens.
*
* @param jobName the given job name
*/
void unregisterStepsFromJob(String jobName);
/**
* Returns the {@link Step} of the specified job based on its name.
*
* @param jobName the name of the job
* @param stepName the name of the step to retrieve
* @return the step with the given name belonging to the mentioned job
* @throws NoSuchJobException no such job with that name exists
* @throws NoSuchStepException no such step with that name for that job exists
*/
Step getStep(String jobName, String stepName) throws NoSuchJobException, NoSuchStepException;
}

View File

@@ -24,24 +24,33 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
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.StepLocator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;
/**
* Default implementation of {@link JobLoader}. Uses a {@link JobRegistry} to
* manage a population of loaded jobs and clears them up when asked.
*
* manage a population of loaded jobs and clears them up when asked. An optional
* {@link StepRegistry} might also be set to register the step(s) available for
* each registered job.
*
* @author Dave Syer
*
* @author Stephane Nicoll
*/
public class DefaultJobLoader implements JobLoader {
public class DefaultJobLoader implements JobLoader, InitializingBean {
private static Log logger = LogFactory.getLog(DefaultJobLoader.class);
private JobRegistry jobRegistry;
private StepRegistry stepRegistry;
private Map<ApplicationContextFactory, ConfigurableApplicationContext> contexts = new ConcurrentHashMap<ApplicationContextFactory, ConfigurableApplicationContext>();
@@ -51,30 +60,51 @@ public class DefaultJobLoader implements JobLoader {
* Default constructor useful for declarative configuration.
*/
public DefaultJobLoader() {
this(null);
this(null, null);
}
/**
* Creates a job loader with the job registry provided.
*
* @param jobRegistry a {@link JobRegistry}
*/
public DefaultJobLoader(JobRegistry jobRegistry) {
this(jobRegistry, null);
}
/**
* Create a job loader with the job registry provided.
* Creates a job loader with the job and step registries provided.
*
* @param jobRegistry a {@link JobRegistry}
* @param stepRegistry a {@link StepRegistry}
*/
public DefaultJobLoader(JobRegistry jobRegistry) {
public DefaultJobLoader(JobRegistry jobRegistry, StepRegistry stepRegistry) {
this.jobRegistry = jobRegistry;
this.stepRegistry = stepRegistry;
}
/**
* The {@link JobRegistry} to use for jobs created.
*
* @param jobRegistry
*
* @param jobRegistry the job registry
*/
public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
/**
* The {@link StepRegistry} to use for the steps of created jobs.
*
* @param stepRegistry the step registry
*/
public void setStepRegistry(StepRegistry stepRegistry) {
this.stepRegistry = stepRegistry;
}
/**
* Unregister all the jobs and close all the contexts created by this
* loader.
*
*
* @see JobLoader#clear()
*/
public void clear() {
@@ -84,7 +114,7 @@ public class DefaultJobLoader implements JobLoader {
}
}
for (String jobName : jobRegistry.getJobNames()) {
jobRegistry.unregister(jobName);
doUnregister(jobName);
}
contexts.clear();
}
@@ -96,7 +126,7 @@ public class DefaultJobLoader implements JobLoader {
ConfigurableApplicationContext context = contexts.get(factory);
for (String name : contextToJobNames.get(context)) {
logger.debug("Unregistering job: " + name + " from context: " + context.getDisplayName());
jobRegistry.unregister(name);
doUnregister(name);
}
context.close();
}
@@ -105,7 +135,7 @@ public class DefaultJobLoader implements JobLoader {
return doLoad(factory, true);
}
catch (DuplicateJobException e) {
throw new IllegalStateException("Found duplicte job in reload (it should have been unregistered "
throw new IllegalStateException("Found duplicate job in reload (it should have been unregistered "
+ "if it was previously registered in this loader)", e);
}
}
@@ -144,14 +174,12 @@ public class DefaultJobLoader implements JobLoader {
// On reload try to unregister first
if (unregister) {
logger.debug("Unregistering job: " + jobName + " from context: " + context.getDisplayName());
jobRegistry.unregister(jobName);
doUnregister(jobName);
}
logger.debug("Registering job: " + jobName + " from context: " + context.getDisplayName());
JobFactory jobFactory = new ReferenceJobFactory(job);
jobRegistry.register(jobFactory);
doRegister(context, job);
jobsRegistered.add(jobName);
}
}
@@ -174,4 +202,73 @@ public class DefaultJobLoader implements JobLoader {
}
/**
* Returns all the {@link Step} instances defined by the specified {@link StepLocator}.
* <p/>
* The specified <tt>jobApplicationContext</tt> is used to collect additional steps that
* are not exposed by the step locator
*
* @param stepLocator the given step locator
* @param jobApplicationContext the application context of the job
* @return all the {@link Step} defined by the given step locator and context
* @see StepLocator
*/
private Collection<Step> getSteps(final StepLocator stepLocator, final ApplicationContext jobApplicationContext) {
final Collection<String> stepNames = stepLocator.getStepNames();
final Collection<Step> result = new ArrayList<Step>();
for (String stepName : stepNames) {
result.add(stepLocator.getStep(stepName));
}
// Because some steps are referenced by name, we need to look in the context to see if there
// are more Step instances defined. Right now they are registered as being available in the
// context of the job but we have no idea if they are linked to that Job or not.
@SuppressWarnings("unchecked")
final Map<String, Step> allSteps = jobApplicationContext.getBeansOfType(Step.class);
for (Map.Entry<String, Step> entry : allSteps.entrySet()) {
if (!stepNames.contains(entry.getKey())) {
result.add(entry.getValue());
}
}
return result;
}
/**
* Registers the specified {@link Job} defined in the specified {@link ConfigurableApplicationContext}.
* <p/>
* Makes sure to update the {@link StepRegistry} if it is available.
*
* @param context the context in which the job is defined
* @param job the job to register
* @throws DuplicateJobException if that job is already registered
*/
private void doRegister(ConfigurableApplicationContext context, Job job) throws DuplicateJobException {
final JobFactory jobFactory = new ReferenceJobFactory(job);
jobRegistry.register(jobFactory);
if (stepRegistry != null) {
if (!(job instanceof StepLocator)) {
throw new UnsupportedOperationException("Cannot locate steps from a Job that is not a StepLocator: job="
+ job.getName() + " does not implement StepLocator");
}
stepRegistry.register(job.getName(), getSteps((StepLocator) job, context));
}
}
/**
* Unregisters the job identified by the specified <tt>jobName</tt>.
*
* @param jobName the name of the job to unregister
*/
private void doUnregister(String jobName) {
jobRegistry.unregister(jobName);
if (stepRegistry != null) {
stepRegistry.unregisterStepsFromJob(jobName);
}
}
public void afterPropertiesSet() {
Assert.notNull(jobRegistry, "Job registry could not be null.");
}
}

View File

@@ -15,12 +15,6 @@
*/
package org.springframework.batch.core.configuration.support;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobFactory;
@@ -28,51 +22,55 @@ import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Simple, thread-safe, map-based implementation of {@link JobRegistry}.
*
* Simple, thread-safe, map-based implementation of {@link JobRegistry}.
*
* @author Dave Syer
* @author Robert Fischer
*
*/
public class MapJobRegistry implements JobRegistry {
/**
* The map holding the registered job factories.
*/
// The "final" ensures that it is visible and initialized when the constructor resolves.
private final ConcurrentMap<String, JobFactory> map = new ConcurrentHashMap<String, JobFactory>();
/**
* The map holding the registered job factories.
*/
// The "final" ensures that it is visible and initialized when the constructor resolves.
private final ConcurrentMap<String, JobFactory> map = new ConcurrentHashMap<String, JobFactory>();
public void register(JobFactory jobFactory) throws DuplicateJobException {
Assert.notNull(jobFactory);
String name = jobFactory.getJobName();
Assert.notNull(name, "Job configuration must have a name.");
JobFactory previousValue = map.putIfAbsent(name, jobFactory);
if(previousValue != null) {
throw new DuplicateJobException("A job configuration with this name [" + name
+ "] was already registered");
}
}
public void register(JobFactory jobFactory) throws DuplicateJobException {
Assert.notNull(jobFactory);
String name = jobFactory.getJobName();
Assert.notNull(name, "Job configuration must have a name.");
JobFactory previousValue = map.putIfAbsent(name, jobFactory);
if (previousValue != null) {
throw new DuplicateJobException("A job configuration with this name [" + name
+ "] was already registered");
}
}
public void unregister(String name) {
Assert.notNull(name, "Job configuration must have a name.");
map.remove(name);
}
public void unregister(String name) {
Assert.notNull(name, "Job configuration must have a name.");
map.remove(name);
}
public Job getJob(String name) throws NoSuchJobException {
JobFactory factory = map.get(name);
if(factory == null) {
throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered");
} else {
return factory.createJob();
}
}
public Job getJob(String name) throws NoSuchJobException {
JobFactory factory = map.get(name);
if (factory == null) {
throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered");
} else {
return factory.createJob();
}
}
/**
* Provides an unmodifiable view of the job names.
*/
public Set<String> getJobNames() {
return Collections.unmodifiableSet(map.keySet());
}
/**
* Provides an unmodifiable view of the job names.
*/
public Set<String> getJobNames() {
return Collections.unmodifiableSet(map.keySet());
}
}

View File

@@ -0,0 +1,64 @@
package org.springframework.batch.core.configuration.support;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Simple map-based implementation of {@link StepRegistry}. Access to the map is
* synchronized, guarded by an internal lock.
*
* @author Sebastien Gerard
* @author Stephane Nicoll
*/
public class MapStepRegistry implements StepRegistry {
private final ConcurrentMap<String, Map<String, Step>> map = new ConcurrentHashMap<String, Map<String, Step>>();
public void register(String jobName, Collection<Step> steps) throws DuplicateJobException {
Assert.notNull(jobName, "The job name cannot be null.");
Assert.notNull(steps, "The job steps cannot be null.");
final Map<String, Step> jobSteps = new HashMap<String, Step>();
for (Step step : steps) {
jobSteps.put(step.getName(), step);
}
final Object previousValue = map.putIfAbsent(jobName, jobSteps);
if (previousValue != null) {
throw new DuplicateJobException("A job configuration with this name [" + jobName
+ "] was already registered");
}
}
public void unregisterStepsFromJob(String jobName) {
Assert.notNull(jobName, "Job configuration must have a name.");
map.remove(jobName);
}
public Step getStep(String jobName, String stepName) throws NoSuchJobException {
Assert.notNull(jobName, "The job name cannot be null.");
Assert.notNull(stepName, "The step name cannot be null.");
if (!map.containsKey(jobName)) {
throw new NoSuchJobException("No job configuration with the name [" + jobName + "] was registered");
} else {
final Map<String, Step> jobSteps = map.get(jobName);
if (jobSteps.containsKey(stepName)) {
return jobSteps.get(stepName);
} else {
throw new NoSuchStepException("The step called [" + stepName + "] does not exist in the job [" +
jobName + "]");
}
}
}
}

View File

@@ -16,82 +16,266 @@
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 createWithOnlyJobRegistry() {
final DefaultJobLoader loader = new DefaultJobLoader();
loader.setJobRegistry(jobRegistry);
loader.afterPropertiesSet();
}
@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 testNoStepRegistryAvailable() throws DuplicateJobException {
final JobLoader loader = new DefaultJobLoader(jobRegistry);
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ClassPathResource("job-context-with-steps.xml", getClass()));
loader.load(factory);
// No step registry available so just registering the jobs
assertEquals(2, jobRegistry.getJobNames().size());
}
@Test
public void testLoadWithJobThatIsNotAStepLocator() throws DuplicateJobException {
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ByteArrayResource(BASIC_JOB_XML.getBytes()));
try {
jobLoader.load(factory);
fail("Should have failed with a ["+UnsupportedOperationException.class.getName()+"] as job does not" +
"implement StepLocator.");
} catch (UnsupportedOperationException e) {
// Job is not a step locator, can't register steps
}
}
@Test
public void testLoadWithJobThatIsNotAStepLocatorNoStepRegistry() throws DuplicateJobException {
final JobLoader loader = new DefaultJobLoader(jobRegistry);
GenericApplicationContextFactory factory = new GenericApplicationContextFactory(
new ByteArrayResource(BASIC_JOB_XML.getBytes()));
try {
loader.load(factory);
} catch (UnsupportedOperationException e) {
fail("Should not have failed with a [" + UnsupportedOperationException.class.getName() + "] as " +
"stepRegistry is not available for this JobLoader instance.");
}
}
@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 BASIC_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$BasicStubJob'/></beans>",
DefaultJobLoaderTests.class.getName());
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 BasicStubJob implements Job {
@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;
}
}
}
public static class StubJob extends BasicStubJob implements StepLocator {
@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

@@ -21,8 +21,6 @@ import junit.framework.TestCase;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.configuration.support.ReferenceJobFactory;
import org.springframework.batch.core.job.JobSupport;
import org.springframework.batch.core.launch.NoSuchJobException;

View File

@@ -0,0 +1,240 @@
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.DuplicateJobException;
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;
import static junit.framework.Assert.fail;
/**
* @author Sebastien Gerard
*/
public class MapStepRegistryTests {
private static final String EXCEPTION_NOT_THROWN_MSG = "An exception should have been thrown";
@Test
public void registerStepEmptyCollection() throws DuplicateJobException {
final StepRegistry stepRegistry = createRegistry();
launchRegisterGetRegistered(stepRegistry, "myJob", getStepCollection());
}
@Test
public void registerStepNullJobName() throws DuplicateJobException {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.register(null, new HashSet<Step>());
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void registerStepNullSteps() throws DuplicateJobException {
final StepRegistry stepRegistry = createRegistry();
try {
stepRegistry.register("fdsfsd", null);
Assert.fail(EXCEPTION_NOT_THROWN_MSG);
} catch (IllegalArgumentException e) {
}
}
@Test
public void registerStepGetStep() throws DuplicateJobException {
final StepRegistry stepRegistry = createRegistry();
launchRegisterGetRegistered(stepRegistry, "myJob",
getStepCollection(
createStep("myStep"),
createStep("myOtherStep"),
createStep("myThirdStep")
));
}
@Test
public void getJobNotRegistered() throws DuplicateJobException {
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() throws DuplicateJobException {
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 registerTwice() throws DuplicateJobException {
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);
// Second registration with same name should fail
try {
stepRegistry.register(jobName, getStepCollection(
createStep("myFourthStep"),
createStep("lastOne")));
fail("Should have failed with a "+DuplicateJobException.class.getSimpleName());
} catch (DuplicateJobException e) {
// OK
}
}
@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, DuplicateJobException {
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() throws DuplicateJobException {
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)
throws DuplicateJobException {
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;
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<description>
Declares two jobs with a set of steps. Also declares two steps that are not attached to any job
but could be linked to them through a partition handler configuration for instance.
</description>
<import resource="job-context-with-steps.xml"/>
<bean id="genericStep1" class="org.springframework.batch.core.step.StepSupport"/>
<bean id="genericStep2" class="org.springframework.batch.core.step.StepSupport"/>
</beans>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 id="job1"
class="org.springframework.batch.core.job.JobSupport">
<property name="steps">
<list>
<bean id="step11" class="org.springframework.batch.core.step.StepSupport"/>
<bean id="step12" class="org.springframework.batch.core.step.StepSupport"/>
</list>
</property>
</bean>
<bean id="job2"
class="org.springframework.batch.core.job.JobSupport">
<property name="steps">
<list>
<bean id="step21" class="org.springframework.batch.core.step.StepSupport"/>
<bean id="step22" class="org.springframework.batch.core.step.StepSupport"/>
</list>
</property>
</bean>
</beans>

View File

@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 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-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<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 id="test-job" class="org.springframework.batch.core.job.JobSupport" />
<bean id="test-job" class="org.springframework.batch.core.job.JobSupport">
<property name="steps">
<bean id="test-step"
class="org.springframework.batch.core.step.StepSupport"/>
</property>
</bean>
<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry" />

View File

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
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 id="test-job"
class="org.springframework.batch.core.job.JobSupport">
<property name="steps">
<bean id="test-step"
class="org.springframework.batch.core.step.StepSupport"/>
</property>
</bean>
<bean id="test-job"
class="org.springframework.batch.core.job.JobSupport"/>
</beans>