Posts Tagged ‘validation

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
23
Apr
11

Nested JSON

This page describes the process of performing CRUD operations on Java Beans that have Nested Properties using ExtJS.

Requirements

The Item bean contains a length, width, and height property. We will move those attributes to a new bean called Dimension and add a dimension attribute to the Item class.

This will result in the editor Grid submitting the following xml in the Request Body:

<?xml version="1.0" encoding="UTF-8"?>
	<item>
		<itemId>5</itemId>
		<name>tes</name>
		<description>111111</description>
		<stockQty>2</stockQty>
		<color>blue</color>
		<dimension.length>1</dimension.length>
		<dimension.width>1</dimension.width>
		<dimension.height>1</dimension.height>
	</item>

The following changes need to be done:

  1. Create a new Dimension class, modify the Item class to use it.
  2. Modify the IBATIS sql map xml files
  3. Modify ExtJS JsonReader to read the nested information

src/main/java/com/test/Dimension.java

package com.test;

import java.math.BigDecimal;

public class Dimension {
    private BigDecimal length;
    private BigDecimal width;
    private BigDecimal height;
    
    
	public BigDecimal getLength() {
		return length;
	}
	public void setLength(BigDecimal length) {
		this.length = length;
	}
	public BigDecimal getWidth() {
		return width;
	}
	public void setWidth(BigDecimal width) {
		this.width = width;
	}
	public BigDecimal getHeight() {
		return height;
	}
	public void setHeight(BigDecimal height) {
		this.height = height;
	}
}

Item

Replace the implementation of Item.java with the following.

src/main/java/com/test/Item.java

package com.test;
 
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlRootElement;
 
/**
 * Represents an item in inventory or in a shopping cart.
 */
@XmlRootElement
public class Item {
    private Integer itemId;
    private String oemId;
    private String name;
    private String description;
    private String imageURL;
    private String color;
    private BigDecimal price;
    private Dimension dimension = new Dimension();
     
    /**
     * Weight in grams
     */
    private BigDecimal weight;
    /**
     * Quantity in stock.
     */
    private Integer stockQty;
 
    public Item() {
        super();
    }
 
    public Item(Integer id, String name) {
        this.name = name;
        this.itemId = id;
    }
 
    public Integer getItemId() {
        return itemId;
    }
    public void setItemId(Integer itemId) {
        this.itemId = itemId;
    }
    public String getOemId() {
        return oemId;
    }
    public void setOemId(String oemId) {
        this.oemId = oemId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public String getImageURL() {
        return imageURL;
    }
    public void setImageURL(String imageURL) {
        this.imageURL = imageURL;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getWeight() {
        return weight;
    }
    public void setWeight(BigDecimal weight) {
        this.weight = weight;
    }
 
    public Integer getStockQty() {
        return stockQty;
    }
 
    public void setStockQty(Integer stockQty) {
        this.stockQty = stockQty;
    }

    public void setDimension(Dimension dimension) {
		this.dimension = dimension;
    }

    public Dimension getDimension() {
		return dimension;
    }
}

IBATIS SQL Mapper

Change the SQL mapper configuration file to look like this…

src/main/resources/com/test/ItemDataManager.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
        PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
        "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Item">
 
<typeAlias alias="Item" type="com.test.Item" />

    <resultMap id="itemResultMap" class="Item">
		<result property="itemId" column="itemId"/>
		<result property="oemId" column="oemId"/>
		<result property="name" column="name"/>
		<result property="description" column="description"/>
		<result property="color" column="color"/>
		<result property="imageURL" column="imageURL"/>
		<result property="price" column="price"/>
		<result property="dimension.length" column="length"/>
		<result property="dimension.width" column="width"/>
		<result property="dimension.height" column="height"/>
		<result property="weight" column="weight"/>
		<result property="stockQty" column="stockQty"/>
    </resultMap>

 
    <select id="allItems" resultMap="itemResultMap" >
        select item_id as itemId,
        oem_id as oemId,
        item_x as name,
        description as description,
        color as color,
        image_url as imageURL,
        price as price,
        length as length,
        width as width,
        height as height,
        weight as weight,
        stock_qty as stockQty
        from item;
    </select>
    <select id="getItemInfo" parameterClass="int" resultMap="itemResultMap" >
        select item_id as itemId,
        oem_id as oemId,
        item_x as name,
        description as description,
        color as color,
        image_url as imageURL,
        price as price,
        length as length,
        width as width,
        height as height,
        weight as weight,
        stock_qty as stockQty
        from item where item_id = #id#;
    </select>
 
    <insert id="itemInsert" parameterClass="Item">
        insert into item (oem_id,
            item_x,
            description,
            color,
            image_url,
            price,
            length,
            width,
            height,
            weight,
            stock_qty)
        values ( #oemId#, #name#, #description#, #color#,
            #imageURL#, #price#, #dimension.length#, #dimension.width#,
            #dimension.height#, #weight#, #stockQty# );
        <selectKey resultClass="int" >
            <!-- HSQLDB -->
            CALL IDENTITY();
            <!-- MySQL -->
            <!-- SELECT LAST_INSERT_ID(); -->
        </selectKey>
    </insert>
    <update id="updateItem" parameterClass="Item">
        update item set oem_id = #oemId#, item_x = #name#, description=#description#,
            color = #color#, image_url = #imageURL#, price = #price#, length = #dimension.length#,
            width = #dimension.width#, height = #dimension.height#, weight = #weight#, stock_qty = #stockQty#
        where item_id = #itemId#;
    </update>
    <delete id="deleteItem" parameterClass="java.lang.Integer">
        delete from item where item_id = #itemId#;
    </delete>
</sqlMap>

Change the Grid

MyApp={};
MyApp.getContext = function() {
	var base = document.getElementsByTagName('base')[0];
	if (base && base.href && (base.href.length > 0)) {
		base = base.href;
	} else {
		base = document.URL;
	}
	return base.substr(0, base.indexOf("/", base.indexOf("/", base.indexOf("//") + 2) + 1));
};

var App = new Ext.App({});
 
var proxy = new Ext.data.HttpProxy({
	url: MyApp.getContext() + '/appdata/itemresource'
});

var reader = new Ext.data.JsonReader({
    totalProperty: 'total',
    successProperty: 'success',
    idProperty: 'itemId',
    root: 'data',
    record: 'item',
    messageProperty: 'message',  // <-- New "messageProperty" meta-data
    fields : [
              {name: 'itemId'},
              {name: 'name', allowBlank: false},
              {name: 'description', allowBlank: true},
              {name: 'stockQty', allowBlank: true},
              {name: 'color', allowBlank: true},
// The 'mapping' field allows binding to complex nested JSON objects              
              {name: 'length', allowBlank: true, mapping: 'dimension.length'},              
              {name: 'width', allowBlank: true, mapping: 'dimension.width'},              
              {name: 'height', allowBlank: true, mapping: 'dimension.height'}              
    ]
});

// The new DataWriter component.
var writer = new Ext.data.XmlWriter({
    encode : false,
    writeAllFields : true,
    xmlEncoding : "UTF-8"
});

// Typical Store collecting the Proxy, Reader and Writer together.
var store = new Ext.data.Store({
    id: 'item',
    restful: true,     // <-- This Store is RESTful
    proxy: proxy,
    reader: reader,
    writer: writer    // <-- plug a DataWriter into the store
});

// load the store
store.load();

// The following is necessary to map the Record Fields to the grid columns.
var userColumns =  [
    {header: "Item Id", width: 40, sortable: true, dataIndex: 'itemId'},
    {header: "Item Name", width: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({})},
    {header: "Description", width: 100, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({})},
    {header: "Quantity", width: 100, sortable: true, dataIndex: 'stockQty', editor: new Ext.form.TextField({})},
    {header: "Color", width: 100, sortable: true, dataIndex: 'color', editor: new Ext.form.TextField({})},
    {header: "Length", width: 100, sortable: true, 
    	dataIndex: 'length', editor: new Ext.form.TextField({})},
    {header: "Width", width: 100, sortable: true, 
    	dataIndex: 'width', editor: new Ext.form.TextField({})},
    {header: "Height", width: 100, sortable: true, 
    	dataIndex: 'height', editor: new Ext.form.TextField({})}
];

Ext.onReady(function() {
    Ext.QuickTips.init();

    // use RowEditor for editing
    var editor = new Ext.ux.grid.RowEditor({
        saveText: 'Save'
    });

    // Create a typical GridPanel with RowEditor plugin
    var userGrid = new Ext.grid.GridPanel({
        renderTo: 'user-grid',
        iconCls: 'icon-grid',
        frame: true,
        title: 'Fun with RESTful CRUD',
        height: 300,
        store: store,
        plugins: [editor],
        columns : userColumns,
    	enableColumnHide: false,
        tbar: [{
        	id: 'addBtn',
            text: 'Add',
            iconCls: 'silk-add',
            handler: onAdd
        }, '-', {
        	id: 'deleteBtn',
            text: 'Delete',
            iconCls: 'silk-delete',
            handler: onDelete
        }, '-'],
        viewConfig: {
            forceFit: true
        }
    });

    function onAdd(btn, ev) {
        var u = new userGrid.store.recordType({
            first : '',
            last: '',
            email : ''
        });
        editor.stopEditing();
        userGrid.store.insert(0, u);
        editor.startEditing(0);
    }

    // Disable the buttons when editing.
    editor.addListener('beforeedit', function(roweditor, rowIndex){
        Ext.getCmp('addBtn').disable();
        Ext.getCmp('deleteBtn').disable();
    });
    editor.addListener('afteredit', function(roweditor, rowIndex){
        Ext.getCmp('addBtn').enable();
        Ext.getCmp('deleteBtn').enable();
    });
    editor.addListener('canceledit', function(roweditor, rowIndex){
    	// get fresh records from the database.
        userGrid.store.reload({});
        Ext.getCmp('addBtn').enable();
        Ext.getCmp('deleteBtn').enable();
    });     

    function onDelete() {
        var rec = userGrid.getSelectionModel().getSelected();
        if (!rec) {
            return false;
        }
        userGrid.store.remove(rec);
    }
    activateGridValidation(userGrid);
});

Test the application

In the project top folder type in the following command to compile and run the code in jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/crud/

Modify the application presented in Part II

In this section we will modify the application presented in part II of my posting in this series.

Regular form panel can submit nested bean information. The submission looks like this:

name=4&description=444444&stockQty=4&color=blue&dimension.length=4&dimension.width=4&dimension.height=4

Modify the form.html page to look like this…

src/main/webapp/form.html

<html>
<head>
 
<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>
 
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
 
<script type="text/javascript">
 
MyApp={};
MyApp.getContext = function() {
    var base = document.getElementsByTagName('base')[0];
    if (base && base.href && (base.href.length > 0)) {
        base = base.href;
    } else {
        base = document.URL;
    }
    return base.substr(0, base.indexOf("/", base.indexOf("/", base.indexOf("//") + 2) + 1));
};
 
function buildWindow() {
    Ext.QuickTips.init();
    Ext.form.Field.prototype.msgTarget = 'side';
    var bd = Ext.getBody();
 
    var fs = new Ext.FormPanel({
        labelWidth: 75,
        frame: true,
        title:'Form with crazy amount of validation',
        bodyStyle:'padding:5px 5px 0',
        defaults: {width: 230},
        waitMsgTarget: true,
        defaultType: 'textfield',    
 
        items: [{
                fieldLabel: 'Item Name',
                name: 'name'
            },{
                fieldLabel: 'Description',
                name: 'description'
            },{
                fieldLabel: 'Quantity',
                name: 'stockQty'
            }, {
                fieldLabel: 'Color',
                name: 'color'
            }, {
                fieldLabel: 'Length',
                name: 'dimension.length'
		    }, {
		        fieldLabel: 'Width',
		        name: 'dimension.width'
			}, {
			    fieldLabel: 'Height',
			    name: 'dimension.height'
			}
        ],    
 
    });
 
    var onSuccessOrFail = function(form, action) {
        var result = action.result;
 
        if(result.success) {
            Ext.MessageBox.alert('Success', 'Success!');
            userGrid.store.reload({});
        } else { // put code here to handle form validation failure.
        }
 
    }
 
    var submit = fs.addButton({
        text: 'Submit',
        disabled:false,
        handler: function(){
            fs.getForm().submit({
                url:MyApp.getContext() +'/app/itemresource',
                waitMsg:'Submitting Data...',
                submitEmptyText: false,
                success : onSuccessOrFail,
                failure : onSuccessOrFail
            });
        }
    });
 
    fs.render('form-first');
 
    // the following is necessary to display the grid.
 
// The following is necessary to map the Record Fields to the grid columns.
var userColumns =  [
    {header: "Item Id", width: 40, sortable: true, dataIndex: 'itemId'},
    {header: "Item Name", width: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({})},
    {header: "Description", width: 100, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({})},
    {header: "Quantity", width: 100, sortable: true, dataIndex: 'stockQty', editor: new Ext.form.TextField({})},
    {header: "Color", width: 100, sortable: true, dataIndex: 'color', editor: new Ext.form.TextField({})},
    {header: "Length", width: 100, sortable: true, 
    	dataIndex: 'length', editor: new Ext.form.TextField({})},
    {header: "Width", width: 100, sortable: true, 
    	dataIndex: 'width', editor: new Ext.form.TextField({})},
    {header: "Height", width: 100, sortable: true, 
    	dataIndex: 'height', editor: new Ext.form.TextField({})}
];
 
    var proxy = new Ext.data.HttpProxy({
        url: MyApp.getContext() + '/app/itemresource'
    });
 
    var reader = new Ext.data.JsonReader({
        totalProperty: 'total',
        successProperty: 'success',
        idProperty: 'itemId',
        root: 'data',
        record: 'item',
        messageProperty: 'message',  // <-- New "messageProperty" meta-data
        fields : [
                  {name: 'itemId'},
                  {name: 'name', allowBlank: false},
                  {name: 'description', allowBlank: true},
                  {name: 'stockQty', allowBlank: true},
              {name: 'color', allowBlank: true},
// The 'mapping' field allows binding to complex nested JSON objects              
              {name: 'length', allowBlank: true, mapping: 'dimension.length'},              
              {name: 'width', allowBlank: true, mapping: 'dimension.width'},              
              {name: 'height', allowBlank: true, mapping: 'dimension.height'}              
        ]
    });
 
    // Typical Store collecting the Proxy, Reader and Writer together.
    var store = new Ext.data.Store({
        id: 'item',
        restful: true,     // <-- This Store is RESTful
        proxy: proxy,
        reader: reader
    });
 // load the store
    store.load();
 
    // Create a typical GridPanel with RowEditor plugin
    var userGrid = new Ext.grid.GridPanel({
        renderTo: 'user-grid',
        iconCls: 'icon-grid',
        frame: true,
        title: 'Records in HyperSQL Database',
        height: 300,
        store: store,
        columns : userColumns,
        enableColumnHide: false,
        viewConfig: {
            forceFit: true
        }
    });
 
}
Ext.onReady(buildWindow);
</script>
</head>
<body>
 
<div class="container" style="width:500px">
    <div id="form-first"></div>
    <div id="user-grid"></div>
</div>
 
</body>
</html>
23
Apr
11

CRUD with Extjs

The content of this page has moved/combined with another page: https://numberformat.wordpress.com/2011/04/07/restful-crud-using-extjs/

20
Apr
11

ExtJS + Spring MVC validation using commons-validator

This page describes the process of validating a extJS form using Spring MVC and the commons-validator framework. For those that want instant gratification, a complete working version of the project is available at the following SVN location. http://ext-jsf.googlecode.com/svn/trunk/wordpress/2011/04/extjs-springmvc-form-validation/. Get the project and skip to the Test The Application section of this page.

Background

This page is a re-implementation of extjs-form-validation-using-commons-validator. This page uses Spring MVC instead of Struts.

Commons validation is a mature project that’s been out there for a while now. It became popular when it was introduced as part of the Struts framework. Commons validation allows developers to specify validation rules inside XML files, thus allowing them to be applied uniformly across the site. In addition the same set of rules can be used to generate both client side and server side validation thus reducing maintenance cost.

In the interest of brevity, this page will only cover the server side validation.

Using commons-validator by itself doesn’t give you much benefit. All you get is the ability to specify rules and validation logic that returns whether the validation passed or failed.

You are left on your own to:

  1. Write your own code to iterate thru the form fields and collect the failure messages
  2. Write a servlet to handle requests over HTTP and present the failure messages back to the client
  3. Internationalization Support

Although its not that much work, when you start implementing it you quickly realize that this boiler plate code is something that a framework should provide.

This boiler plate code has been written years ago by the makers of the Struts framework. And later the Folks over at springsource wrote a validator module for it allowing the Spring framework to use validation.

By extending the Spring MVC framework slightly, we can use it to validate forms submitted by extJS components.

Spring MVC comes pre-packaged with the “form:errors” tag. Upon validation failures the Spring MVC controller should returns the user back to the “input” page and the input page contains a “form:errors” tag that displays the failed validation along with a description of the failure in HTML format.

The Approach

The approach presented here focuses on extending the “form:errors” tag to return JSON instead of html. The JSON is parsed by the extJS formPanel and the errors are presented to the user.

On success the extJS formPanel expects:

{success: true, msg : 'Thank you for your submission.'}

On failure the extJS formPanel expects:

{
   success : false,
   msg     : 'This is an example error message',
   errors  : {
      firstName : 'Sample Error for First Name',
      lastName  : 'Sample Error for Last Name'
   }
}

Note:
Some of the code on this page are not core to the understanding “approach”, for this reason the code will initially be collapsed.

Requirements

Since this page is about integrating 3 different technologies together a basic understanding of Spring MVC, commons-validation, and extJS is assumed. If any of these technologies are not clear then please visit some of my other tutorial pages.

  1. Maven 2
  2. Basic Understanding of extJS, Spring MVC and commons-validator
  3. Successful completion of all examples in the the following page.

Continue with an existing project

We will continue with an existing project that implements the Model layer. If you have not done so already please visit the following page for more information.

Add the validation libraries to the pom.xml

pom.xml

		<dependency>
		    <groupId>org.springmodules</groupId>
		    <artifactId>spring-modules-validation</artifactId>
		    <version>0.8</version>
		</dependency>
		<dependency>
		    <groupId>commons-validator</groupId>
		    <artifactId>commons-validator</artifactId>
		    <version>1.1.4</version>
		</dependency>       

Project Resources

The following file contains the messages to display to the user. Please note that there are a series of “typeMismatch” lines. These are for the Spring MVC data binder. It seems that in Spring MVC the Binding occurs before the validation. Therefore if these entries are not made in the property file, the system would display cryptic error messages about the binding attempt.

src/main/resources/application.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.

# The following are used during Spring MVC Data Binding
typeMismatch=invalid field 
typeMismatch.int={0} must be an integer. 
typeMismatch.java.lang.Integer={0} must be an integer. 
typeMismatch.java.util.Date={0} must be a Date.

item.name=Item Name
item.description=Item Description
item.color=Item Color
item.stockQty=Item Quantity
item.color.must.be=Item color must be blue.

Add the following 2 beans to the existing spring configuration file.

src/main/resources/applicationContext.xml

	<bean id="validatorFactory"
	      class="org.springmodules.validation.commons.DefaultValidatorFactory">
	<property name="validationConfigLocations">
	    <list>
	      <value>/WEB-INF/validation.xml</value>
	      <value>/WEB-INF/validator-rules.xml</value>
	    </list>
	  </property>
	</bean>    
	<bean id="beanValidator" class="org.springmodules.validation.commons.DefaultBeanValidator">
	<property name="validatorFactory" ref="validatorFactory"/>
	</bean>    
 
 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">  
     <property name="basenames">  
         <value>application</value>  
     </property>  
 </bean>  

Extending SpringMVC tag libraries

Create the fw directory

mkdir -p src/main/java/com/test/fw

The Spring MVC framework does not return validation failures in JSON format. Therefore we need to modify the “form:errors” tag to return errors in a format that can be recognized by extJS. We will be overriding the default behaviour of the tags using the following classes.

src/main/java/com/test/fw/JSFormTag.java

package com.test.fw;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.springframework.beans.PropertyAccessor;
import org.springframework.web.servlet.tags.form.FormTag;
import org.springframework.web.servlet.tags.form.TagWriter;

/**
 * Extend the Spring MVC Form tag to suppress any output.
 */
public class JSFormTag extends FormTag {
	private static final long serialVersionUID = 1L;

	private String previousNestedPath;

	@Override
	protected int writeTagContent(TagWriter tagWriter) throws JspException {
		
		// Expose the form object name for nested tags...
		String modelAttribute = resolveModelAttribute();
		this.pageContext.setAttribute(MODEL_ATTRIBUTE_VARIABLE_NAME, modelAttribute, PageContext.REQUEST_SCOPE);
		this.pageContext.setAttribute(COMMAND_NAME_VARIABLE_NAME, modelAttribute, PageContext.REQUEST_SCOPE);

		// Save previous nestedPath value, build and expose current nestedPath value.
		// Use request scope to expose nestedPath to included pages too.
		this.previousNestedPath =
				(String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
		this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME,
				modelAttribute + PropertyAccessor.NESTED_PROPERTY_SEPARATOR, PageContext.REQUEST_SCOPE);

		return EVAL_BODY_INCLUDE;
	}

	/**
	 * Closes the '<code>form</code>' block tag and removes the form object name
	 * from the {@link javax.servlet.jsp.PageContext}.
	 */
	public int doEndTag() throws JspException {
		//this.tagWriter.endTag();
		this.pageContext.removeAttribute(MODEL_ATTRIBUTE_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
		this.pageContext.removeAttribute(COMMAND_NAME_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
		if (this.previousNestedPath != null) {
			// Expose previous nestedPath value.
			this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, this.previousNestedPath, PageContext.REQUEST_SCOPE);
		}
		else {
			// Remove exposed nestedPath value.
			this.pageContext.removeAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
		}
		return EVAL_PAGE;
	}

	/**
	 * Clears the stored {@link TagWriter}.
	 */
	public void doFinally() {
		super.doFinally();
		this.previousNestedPath = null;
	}

	
}

src/main/java/com/test/fw/MyRequestContext.java

package com.test.fw;

import java.util.Map;

import javax.servlet.jsp.PageContext;

import org.springframework.web.servlet.support.JspAwareRequestContext;

/**
 * Sole purpose of this class is to force the JSPAwareRequestContext to
 * reveal the model object.
 */
public class MyRequestContext extends JspAwareRequestContext {
	@Override
	public Object getModelObject(String modelName) {
		return super.getModelObject(modelName);
	}

	public MyRequestContext(PageContext pageContext, Map model) {
		super(pageContext, model);
	}

	public MyRequestContext(PageContext pageContext) {
		super(pageContext);
	}
	
}

The following class is core to the understanding of this page. This class is a replacement for the form:errors tag. Instead of outputting HTML to display the errors to the user it show the errors in JSON format.

src/main/java/com/test/fw/JSErrorsTag.java

package com.test.fw;

import java.io.IOException;
import java.io.Writer;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.PropertyAccessor;
import org.springframework.web.servlet.tags.NestedPathTag;
import org.springframework.web.servlet.tags.RequestContextAwareTag;

public class JSErrorsTag extends RequestContextAwareTag {
	private static final long serialVersionUID = 1L;
	
	protected JSBindStatus bindStatus;
	protected String path;
	
	protected static final String NESTED_PATH_VARIABLE_NAME = NestedPathTag.NESTED_PATH_VARIABLE_NAME;
	public static final String MESSAGES_ATTRIBUTE = "messages";

	
	@Override
	protected int doStartTagInternal() throws Exception {
		SafeWriter safeWriter = new SafeWriter(pageContext);
		JSONObject jsonReturn = new JSONObject();
		JSONObject jsonErrors = new JSONObject();
		 

		
		Map<String, String> errorMessages = getJSBindStatus().getErrorMessagesMap();
		JSONObject jsonData = new JSONObject(getJSBindStatus().getModel());

		boolean successFlag = false;
		if(errorMessages==null || errorMessages.size() == 0) {
			successFlag = true;
		} else {
			jsonReturn.put("errors", jsonErrors);			
		}
		try {
			jsonReturn.put("data", jsonData);
			jsonReturn.put("msg", successFlag ? "Success!" : "There was a validation failure." );
			jsonReturn.put("success", successFlag);
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
		
		for(Map.Entry<String, String> error : errorMessages.entrySet()) {
			try {
				jsonErrors.append(error.getKey(), error.getValue());
			} catch (JSONException e) {
				throw new RuntimeException(e);				
			}
		}
	
		safeWriter.append(jsonReturn.toString());

		return 0;
	}	
	
	public void doFinally() {
		super.doFinally();
		this.pageContext.removeAttribute(MESSAGES_ATTRIBUTE,
				PageContext.PAGE_SCOPE);
		this.bindStatus = null;
	}
	
	/**
	 * Get the value of the nested path that may have been exposed by the
	 * {@link NestedPathTag}.
	 */
	protected String getNestedPath() {
		return (String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
	}
	
	protected JSBindStatus getJSBindStatus() throws JspException {
		if (this.bindStatus == null) {
			// HTML escaping in tags is performed by the ValueFormatter class.
			String nestedPath = getNestedPath();
			String pathToUse = (nestedPath != null ? nestedPath + getPath() : getPath());
			if (pathToUse.endsWith(PropertyAccessor.NESTED_PROPERTY_SEPARATOR)) {
				pathToUse = pathToUse.substring(0, pathToUse.length() - 1);
			}

			//TODO: is this the correct way? check the original code.
			MyRequestContext context = new MyRequestContext(pageContext);

			this.bindStatus = new JSBindStatus(context, pathToUse, false);
			
		}
		return this.bindStatus;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}	
	
	private static class SafeWriter {

		private PageContext pageContext;

		private Writer writer;

		public SafeWriter(PageContext pageContext) {
			this.pageContext = pageContext;
		}

		public SafeWriter append(String value) throws JspException {
			try {
				getWriterToUse().write(String.valueOf(value));
				return this;
			}
			catch (IOException ex) {
				throw new JspException("Unable to write to JspWriter", ex);
			}
		}

		private Writer getWriterToUse() {
			return (this.pageContext != null ? this.pageContext.getOut() : this.writer);
		}
	}	
	
}

src/main/java/com/test/fw/JSBindStatus.java

package com.test.fw;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.context.NoSuchMessageException;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.servlet.support.RequestContext;

/**
 * This is the BindStatus class simplified.
 */
public class JSBindStatus {

	protected final Errors errors;
	protected final Object model;
	
	protected List<FieldError> objectErrors;
	protected final String expression;

	protected Map<String,String> errorMessagesMap;
	protected RequestContext requestContext;
	protected String path;
	protected boolean htmlEscape;
	
	public JSBindStatus(MyRequestContext requestContext, String path,
			boolean htmlEscape) throws IllegalStateException {

		this.requestContext = requestContext;
		this.path = path;
		this.htmlEscape = htmlEscape;

		// determine name of the object and property
		String beanName = null;
		int dotPos = path.indexOf('.');
		if (dotPos == -1) {
			// property not set, only the object itself
			beanName = path;
			this.expression = null;
		}
		else {
			beanName = path.substring(0, dotPos);
			this.expression = path.substring(dotPos + 1);
		}

		this.model = requestContext.getModelObject(beanName);

		this.errors = requestContext.getErrors(beanName, false);
					
		if (this.errors != null) {
			// Usual case: A BindingResult is available as request attribute.
			// Can determine error codes and messages for the given expression.
			// Can use a custom PropertyEditor, as registered by a form controller.
			if (this.expression != null) {
				if ("*".equals(this.expression)) {
					this.objectErrors = this.errors.getAllErrors();
				}
			}
			else {
				this.objectErrors = this.errors.getGlobalErrors();
			}
			//initErrorCodes();
		}			
	}

	public Map<String, String> getErrorMessagesMap() {
		initErrorMessages();
		return errorMessagesMap;
	}
	
	/**
	 * Extract the error messages from the ObjectError list.
	 */
	private void initErrorMessages() throws NoSuchMessageException {
		if (this.errorMessagesMap == null) {
			this.errorMessagesMap = new HashMap<String,String>();
			if(objectErrors != null) {
				for (FieldError error : objectErrors) {
					String message = this.requestContext.getMessage(error, this.htmlEscape);
					errorMessagesMap.put(error.getField(), message);
				}				
			}
		}
	}

	public Object getModel() {
		return model;
	}	
}

Custom Tag library descriptor

The following is the tag library configuration file. Typically this would be packaged into a re-usable jar file but for the purposes of this tutorial I will put it into the web-inf folder.

src/main/webapp/WEB-INF/rest-mvc.tld

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-
jsptaglibrary_2_0.xsd"
    version="2.0">
<tlib-version>1.0</tlib-version>
<uri>http://test.com/jsrestlib</uri>

	<tag>
		<name>form</name>
		<tag-class>com.test.fw.JSFormTag</tag-class>
		<body-content>JSP</body-content>
		<description>Renders an HTML 'form' tag and exposes a binding path to inner tags for binding.</description>
		<attribute>
			<name>id</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>name</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute - added for backwards compatibility cases</description>
		</attribute>
		<attribute>
			<name>htmlEscape</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Enable/disable HTML escaping of rendered values.</description>
		</attribute>
		<attribute>
			<name>cssClass</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Equivalent to "class" - HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>cssStyle</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Equivalent to "style" - HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>lang</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>title</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>dir</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>onclick</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>ondblclick</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmousedown</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseup</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseover</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmousemove</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseout</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeypress</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeyup</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeydown</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<!-- Form specific attributes -->
		<attribute>
			<name>modelAttribute</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Name of the model attribute under which the form object is exposed.
				Defaults to 'command'.</description>
		</attribute>
		<attribute>
			<name>commandName</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Name of the model attribute under which the form object is exposed.
				Defaults to 'command'.</description>
		</attribute>
		<attribute>
			<name>action</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Required Attribute</description>
		</attribute>
		<attribute>
			<name>method</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>target</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>enctype</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>acceptCharset</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Specifies the list of character encodings for input data that is accepted by the server processing this form. The value is a space- and/or comma-delimited list of charset values. The client must interpret this list as an exclusive-or list, i.e., the server is able to accept any single character encoding per entity received.</description>
		</attribute>
		<attribute>
			<name>onsubmit</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onreset</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>autocomplete</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Common Optional Attribute</description>
		</attribute>
		<attribute>
			<name>methodParam</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>The parameter name used for HTTP methods other then GET and POST. Default is '_method'</description>
		</attribute>
	</tag>
 
	<tag>
		<name>jserrors</name>
		<tag-class>com.test.fw.JSErrorsTag</tag-class>
		<body-content>JSP</body-content>
		<description>Renders field errors in an HTML 'span' tag.</description>
		<variable>
			<name-given>messages</name-given>
			<variable-class>java.util.List</variable-class>
		</variable>
		<attribute>
			<name>path</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Path to errors object for data binding</description>
		</attribute>
		<attribute>
			<name>id</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>htmlEscape</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Enable/disable HTML escaping of rendered values.</description>
		</attribute>
		<attribute>
			<name>delimiter</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Delimiter for displaying multiple error messages. Defaults to the br tag.</description>
		</attribute>
		<attribute>
			<name>cssClass</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Equivalent to "class" - HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>cssStyle</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Equivalent to "style" - HTML Optional Attribute</description>
		</attribute>
		<attribute>
			<name>lang</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>title</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>dir</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>tabindex</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Standard Attribute</description>
		</attribute>
		<attribute>
			<name>onclick</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>ondblclick</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmousedown</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseup</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseover</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmousemove</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onmouseout</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeypress</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeyup</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>onkeydown</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>HTML Event Attribute</description>
		</attribute>
		<attribute>
			<name>element</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
			<description>Specifies the HTML element that is used to render the enclosing errors.</description>
		</attribute>
	</tag>

</taglib>

Controller Class

src/main/java/com/test/ItemResourceController.java

package com.test;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
public class ItemResourceController {
    private ItemModel itemModel;

    @Qualifier("beanValidator")
    private Validator beanValidator;    

    @RequestMapping(value="/itemresource/*", method = RequestMethod.DELETE)
    public String itemInfoDelete(HttpServletRequest request,
            HttpServletResponse response) {
 
        //TODO: perform validation
        final String pattern = "/itemresource/*";
 
        String[] names = { "itemId" };
 
        Map<String, String> pathMap = PathUtil.getPathVariables(pattern, names,
                request.getPathInfo());
        String itemId = pathMap.get("itemId");
 
        itemModel.deleteItem(Integer.parseInt(itemId));
        JSONObject obj = new JSONObject();
        PrintWriter writer = null;
 
        try {
            obj.put("success", true);
 
            writer = response.getWriter();
            writer.print(obj.toString());
        }  catch (IOException e) {
            throw new RuntimeException(e);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
 
        return null;
    }
 
    @RequestMapping(value="/itemresource", method = RequestMethod.GET)
    public String itemInfo(HttpServletRequest request,
            HttpServletResponse response) {
 
        //TODO: perform validation
        List<Item> items = itemModel.getAllItems(0, 100);
        request.setAttribute("items", items);
 
        List<JSONObject> jsonList = new ArrayList<JSONObject>();
        for (Item item : items) {
            jsonList.add(new JSONObject(item));
        }
 
        JSONObject obj = new JSONObject();
        try {
            obj.put("success", true);
            obj.put("message", "Loaded data");
            obj.put("data", jsonList);
 
            PrintWriter writer;
            writer = response.getWriter();
            writer.print(obj.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
 
        return null;
    }
 
    @RequestMapping(value="/itemresource/*", method = RequestMethod.PUT)
    public String updateItem(@ModelAttribute Item item, BindingResult result) {
 
        beanValidator.validate(item, result);

        if(result.hasErrors()) {        	
        	return "response";
        }
                
        itemModel.updateItem(item);
        
        return "response";
    }
 
    @RequestMapping(value="/itemresource",method = RequestMethod.POST)
    public String createItem(@ModelAttribute Item item, BindingResult result) {
 
        beanValidator.validate(item, result);
        System.out.println(item);

        if(result.hasErrors()) {        	
        	return "response";
        }
        
        itemModel.createItem(item);
        
        return "response";
    }
 
    public ItemModel getItemModel() {
        return itemModel;
    }
 
    @Required
    @Autowired
    public void setItemModel(ItemModel itemModel) {
        this.itemModel = itemModel;
    }
    
    @Required
    @Autowired
	public void setBeanValidator(Validator beanValidator) {
		this.beanValidator = beanValidator;
	}

	public Validator getBeanValidator() {
		return beanValidator;
	}
    
}

src/main/java/com/test/PathUtil.java

package com.test;
 
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * Helper class to allow URL path's to be parsed into
 * EL Expression type tokens.
 */
public class PathUtil {
    public static final Map<String, String> getPathVariables(String patternStr,
            String[] names, String path) {
        String regExPattern = patternStr.replace("*", "(.*)");
 
        Map<String, String> tokenMap = new HashMap<String, String>();
 
        Pattern p = Pattern.compile(regExPattern);
 
        Matcher matcher = p.matcher(path);
 
        if (matcher.find()) {
            // Get all groups for this match
            for (int i = 0; i <= matcher.groupCount(); i++) {
                String groupStr = matcher.group(i);
                if (i != 0) {
                    tokenMap.put(names[i - 1], groupStr);
                }
            }
        }
        return tokenMap;
    }
}

Success and Failure JSP’s

The following file will be called when the data is accepted by the Controller class. When validation fails the JSP will display the errors. Notice we are using the customized tag classes defined above and the commandName and name attributes are required. This means that the error page would need to be customized for each form submission. If you guys are able find a way to make this generic then please send me a comment below.

src/main/webapp/WEB-INF/jsp/response.jsp

<%@ taglib prefix="jserror"	uri="http://test.com/jsrestlib" %>

<jserror:form commandName="item" name="item" method="post">
	<jserror:jserrors path="*"/>
</jserror:form>

Validation configuration

src/main/webapp/WEB-INF/validation.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>
<formset>
<form name="item">

    <field property="name" depends="required">
        <arg key="item.name" />
    </field>
    <field property="description" depends="required,minlength,maxlength">
        <arg0 key="item.description" />
        <arg1 name="minlength" key="${var:minlength}" resource="false"/>
        <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
        <var>
            <var-name>minlength</var-name>
            <var-value>5</var-value>
        </var>
        <var>
            <var-name>maxlength</var-name>
            <var-value>10</var-value>
        </var> 
    </field>
        <field property="stockQty" depends="integer">
        <arg0 key="item.stockQty" />
    </field>
    <field property="color" depends="required,mask">
        <msg name="mask" key="item.color.must.be"/>
        <arg0 key="item.color" />
        <var><var-name>mask</var-name><var-value>blue</var-value></var>
    </field>
</form>
</formset>
</form-validation>

src/main/webapp/WEB-INF/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>

Form Panel

The following html page contains the extJS form panel. When the user clicks submit, an Ajax call will be made to the server. If there are any form validation failures they will be displayed next to the text boxes. If all is okay then the page will display a modal success dialog.

src/main/webapp/form.html

<html>
<head>
 
<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>
 
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
 
<script type="text/javascript">
 
MyApp={};
MyApp.getContext = function() {
    var base = document.getElementsByTagName('base')[0];
    if (base && base.href && (base.href.length > 0)) {
        base = base.href;
    } else {
        base = document.URL;
    }
    return base.substr(0, base.indexOf("/", base.indexOf("/", base.indexOf("//") + 2) + 1));
};


function buildWindow() {
    Ext.QuickTips.init();
    Ext.form.Field.prototype.msgTarget = 'side';
    var bd = Ext.getBody();
 
    var fs = new Ext.FormPanel({
        labelWidth: 75,
        frame: true,
        title:'Form with crazy amount of validation',
        bodyStyle:'padding:5px 5px 0',
        defaults: {width: 230},
        waitMsgTarget: true,
        defaultType: 'textfield',     
 
        items: [{
                fieldLabel: 'Item Name',
                name: 'name'
            },{
                fieldLabel: 'Description',
                name: 'description'
            },{
                fieldLabel: 'Quantity',
                name: 'stockQty'
            }, {
                fieldLabel: 'Color',
                name: 'color'
            }
        ],     
 
    });
 
    var onSuccessOrFail = function(form, action) {
        var result = action.result;
 
        if(result.success) {
            Ext.MessageBox.alert('Success', 'Success!');
            userGrid.store.reload({});            
        } else { // put code here to handle form validation failure.
        }
 
    }
 
    var submit = fs.addButton({
        text: 'Submit',
        disabled:false,
        handler: function(){
            fs.getForm().submit({
                url:MyApp.getContext() +'/app/itemresource',
                waitMsg:'Submitting Data...',
                submitEmptyText: false,
                success : onSuccessOrFail,
                failure : onSuccessOrFail
            });
        }
    });
 
    fs.render('form-first');

    // the following is necessary to display the grid.
    
// The following is necessary to map the Record Fields to the grid columns.
var userColumns =  [
    {header: "Item Id", width: 40, sortable: true, 
    	dataIndex: 'itemId'},
    {header: "Item Name", width: 100, sortable: true, 
    	dataIndex: 'name', editor: new Ext.form.TextField({})},
    {header: "Description", width: 100, sortable: true, 	enableColumnHide: false, 
    	dataIndex: 'description', editor: new Ext.form.TextField({})},
    {header: "Quantity", width: 100, sortable: true, 
    	dataIndex: 'stockQty', editor: new Ext.form.TextField({})},
    {header: "Color", width: 100, sortable: true, 
    	dataIndex: 'color', editor: new Ext.form.TextField({})}
];
    

    var proxy = new Ext.data.HttpProxy({
    	url: MyApp.getContext() + '/app/itemresource'
    });

    var reader = new Ext.data.JsonReader({
        totalProperty: 'total',
        successProperty: 'success',
        idProperty: 'itemId',
        root: 'data',
        record: 'item',
        messageProperty: 'message',  // <-- New "messageProperty" meta-data
        fields : [
                  {name: 'itemId'},
                  {name: 'name', allowBlank: false},
                  {name: 'description', allowBlank: true},
                  {name: 'itemQty', allowBlank: true},
                  {name: 'color', allowBlank: true}
        ]
    });
     
    // Typical Store collecting the Proxy, Reader and Writer together.
    var store = new Ext.data.Store({
        id: 'item',
        restful: true,     // <-- This Store is RESTful
        proxy: proxy,
        reader: reader
    });    
 // load the store
    store.load();
    
    // Create a typical GridPanel with RowEditor plugin
    var userGrid = new Ext.grid.GridPanel({
        renderTo: 'user-grid',
        iconCls: 'icon-grid',
        frame: true,
        title: 'Records in HyperSQL Database',
        height: 300,
        store: store,
        columns : userColumns,
    	enableColumnHide: false, 
        viewConfig: {
            forceFit: true
        }
    });
    
}
Ext.onReady(buildWindow);
</script>
</head>
<body>

<div class="container" style="width:500px">
	<div id="form-first"></div>
    <div id="user-grid"></div>
</div>

</body>
</html>

src/main/webapp/index.html

<html>
<body>
<h3><a href="http://localhost:8080/crud/app/item">Part I - Simple form.</a></h3>
<h3><a href="form.html">Part II - Click here for the ExtJS Form</a></h3>
</body>
</html>

Test the application

In the project top folder type in the following command to compile and run the code in jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/crud/

An extJS form should display allowing you to enter values. Click submit and an Ajax form submission will be made to the Spring MVC Controller. Any validation failures will show next to the component.

Note: If you have any suggestions to improve the code please send me a comment below. I read every comment and appreciate constructive feedback.

Next Steps

That’s all for now.

27
Mar
11

Preventing XSS using commons validator

This page describes the process of validating html submitted form to prevent a Cross Site Scripting attack using commons validator.

Background

Adding input validation to your application adds an extra layer of security however its only the second best method to protect against cross site scripting attacks.[1]

The best method is to is to encode the output before displaying it to the user.[2]

Regardless, adding more layers of security doesn’t hurt.

Approach

The approach taken here is to white-list characters that are allowed to be entered by creating a custom validator. Developers can use “mask” rule however this

  1. clutters up the validator.xml file
  2. could possibly interfere with a a field’s existing mask rule

Requirements

  1. Successful completion of my previous post

Procedure

We will be re-using the project created in my previous post. Or you can skip the implementation and just follow along below.

Create a Custom Validator

The following class extends the FieldChecks class provided by the struts framework. The extra method is based on the mask method of the parent class. The only difference is that the mask pattern is hard coded into the implementation of the method.

src/main/java/com/test/InfoSecurityChecks.java

package com.test;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorUtil;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.validator.FieldChecks;
import org.apache.struts.validator.Resources;

public class InfoSecurityChecks extends FieldChecks {
	private static final long serialVersionUID = 1L;
	private static final Log log = LogFactory.getLog(InfoSecurityChecks.class);

	public InfoSecurityChecks() {
		super();
	}

	/**
	 * The code for this method was taken from the mask method of the parent
	 * class. The mask regular expression has been hard coded to only allow
	 * characters that are approved.
	 *
	 */
	public static boolean validateInfoSec(Object bean, ValidatorAction va,
			Field field, ActionErrors errors, HttpServletRequest request) {
		System.out.println("infosec validator ran");
		// The following is allowed
		// alpha numeric characters and space
		// special characters except < > ( )
		String mask = "^[A-Za-z0-9!\"\\\\#$%&'*+,/:;=\\.?@_\\`{\\|}\\~\\-\\^ ]*$";
		String value = null;
		if (isString(bean))
			value = (String) bean;
		else {
			value = ValidatorUtil.getValueAsString(bean, field.getProperty());
		}
		System.out.println(value);
		try {
			if ((!(GenericValidator.isBlankOrNull(value)))
					&& (!(GenericValidator.matchRegexp(value, mask)))) {
				errors.add(field.getKey(), Resources.getActionError(request,
						va, field));
				System.out.println("did not match returning false.");
				return false;
			}
			return true;
		} catch (Exception e) {
			 log.error(e.getMessage(), e);
		}
		return true;
	}
}

Modify the Validator Rules

Modify the validator-rules.xml and add an additional “validator” section towards the end.

src/main/webapp/WEB-INF/validator-rules.xml

      <validator name="infosec"
            classname="com.test.InfoSecurityChecks"
               method="validateInfoSec"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
                  msg="errors.infosec">
      </validator>

Add the following new line to the application.properties

src/main/resources/application.properties

errors.infosec=The input contains invalid characters.

Modify the validation.xml and add “infosec” to the end of the depends attribute of the “lastName” attribute of the “userInformation” form.

src/main/webapp/WEB-INF/validation.xml

    <field property="lastName" depends="required,infosec">
        <arg key="userInformation.lastName" />
    </field>

Test the application

Return to the project top level directory and execute the following command to start the jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/

An extJS form should display allowing you to enter values. Click submit and an Ajax form submission will be made to the Struts Action. Any validation failures will show next to the component.

Try entering a <script>alert(‘test xss’);</script> in the last name field.

System should display a validation failure message indicating that there are invalid characters in the input.

References

[1] XSS (Cross Site Scripting) Prevention Cheat Sheet
[2] http://www.ibm.com/developerworks/web/library/wa-secxss/

27
Mar
11

ExtJS form validation using commons-validator

This page describes the process of using commons-validator to validate extJS form submissions.

Background

Commons validation is a mature project that’s been out there for a while now. It became popular when it was introduced as part of the Struts framework. Commons validation allows developers to specify validation rules inside XML files, thus allowing them to be applied uniformly across the site. In addition the same set of rules can be used to generate both client side and server side validation thus reducing maintenance cost.

In the interest of brevity, this page will only cover the server side validation.

Using commons-validator by itself doesn’t give you much benefit. All you get is the ability to specify rules and validation logic that returns whether the validation passed or failed.

You are left on your own to:

  1. Write your own code to iterate thru the form fields and collect the failure messages
  2. Write a servlet to handle requests over HTTP and present the failure messages back to the client
  3. Internationalization Support

Although its not that much work, when you start implementing it you quickly realize that this boiler plate code is something that a framework should provide.

This boiler plate code has been written years ago by the makers of the Struts framework. By extending the framework slightly, we can use it to validate forms submitted by extJS components.

Struts comes pre-packaged with the “html:errors” tag. Upon validation failures struts returns the user back to the “input” page and the input page contains a “html:errors” tag that displays the failed validation along with a description of the failure in HTML format.

The Approach

The approach presented here focues on extending the “html:errors” tag to return JSON instead of html. The JSON is parsed by the extJS formPanel and the errors are presented to the user.

On success the extJS formPanel expects:

{success: true, msg : 'Thank you for your submission.'}

On failure the extJS formPanel expects:

{
   success : false,
   msg     : 'This is an example error message',
   errors  : {
      firstName : 'Sample Error for First Name',
      lastName  : 'Sample Error for Last Name'
   }
}

Note:
Some of the code on this page are not core to the understanding “approach”, for this reason the code will initially be collapsed.

Requirements

Since this page is about integrating 3 different technologies together a basic understanding of struts, commons-validation, and extJS is assumed. If any of these technologies are not clear then please visit some of my other tutorial pages.

  1. Maven 2
  2. Basic Understanding of extJS, Struts 1 and commons-validator

Outline

This tutorial is quite lengthy.

Here is an outline:

  1. Create and configure a new project
  2. Extend the html:errors tag
  3. Configure the Struts Framework
  4. Create the struts action class and JSP
  5. Configure the page validation

Create and configure a new project

I would recommend starting from a new project.

Create a new project using Maven 2.

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp

groupId: com.test
artifactId: form-validation

Answer the rest of the questions with defaults “Just hit the enter key”,

cd to the project base folder.

cd form-validation

Since this is a web project maven2 does not create the java source folder.

Create missing folder
mkdir -p src/main/java/com/test

Modify the project Configuration

Replace the pom.xml with the following.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test</groupId>
	<artifactId>form-validation</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>form-validation Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>commons-validator</groupId>
			<artifactId>commons-validator</artifactId>
			<version>1.1.3</version>
		</dependency>
		<dependency>
			<groupId>struts</groupId>
			<artifactId>struts</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.geronimo.specs</groupId>
			<artifactId>geronimo-servlet_2.5_spec</artifactId>
			<version>1.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.7.0</version>
		</dependency>
		<dependency>
			<groupId>commons-digester</groupId>
			<artifactId>commons-digester</artifactId>
			<version>1.8</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20090211</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>form-validation</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.0.2</version>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>7.0.0.v20091005</version>
				<configuration>
					<scanIntervalSeconds>2</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Extend the html:errors tag

Extend the html:errors tag to return errors in JSON format.

src/main/java/com/test/ExtJsonErrorsTag.java

package com.test;

import java.util.Iterator;

import javax.servlet.jsp.JspException;

import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.taglib.html.ErrorsTag;
import org.apache.struts.util.RequestUtils;
import org.apache.struts.util.ResponseUtils;
import org.json.JSONException;
import org.json.JSONObject;

public class ExtJsonErrorsTag extends ErrorsTag {

	private static final long serialVersionUID = 1L;

	public int doStartTag() throws JspException {
		ActionErrors errors = null;
		JSONObject jsonReturn = new JSONObject();
		try {
			errors = RequestUtils.getActionErrors(this.pageContext, this.name);
		} catch (JspException e) {
			RequestUtils.saveException(this.pageContext, e);
			throw e;
		}
		if ((errors == null) || (errors.isEmpty())) {
			
			try {
				jsonReturn.put("success", true);
				jsonReturn.put("msg", "Thank you for your submission.");
			} catch (JSONException e) {
				throw new JspException(e);
			}

			ResponseUtils.write(this.pageContext, jsonReturn.toString());
			return 1;
		}

		Iterator errorIterator = errors.properties();
		JSONObject jsonErrors = new JSONObject();

		while (errorIterator.hasNext()) {
			String property = (String) errorIterator.next();
			processErrors(errors, property, jsonErrors);
		}

		try {
			jsonReturn.put("success", false);
			jsonReturn.put("msg", "There was a validation failure.");
			jsonReturn.put("errors", jsonErrors);
			ResponseUtils.write(this.pageContext, jsonReturn.toString());
		} catch (JSONException e) {
			throw new JspException(e);
		}

		return 1;
	}

	private void processErrors(ActionErrors errors, String property,
			JSONObject jsonErrors) throws JspException {
		String message;
		Iterator reports = (property == null) ? errors.get() : errors
				.get(property);
		while (reports.hasNext()) {
			ActionError report = (ActionError) reports.next();

			message = RequestUtils.message(this.pageContext, this.bundle,
					this.locale, report.getKey(), report.getValues());

			if (message != null) {
				try {
					jsonErrors.put(property, message);
				} catch (JSONException e) {
					throw new JspException(e);
				}
			}
		}

	}
}

This is the tag descriptor for the above tag. Nothing interesting here. Just copy and paste this into struts-ext-html.tld.

src/main/webapp/WEB-INF/struts-ext-html.tld

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
	<tlibversion>1.2</tlibversion>
	<jspversion>1.1</jspversion>
	<shortname>html-ext</shortname>
	<uri>http://struts.apache.org/tags-html-ext</uri>
	<tag>
		<name>errors</name>
		<tagclass>com.test.ExtJsonErrorsTag</tagclass>
		<bodycontent>empty</bodycontent>
		<attribute>
			<name>bundle</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>footer</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>header</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>locale</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>name</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>prefix</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>property</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>suffix</name>
			<required>false</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
</taglib>

Action Class and JSP

This is where you provide logic that will run after the form is validated. Control will only be passed to the execute method when the form is fully validated.

src/main/java/com/test/UserSubmitAction.java

package com.test;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
 
public class UserSubmitAction extends org.apache.struts.action.Action {
    public ActionForward execute(ActionMapping mapping,ActionForm form,
           HttpServletRequest request,HttpServletResponse response) throws Exception {
 
    	System.out.println("action ran.");

    	return mapping.findForward("success");
    }
}

The following JSP presents an extJS form to allow the user to enter data.

src/main/webapp/index.jsp

<html>
<head>
 
<link rel="stylesheet" type="text/css"
    href="http://dev.sencha.com/deploy/ext-3.3.1/resources/css/ext-all.css"/>
 
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/adapter/ext/ext-base-debug.js">
</script>
<script type="text/javascript"
    src="http://dev.sencha.com/deploy/ext-3.3.1/ext-all-debug.js">
</script>
 
<script type="text/javascript"> 
function buildWindow() {
    Ext.QuickTips.init();
    Ext.form.Field.prototype.msgTarget = 'side';
    var bd = Ext.getBody();

    var fs = new Ext.FormPanel({
        labelWidth: 75, 
        frame: true,
        title:'Form with crazy amount of validation',
        bodyStyle:'padding:5px 5px 0',
        width: 350,
        defaults: {width: 230},
        waitMsgTarget: true,
        defaultType: 'textfield',       
 
        items: [{
                fieldLabel: 'First Name',
                name: 'firstName'
            },{
                fieldLabel: 'Last Name',
                name: 'lastName'
            },{
                fieldLabel: 'Company',
                name: 'company'
            }, {
                fieldLabel: 'Email',
                name: 'email',
                vtype:'email'
            },{
                fieldLabel: 'Time',
                name: 'time'
            } 
        ],       
 
    });
 
    var onSuccessOrFail = function(form, action) {
        var result = action.result;
 
        if(result.success) {
            Ext.MessageBox.alert('Success', 'Success!');
        } else { // put code here to handle form validation failure.
        }
 
    }
 
    var submit = fs.addButton({
        text: 'Submit',
        disabled:false,
        handler: function(){
            fs.getForm().submit({
                url:'/form/UserSubmit',
                waitMsg:'Submitting Data...',
                submitEmptyText: false,
                success : onSuccessOrFail,
                failure : onSuccessOrFail
            });
        }
    });
 
    fs.render('form-first');
}
Ext.onReady(buildWindow);
</script>
</head>
<body>
<div id="form-first"></div>
</body>
</html>

Success and Failure JSP’s

This JSP displays regardless if there was a validation failure or not. This is to satisfy the extJS component requirement that all form submissions should return a result.

src/main/webapp/WEB-INF/result.jsp

<%@ taglib uri="/tags/struts-html-ext" prefix="html-ext" %>
<html-ext:errors/> 

Validation configuration

All the fields of the form are required. In addition the first name must be at least 10 and no more than 15 characters. The email address must be valid formatted and the time field must be in the proper format in order to proceed. All of this is coded into the following file.

src/main/webapp/WEB-INF/validation.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>
<formset>
<form name="userInformation">
    <field property="firstName" depends="required,minlength,maxlength">
        <arg0 key="userInformation.firstName" />
        <arg1 name="minlength" key="${var:minlength}" resource="false"/>
        <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
        <var>
        	<var-name>minlength</var-name>
        	<var-value>10</var-value>
        </var>
        <var>
        	<var-name>maxlength</var-name>
        	<var-value>15</var-value>
        </var>
        
    </field>
    <field property="lastName" depends="required">
        <arg key="userInformation.lastName" />
    </field>
    <field property="company" depends="required">
        <arg key="userInformation.company" />
    </field>
    <field property="email" depends="required,email">
        <arg key="userInformation.email" />
    </field>
    <field property="time" depends="required,mask">
        <msg name="mask" key="userInformation.time.maskmsg"/>
        <arg0 key="userInformation.time" />
        <var><var-name>mask</var-name><var-value>^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$</var-value></var>
    </field>
</form>
</formset>
</form-validation>

Configure the Struts Framework

The following files are necessary to get the struts framework initialized and ready to process form submissions. The lines specific to this application have been highlighted.

Struts Config

The following struts configuration file contains a dyna-form and an action mapping. The /UserSubmit action mapping “input” page will be invoked if there are validation failures. If all goes well with validation the control will forward to “result.jsp” which will display JSON output expected by the extJS component.

src/main/webapp/WEB-INF/struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>
    <!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>
	<form-beans>
		<form-bean name="userInformation"
			type="org.apache.struts.validator.DynaValidatorForm">
			<form-property name="firstName" type="java.lang.String" />
			<form-property name="lastName" type="java.lang.String" />
			<form-property name="company" type="java.lang.String" />
			<form-property name="email" type="java.lang.String" />
			<form-property name="time" type="java.lang.String" />
		</form-bean>
	</form-beans>

	<action-mappings>
		<action name="userInformation" path="/UserSubmit" input="/WEB-INF/result.jsp"
			type="com.test.UserSubmitAction" validate="true">
			<forward name="success" path="/WEB-INF/result.jsp"></forward>
		</action>
	</action-mappings>

	<!-- The "application.properties" is located in the root of the classpath. -->
	<message-resources parameter="application" />

	<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
		<set-property property="pathnames"
			value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml," />
	</plug-in>

</struts-config>

The following file was taken from the commons-validator framework. It contains information about the various classes that perform validation. Just copy and paste this into validator-rules.xml. The client side JavaScript validation code has been removed since we will only be performing server side validation. I am thinking about writing another tutorial on how to have commons validator generate extJS specific client side JavaScript validation. Let me know if you guys are interested in seeing this.

src/main/webapp/WEB-INF/validator-rules.xml

<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">

<form-validation>
   <global>
      <validator name="required"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateRequired"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
                  msg="errors.required">
      </validator>

      <validator name="requiredif"
                 classname="org.apache.struts.validator.FieldChecks"
                 method="validateRequiredIf"
                 methodParams="java.lang.Object,
                               org.apache.commons.validator.ValidatorAction,
                               org.apache.commons.validator.Field,
                               org.apache.struts.action.ActionErrors,
                               org.apache.commons.validator.Validator,
                               javax.servlet.http.HttpServletRequest"
                 msg="errors.required">
      </validator>

      <validator name="minlength"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMinLength"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.minlength">
      </validator>


      <validator name="maxlength"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMaxLength"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.maxlength">

      </validator>


      <validator name="mask"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateMask"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.invalid">

      </validator>


      <validator name="byte"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateByte"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.byte"
       jsFunctionName="ByteValidations">
      </validator>

      <validator name="short"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateShort"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.short"
       jsFunctionName="ShortValidations">
      </validator>

      <validator name="integer"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateInteger"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.integer"
       jsFunctionName="IntegerValidations">
      </validator>

      <validator name="long"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateLong"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.long"/>
      <validator name="float"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateFloat"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.float"
       jsFunctionName="FloatValidations">
      </validator>

      <validator name="double"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateDouble"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.double"/>

      <validator name="date"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateDate"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.date"
       jsFunctionName="DateValidations">


      </validator>

<!-- range is deprecated use intRange instead -->
      <validator name="range"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateIntRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="integer"
                  msg="errors.range">
      </validator>

      <validator name="intRange"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateIntRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="integer"
                  msg="errors.range">

      </validator>

      <validator name="floatRange"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateFloatRange"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends="float"
                  msg="errors.range">
      </validator>

      <validator name="creditCard"
            classname="org.apache.struts.validator.FieldChecks"
               method="validateCreditCard"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                       org.apache.commons.validator.Field,
                       org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.creditcard">

      </validator>

     <validator name="email"
            classname="org.apache.struts.validator.FieldChecks"
              method="validateEmail"
         methodParams="java.lang.Object,
                       org.apache.commons.validator.ValidatorAction,
                      org.apache.commons.validator.Field,
                      org.apache.struts.action.ActionErrors,
                       javax.servlet.http.HttpServletRequest"
              depends=""
                  msg="errors.email">
      </validator>
   </global>
</form-validation>

The following file contains the messages to display to the user.

src/main/resources/application.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.

userInformation.firstName=First Name
userInformation.lastName=Last Name
userInformation.company=company
userInformation.email=email
userInformation.time=time
userInformation.time.maskmsg=Time must be formatted like (##:## AM|PM)

This is a bare minimum log4j.properties file.

src/main/resources/log4j.properties

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=INFO, A1
 
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c - %m%n

src/main/webapp/WEB-INF/web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>
         /WEB-INF/struts-config.xml
        </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
 
    </servlet>
 
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>/form/*</url-pattern>
    </servlet-mapping>

  <!-- Struts Tag Library Descriptors -->
 
  <taglib>
    <taglib-uri>/tags/struts-html-ext</taglib-uri>
    <taglib-location>/WEB-INF/struts-ext-html.tld</taglib-location>
  </taglib>
  
</web-app>

Test the application

In the project top folder type in the following command to compile and run the code in jetty servlet engine.

mvn clean compile jetty:run

Navigate to the following URL:
http://localhost:8080/

An extJS form should display allowing you to enter values. Click submit and an Ajax form submission will be made to the Struts Action. Any validation failures will show next to the component.

That’s all for now.

06
Dec
09

JSF form Validation

This page describes the process of setting up validation in a JSF application.

JSF Standard Validators

JSF comes with standard validators

  1. <validateDoubleRange> : validates to see if the value is a double
  2. <validateLongRange> : validates to see if the value is a long.
  3. <validateLength> : validates to see if the string value for length.

With all of the tags above you can specify a minimum and/or maximum value.

Requirements

  • Maven
  • That’s pretty much it!

Generate the project from an archetype:

mvn archetype:generate -DarchetypeGroupId=org.vtechfw -DarchetypeArtifactId=springmvc-jsf-archetype -DarchetypeVersion=1.0-SNAPSHOT -DarchetypeRepository=http://www.vermatech.com/m2repo

groupId: com.test
artifactId: jsf-validation

The rest of the questions just hit enter choosing the defaults.

test to see if the project comes up

cd jsf-validation;
mvn jetty:run;

Navigate to the application http://localhost:8080/ It should come up with a homepage.

If it does then the test is complete You can continue with the rest of the page.

Message Resources Property File

This file contains all the error messages we will display when validation fails.

src/main/resources/com/test/jsftest/messages.properties

username_required=Username is required
password_required=Password is required
between=Value must be between 2 and 10.

In the resource bundle if the key are composed of multilple word separated by ▒??dot▒??(.),you should represent that key in the JSP page using the format #{msg[‘key’]}.This is a must because ▒??dot▒??(.) is considered as a special character in JSF expression language.

The next step is to get the message bundle property registered in the application.

src/main/webapp/WEB-INF/faces-config.xml

<application>
    <resource-bundle>
        <base-name>
            com.test.jsftest.messages
        </base-name>
        <var>msgs</var>
    </resource-bundle>
</application>

As an alternative you can also type the following in each JSP:

<f:loadBundle basename="com.test.jsftest.messages" var="msgs"/>

Create the Managed Bean Java Class

In the Reference section below get the source for UserManagedBean.java and create it in your source directory.
src/main/java/com/test/jsftest/UserManagedBean.java

package com.test.jsftest;

public class UserManagedBean {
        private String username = "";
        private String password = "";

        public String getUsername() {
                return username;
        }
        public void setUsername(String username) {
                this.username = username;
        }

        public String getPassword() {
                return password;
        }

        public void setPassword(String password) {
                this.password = password;
        }
}

Open up faces-config.xml and insert this right above the managed beans.
src/main/webapp/WEB-INF/faces-config.xml

<managed-bean>
        <managed-bean-name>userManagedBean</managed-bean-name>
        <managed-bean-class>com.test.jsftest.UserManagedBean</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

Setup the Form Page

Next we set up the form page that the user will pull up to enter the username and pssword. The complete source for this page is located in the Reference section. Please copy and paste the source into your webapp/faces directory now.

Once you are done then run the project using

mvn jetty:run

Run The Application

http://localhost:8080/faces/login.faces

Discussions

As you can see below the following is a snippit of what is defined in the login.jsp file.

                <p>Enter your username here: <br/>
                <h:inputText id="username" value="#{username}" size="35"
                        required="true" requiredMessage="#{msgs.username_required}"
                        validatorMessage="#{msgs.between}">
                        <f:validateLength minimum="2" maximum="10" />
                </h:inputText>
                <h:message for="username"/>
                </p>

I am trying to find out how to place 2 and 10 with parameter markers. If you guys know how to do this then please post a comment.

Adding Color

<h:message for="username" style="color: red;"/>

Showing Bulleted list of errors

In order to show a bulleted list of errors on top or bottom of the screen just put this tag at the desired location.

<h:messages showDetail="#{true}" showSummary="#{false}" />

For Reference

In case you got lost the complete source for each file is included here:

src/main/java/com/test/jsftest/UserManagedBean.java

package com.test.jsftest;

public class UserManagedBean {
        private String username = "";
        private String password = "";

        public String getUsername() {
                return username;
        }
        public void setUsername(String username) {
                this.username = username;
        }

        public String getPassword() {
                return password;
        }

        public void setPassword(String password) {
                this.password = password;
        }
}

src/main/webapp/WEB-INF/faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
              version="1.2">
<application>
    <resource-bundle>
        <base-name>
            com.test.jsftest.messages
        </base-name>
        <var>msgs</var>
    </resource-bundle>
</application>

<managed-bean>
        <managed-bean-name>userManagedBean</managed-bean-name>
        <managed-bean-class>com.test.jsftest.UserManagedBean</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

</faces-config>

src/main/webapp/faces/login.jsp

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
    <html>
    <head>
        <title>Login Page</title>
    </head>
    <body>
        <h:messages showDetail="#{true}" showSummary="#{false}" />
    <h:form>
                <p>This is a simple login page using Faces.</p>
                <p>Enter your username here: <br/>

                <h:inputText id="username" value="#{userManagedBean.username}" size="35"
                        required="true" requiredMessage="#{msgs.username_required}"
                        validatorMessage="#{msgs.between}">
                        <f:validateLength minimum="2" maximum="10" />
                </h:inputText>
                <h:message for="username" style="color: red;"/>
                </p>
                <p>Enter your password here: <br/>
                <h:inputText id="password" value="#{userManagedBean.password}" size="35"
                        required="true" requiredMessage="#{msgs.password_required}">
                        <f:validateLength minimum="2" maximum="10" />
                </h:inputText> <h:message for="password"/>
                </p>
                <h:commandButton value="Submit" action="submit"/>
    </h:form>
    </body>
    </html>
</f:view>

src/main/resources/com/test/jsftest/messages.properties

username_required=Username is required
password_required=Password is required
between=Value must be between 2 and 10.

Thats all for now!

This site is a collaborative effort! The complete text and sourcecode for this is available on GitHub. Corrections and enhancements are welcome, please make the change and submit a pull request in the comment area below.



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

Join 77 other followers

November 2017
S M T W T F S
« Oct    
 1234
567891011
12131415161718
19202122232425
2627282930  

Blog Stats

  • 841,714 hits