diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java index 507c2384b..4eda25b93 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java @@ -15,6 +15,8 @@ */ package org.springframework.batch.core; +import org.springframework.batch.core.job.JobParametersValidator; + /** * Batch domain object representing a job. Job is an explicit abstraction * representing the configuration of a job specified by a developer. It should @@ -34,7 +36,7 @@ public interface Job { * @return true if this job can be restarted after a failure */ boolean isRestartable(); - + /** * Run the {@link JobExecution} and update the meta information like status * and statistics as necessary. This method should not throw any exceptions @@ -54,4 +56,14 @@ public interface Job { */ JobParametersIncrementer getJobParametersIncrementer(); + /** + * A validator for the job parameters of a {@link JobExecution}. Clients of + * a Job may need to validate the parameters for a launch, before or during + * the execution. + * + * @return a validator that can be used to check parameter values (never + * null) + */ + JobParametersValidator getJobParametersValidator(); + } \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java index 3b3e6ba23..f470c996a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.JobParametersValidator; /** * A {@link Job} that can optionally prepend a group name to another job's name, @@ -87,6 +88,10 @@ public class GroupAwareJob implements Job { return delegate.getJobParametersIncrementer(); } + public JobParametersValidator getJobParametersValidator() { + return delegate.getJobParametersValidator(); + } + /* * (non-Javadoc) * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 27155fbcc..e61f82aff 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -157,6 +157,10 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In * @return the step names */ public abstract Collection getStepNames(); + + public JobParametersValidator getJobParametersValidator() { + return jobParametersValidator; + } /** * Boolean flag to prevent categorically a job from restarting, even if it diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java index 2a2318f40..f91ce9b61 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java @@ -18,9 +18,32 @@ import org.springframework.util.Assert; */ public class DefaultJobParametersValidator implements JobParametersValidator, InitializingBean { - private Collection requiredKeys = new HashSet(); + private Collection requiredKeys; - private Collection optionalKeys = new HashSet(); + private Collection optionalKeys; + + /** + * Convenient default constructor for unconstrained validation. + */ + public DefaultJobParametersValidator() { + this(new String[0], new String[0]); + } + + /** + * Create a new validator with the required and optional job parameter keys + * provided. + * + * @see DefaultJobParametersValidator#setOptionalKeys(String[]) + * @see DefaultJobParametersValidator#setRequiredKeys(String[]) + * + * @param requiredKeys the required keys + * @param optionalKeys the optional keys + */ + public DefaultJobParametersValidator(String[] requiredKeys, String[] optionalKeys) { + super(); + setRequiredKeys(requiredKeys); + setOptionalKeys(optionalKeys); + } /** * Check that there are no overlaps between required and optional keys. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java index 8f29f9cc6..c83a2205f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java @@ -52,7 +52,10 @@ import org.springframework.util.Assert; * Repository can reliably recreate it. * * @author Lucas Ward + * @Author Dave Syer + * * @since 1.0 + * * @see JobRepository * @see TaskExecutor */ @@ -93,21 +96,20 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean { if (!job.isRestartable()) { throw new JobRestartException("JobInstance already exists and is not restartable"); } - else { - /* - * There is a very small probability that a non-restartable job - * can be restarted, but only if another process or thread - * manages to launch and fail a job execution for this - * instance between the last assertion and the next method - * returning successfully. - */ - jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - } - } - else { - jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); } + // Check the validity of the parameters before doing creating anything + // in the repository... + job.getJobParametersValidator().validate(jobParameters); + + /* + * There is a very small probability that a non-restartable job can be + * restarted, but only if another process or thread manages to launch + * and fail a job execution for this instance between the last + * assertion and the next method returning successfully. + */ + jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); + try { taskExecutor.execute(new Runnable() { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java index 36d64f2f5..8892594e2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java @@ -22,6 +22,7 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParametersIncrementer; import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.job.JobParametersValidator; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; @@ -88,6 +89,10 @@ public class DefaultJobLoaderTests { return false; } + public JobParametersValidator getJobParametersValidator() { + return null; + } + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java index 9a894829d..bc440f604 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java @@ -46,6 +46,8 @@ public class JobSupport implements BeanNameAware, Job { private int startLimit = Integer.MAX_VALUE; + private DefaultJobParametersValidator jobParametersValidator = new DefaultJobParametersValidator(); + /** * Default constructor. */ @@ -97,6 +99,13 @@ public class JobSupport implements BeanNameAware, Job { public String getName() { return name; } + + /** + * @param jobParametersValidator the jobParametersValidator to set + */ + public void setJobParametersValidator(DefaultJobParametersValidator jobParametersValidator) { + this.jobParametersValidator = jobParametersValidator; + } public void setSteps(List steps) { this.steps.clear(); @@ -157,4 +166,9 @@ public class JobSupport implements BeanNameAware, Job { public JobParametersIncrementer getJobParametersIncrementer() { return null; } + + public JobParametersValidator getJobParametersValidator() { + return jobParametersValidator; + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java index aa45a01be..e0870de00 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/SimpleJobLauncherTests.java @@ -33,10 +33,11 @@ import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.job.DefaultJobParametersValidator; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.launch.support.SimpleJobLauncher; import org.springframework.batch.core.repository.JobRepository; @@ -52,7 +53,7 @@ public class SimpleJobLauncherTests { private SimpleJobLauncher jobLauncher; - private Job job = new JobSupport("foo") { + private JobSupport job = new JobSupport("foo") { @Override public void execute(JobExecution execution) { execution.setExitStatus(ExitStatus.COMPLETED); @@ -78,11 +79,13 @@ public class SimpleJobLauncherTests { run(ExitStatus.COMPLETED); } - private void run(ExitStatus exitStatus) throws Exception { - JobExecution jobExecution = new JobExecution(null, null); + @Test(expected=JobParametersInvalidException.class) + public void testRunWithValidator() throws Exception { + + job.setJobParametersValidator(new DefaultJobParametersValidator(new String[] { "missing-and-required" }, + new String[0])); expect(jobRepository.getLastJobExecution(job.getName(), jobParameters)).andReturn(null); - expect(jobRepository.createJobExecution(job.getName(), jobParameters)).andReturn(jobExecution); replay(jobRepository); jobLauncher.afterPropertiesSet(); @@ -90,9 +93,9 @@ public class SimpleJobLauncherTests { jobLauncher.run(job, jobParameters); } finally { - assertEquals(exitStatus, jobExecution.getExitStatus()); verify(jobRepository); } + } /* @@ -228,6 +231,23 @@ public class SimpleJobLauncherTests { jobLauncher.setJobRepository(jobRepository); jobLauncher.afterPropertiesSet(); // no error } + + private void run(ExitStatus exitStatus) throws Exception { + JobExecution jobExecution = new JobExecution(null, null); + + expect(jobRepository.getLastJobExecution(job.getName(), jobParameters)).andReturn(null); + expect(jobRepository.createJobExecution(job.getName(), jobParameters)).andReturn(jobExecution); + replay(jobRepository); + + jobLauncher.afterPropertiesSet(); + try { + jobLauncher.run(job, jobParameters); + } + finally { + assertEquals(exitStatus, jobExecution.getExitStatus()); + verify(jobRepository); + } + } private boolean contains(String str, String searchStr) { return str.indexOf(searchStr) != -1;