Validating JobParameters using JobParametersValidator interface

In this post under Spring Batch, I will explain how to validate the parameters passed to the job instance.

The interface we will be using for validating the Job parameters is org.springframework.batch.core.JobParametersValidator which has only one method as shown below


public interface JobParametersValidator {
    void validate(JobParameters parameters) throws JobParametersInvalidException;
}

Based on the signature of the method, it is clear that the method doesn’t return anything, take JobParameters as parameters and throws JobParametersInvalidException if the parameters are invalid.

For our example we will use a default implementation of JobParametersValidator interface which is org.springframework.batch.core.job.DefaultJobParametersValidator class

The DefaultJobParametersValidator allows us to specify which parameters are required and which are optional.

The DefaultJobParametersValidator class verifies whether required parameters are passed or not. If required parameters are not passed it throws JobParametersInvalidException exception and the job execution is stopped.

Below is the xml configuration of DefaultJobParametersValidator


1   <bean id="validator" class="org.springframework.batch.core.job.DefaultJobParametersValidator">
2       <property name="requiredKeys">
3           <set>
4               <value>date</value>
5               <value>jobName</value>
6           </set>
7       </property>
8       <property name="optionalKeys">
9           <set>
10              <value>jobGroupName</value>
11          </set>
12      </property>
13  </bean>

In the above xml a bean of type DefaultJobParametersValidator is defined.

The required properties a job should have is mentioned by setting the requiredKeys property of DefaultJobParametersValidator class.

The optional properties a job should have is mentioned by setting the optionalKeys property of DefaultJobParametersValidator class.

Next we integrate with the job as shown in the below xml configuration


1   <batch:job id="importProductsJob">
2       <batch:step id="readWriteProducts">
3           <batch:tasklet>
4               <batch:chunk reader="reader" writer="writer" commit-interval="50"/>
5           </batch:tasklet>
6       </batch:step>
7       <batch:validator ref="validator"/>
8   </batch:job>

As shown at line 7, we use the validator element to set the validator bean created previously.

Below is the complete main code

Main Code


1  package package13;
2  
3  import java.util.Date;
4  
5  import org.springframework.batch.core.Job;
6  import org.springframework.batch.core.JobParameters;
7  import org.springframework.batch.core.JobParametersBuilder;
8  import org.springframework.batch.core.JobParametersInvalidException;
9  import org.springframework.batch.core.launch.JobLauncher;
10 import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
11 import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
12 import org.springframework.context.ApplicationContext;
13 import org.springframework.context.support.ClassPathXmlApplicationContext;
14 import org.springframework.batch.core.repository.JobRestartException;
15 
16 public class Example13 {
17  public static void main(String[] args) throws JobParametersInvalidException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobRestartException {
18      ApplicationContext context = new ClassPathXmlApplicationContext("package13\\job.xml");
19      JobLauncher jobLauncher = (JobLauncher)context.getBean("jobLauncher");
20      JobParameters jobParameters = new JobParametersBuilder().addDate("date", new Date())
21                                      .addString("jobName", "job1").toJobParameters();
22      Job job = (Job)context.getBean("importProductsJob");
23      
24      jobLauncher.run(job, jobParameters);
25      
26      jobParameters = new JobParametersBuilder().addDate("date", new Date()).toJobParameters();
27      job = (Job)context.getBean("importProductsJob");
28      
29      jobLauncher.run(job, jobParameters);
30  }
31 }

At line 24, we successfully execute a job to which all the required parameters are passed.

At line 29, the execution of another job fails because we didn’t passed “jobName” required parameter.

Below is the output

Output

Nov 18, 2018 12:49:05 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@61e4705b: startup date [Sun Nov 18 12:49:05 IST 2018]; root of context hierarchy
Nov 18, 2018 12:49:06 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [package13/job.xml]
Nov 18, 2018 12:49:07 PM org.springframework.batch.core.launch.support.SimpleJobLauncher afterPropertiesSet
INFO: No TaskExecutor has been set, defaulting to synchronous executor.
Nov 18, 2018 12:49:07 PM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=importProductsJob]] launched with the following parameters: [{date=1542525547467, jobName=job1}]
Nov 18, 2018 12:49:07 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [readWriteProducts]
Nov 18, 2018 12:49:07 PM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=importProductsJob]] completed with the following parameters: [{date=1542525547467, jobName=job1}] and the following status: [COMPLETED]
Exception in thread “main” org.springframework.batch.core.JobParametersInvalidException: The JobParameters do not contain required keys: [jobName]
at org.springframework.batch.core.job.DefaultJobParametersValidator.validate(DefaultJobParametersValidator.java:118)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:123)
at package13.Example13.main(Example13.java:29)

Similar to DefaultJobParametersValidator we can create our own class implementing JobParametersValidator interface and having custom validation logic.

Below is the complete xml code


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:batch="http://www.springframework.org/schema/batch"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">
    
    <bean id="employee" class="package13.Employee" scope="prototype"/>
    
    <bean id="reader" class="org.springframework.batch.item.file.FlatFileItemReader">
        <property name="resource" value="file:FileInput.txt"/>
        <property name="lineMapper">
            <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
                <property name="lineTokenizer">
                    <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
                        <property name="names" value="id,name,status,salary"/>
                    </bean>
                </property>
                <property name="fieldSetMapper">
                    <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
                        <property name="prototypeBeanName" value="employee"/>
                    </bean>
                </property>
            </bean>
        </property> 
    </bean>
    
    <bean id="writer" class="org.springframework.batch.item.file.FlatFileItemWriter">
        <property name="resource" value="file:FileOutput.txt"/>
        <property name="lineAggregator">
            <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
                <property name="fieldExtractor">
                    <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
                        <property name="names" value="id,name,status,salary"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
    
    <bean id="validator" class="org.springframework.batch.core.job.DefaultJobParametersValidator">
        <property name="requiredKeys">
            <set>
                <value>date</value>
                <value>jobName</value>
            </set>
        </property>
        <property name="optionalKeys">
            <set>
                <value>jobGroupName</value>
            </set>
        </property>
    </bean>
    
    <batch:job id="importProductsJob">
        <batch:step id="readWriteProducts">
            <batch:tasklet>
                <batch:chunk reader="reader" writer="writer" commit-interval="50"/>
            </batch:tasklet>
        </batch:step>
        <batch:validator ref="validator"/>
    </batch:job>
    
    <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
    
    <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>
    
    <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
        <property name="jobRepository" ref="jobRepository"/>
    </bean>
</beans>

Leave a Reply