17
Oct
11

Spring Batch Validation


This page demonstrates the process of using Spring Batch and the Apache Commons Validator Framework to validate each record in a flat file. To keep this tutorial brief we will stop processing the file and print out the validation failure message on the first occurrence.

Background

The Commons Validator Framework is composed of a core set of classes responsible for validating data. Since the framework is versatile and does not make assumptions about implementation, you can’t use it out of the box unless you write wrapper code to adapt it to your needs.

Years ago the popular Struts framework had provided a set of wrapper classes in the “org.apache.struts.validator” package. The spring MVC framework had made similar adaptations. I had found it strange that the makers of spring batch did not have a drop in replacement so that had lead me to write this page.

Requirements

This tutorial continues from where the previous one left of so please complete the examples in those articles first before proceeding.

Procedure

Start by adding the following dependencies into your pom.xml file.

pom.xml

<!-- The commons Validator Framework -->
	<dependency>
	    <groupId>commons-validator</groupId>
	    <artifactId>commons-validator</artifactId>
	    <version>1.1.4</version>
	</dependency>
<!-- Spring Wrapper classes for the Validator Framework -->
        <dependency>
	    <groupId>org.springmodules</groupId>
	    <artifactId>spring-modules-validation</artifactId>
	    <version>0.8</version>
	</dependency>

The following is the core class that makes this all happen.

src/main/java/com/test/SpringCommonsValidator.java

package com.test;

import java.util.List;

import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;

/**
 * Allows for the commons validation framework to print human readable 
 * validation failure messages. Also its very good at adapting the 
 * Spring Validator Interface to Spring Batch Validator, thus allowing 
 * this class to be used as a Validator in the spring batch framework.
 * 
 * This class delegates most of its work to the spring validator.
 * 
 */
public class SpringCommonsValidator<T> implements Validator<T> {

	private org.springframework.validation.Validator validator;
	private MessageSource messageSource;

	public void validate(T item) throws ValidationException {

		if (!validator.supports(item.getClass())) {
			throw new ValidationException("Validation failed for " + item + ": " + item.getClass().getName()
					+ " class is not supported by validator.");
		}

		BeanPropertyBindingResult errors = new BeanPropertyBindingResult(item, "item");

		validator.validate(item, errors);

		if (errors.hasErrors()) {
			throw new ValidationException("Validation failed for " + item + ": " + errorsToString(errors));
		}
	}

	private String errorsToString(Errors errors) {
		@SuppressWarnings("rawtypes")
		List errorList = errors.getAllErrors();
		StringBuffer buffer = new StringBuffer();
		
		boolean first = true;
		for (Object object : errorList) {
			if(object instanceof MessageSourceResolvable) {
				MessageSourceResolvable r = (MessageSourceResolvable)object;
				if(!first) { buffer.append("; "); } 
				buffer.append(messageSource.getMessage(r, null));
				first = false;
			}
		}
		return buffer.toString();
	}


	public void setValidator(org.springframework.validation.Validator validator) {
		this.validator = validator;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	public MessageSource getMessageSource() {
		return messageSource;
	}
}

Resource Bundle

The following property file is used to display the error messages. I guess some additional work can be done to internationalize these messages but since batch programs are not end-client facing… what’s the point?

On the other hand, having error messages in a property files does have its advantages.

src/main/resources/messages.properties

errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.
 
errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.
 
errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.

Configuration

Modify the spring configuration file to add the validatorFactory, beanValidator and springValidator. These classes allow you to have the ValidatingItemProcessor validate each item that is passed to it.

src/main/resources/simpleJob.xml

Replace the following section

<!-- Processor -->
<beans:bean name="empProcessor" class="com.test.EmployeeProcessor">
</beans:bean>

with the following

<!-- Processor -->
<beans:bean name="empProcessor" class="org.springframework.batch.item.validator.ValidatingItemProcessor">
    <beans:property name="validator" ref="springValidator"/>    
</beans:bean>

<!-- Validator -->
    <beans:bean id="validatorFactory"
          class="org.springmodules.validation.commons.DefaultValidatorFactory">
    <beans:property name="validationConfigLocations">
        <beans:list>
          <beans:value>validation.xml</beans:value>
          <beans:value>validator-rules.xml</beans:value>
        </beans:list>
      </beans:property>
    </beans:bean>
     
    <beans:bean id="beanValidator" class="org.springmodules.validation.commons.DefaultBeanValidator">
        <beans:property name="validatorFactory" ref="validatorFactory"/>
        <beans:property name="useFullyQualifiedClassName" value="true"/>
    </beans:bean> 

    <beans:bean id="springValidator" class="com.test.SpringCommonsValidator">
        <beans:property name="validator" ref="beanValidator"/>
        <beans:property name="messageSource" ref="resourceBundleMessageSource"/>        
    </beans:bean> 

    <beans:bean id="resourceBundleMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <beans:property name="basename" value="messages"/>    
    </beans:bean> 

Commons validator configuration

The following is the validation configuration file used by commons-validator. If you have used the struts framework before then this file may look familiar to you. This file contains some default “shrink-wrapped” validation checks that were originally included with struts. There is nothing to customize here for now so please just copy and paste this into your program and continue on.

src/main/resources/validator-rules.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
 
<form-validation>
    <global>
        <validator name="required"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateRequired"
            methodParams="java.lang.Object,
                        org.apache.commons.validator.ValidatorAction,
                        org.apache.commons.validator.Field,
                        org.springframework.validation.Errors"
            msg="errors.required">
        </validator>
        <validator name="requiredif"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateRequiredIf"
            methodParams="java.lang.Object,
                               org.springframework.validation.ErrorsAction,
                               org.apache.commons.validator.Field,
                               org.springframework.validation.Errors"
            msg="errors.required" />
 
        <validator name="validwhen" msg="errors.required"
            classname="org.apache.struts.validator.validwhen.ValidWhen" method="validateValidWhen"
            methodParams="java.lang.Object,
                       org.springframework.validation.ErrorsAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors" />
 
        <validator name="minlength"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateMinLength"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.minlength"
            jsFunction="org.apache.commons.validator.javascript.validateMinLength" />
 
        <validator name="maxlength"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateMaxLength"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.maxlength"
            jsFunction="org.apache.commons.validator.javascript.validateMaxLength" />
 
        <validator name="mask"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateMask"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.invalid" />
 
        <validator name="byte"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateByte"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.byte" jsFunctionName="ByteValidations" />
 
        <validator name="short"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateShort"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.short" jsFunctionName="ShortValidations" />
 
        <validator name="integer"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateInteger"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.integer" jsFunctionName="IntegerValidations" />
 
        <validator name="long"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateLong"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.long" />
 
        <validator name="float"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateFloat"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.float" jsFunctionName="FloatValidations" />
 
        <validator name="double"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateDouble"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.double" />
 
        <validator name="date"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateDate"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.date" jsFunctionName="DateValidations" />
 
        <validator name="intRange"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateIntRange"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="integer" msg="errors.range" />
 
        <validator name="floatRange"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateFloatRange"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="float" msg="errors.range" />
 
        <validator name="doubleRange"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateDoubleRange"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="double" msg="errors.range" />
 
        <validator name="creditCard"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateCreditCard"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.creditcard" />
 
        <validator name="email"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateEmail"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.email" />
 
        <validator name="url"
            classname="org.springmodules.validation.commons.FieldChecks" method="validateUrl"
            methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.springframework.validation.Errors"
            depends="" msg="errors.url" />
    </global>
</form-validation>

Validation Rules

The next file contains the actual validations that need to be performed.

These validations are:

  1. last Name is required
  2. last Name must be between 20 and 35 characters in length
  3. rank is required.

src/main/resources/validation.xml

<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
 
    <formset>
        <form name="com.test.Employee">
            <field property="lastName" depends="required,minlength,maxlength">
                <arg0 key="lastName" />
                <arg1 name="minlength" key="${var:minlength}" resource="false"/>
                <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
                <var>
                    <var-name>minlength</var-name>
                    <var-value>20</var-value>
                </var>
                <var>
                    <var-name>maxlength</var-name>
                    <var-value>35</var-value>
                </var>                
            </field>
            <field property="rank" depends="required">
                <arg key="rank" />
            </field>
        </form>
    </formset>
</form-validation>

Run the batch

Run the batch application by typing the following on the command line.

mvn clean compile exec:java -Dexec.mainClass=org.springframework.batch.core.launch.support.CommandLineJobRunner -Dexec.args="simpleJob.xml helloWorldJob"

Observe the Results

The following message should display on the console indicating a validation failure.

SEVERE: Encountered an error executing the step
org.springframework.batch.item.validator.ValidationException: Validation failed for Employee [empId=7876, lastName=ADAMS, title=CLERK, salary=1100, rank=null]: lastName can not be less than 20 characters.; rank is required.
	at com.test.SpringCommonsValidator.validate(SpringCommonsValidator.java:38)
	at org.springframework.batch.item.validator.ValidatingItemProcessor.process(ValidatingItemProcessor.java:77)
	at org.springframework.batch.core.step.item.SimpleChunkProcessor.doProcess(SimpleChunkProcessor.java:125)
	at org.springframework.batch.core.step.item.SimpleChunkProcessor.transform(SimpleChunkProcessor.java:288)

Next Steps

The application described here is a quick and dirty approach to illustrate batch validation. In a typical production grade application you will want to allow the job to continue processing the remaining records in a file and send the bad records to a error file. The error file can be reviewed manually and re-processed on the next run.

Comments and feedback are Welcome.

Thanks!

Advertisements

2 Responses to “Spring Batch Validation”


  1. 1 Sailu
    February 19, 2013 at 2:32 pm

    Thanks for the article. I am getting the following exception. Any suggestions please.

    Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.batch.item.validator.ValidatingItemProcessor]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.batch.item.validator.ValidatingItemProcessor.()


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 74 other followers

October 2011
S M T W T F S
« Jul   Jan »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Blog Stats

  • 801,304 hits

%d bloggers like this: