Struts Valdiation Framework
We need to include the below plug-in, in struts-config xml file to enable Validation framework in Struts.
<!-- Validator Configuration -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/>
</plug-in>
Form objects:
All our action form objects should extend ValidatorForm in order for the Validation Framework to pick up the properties from form objects.
Example:
public class ContactForm extends ValidatorForm { }
public class PricingForm extends ValidatorForm { }
Then we define Validation.xml with the corresponding form name like the one highlighted in red color.
Validation.xml:
<form-validation>
<formset>
<form name="ContactForm">
<field property="firstName" depends="required,maxlength">
<arg0 key="contact.errors.firstName" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>128</var-value>
</var>
</field>
</form>
<form name="PricingForm">
<field property="firstName" depends="required,maxlength">
<arg0 key="contact.errors.firstName" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>128</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
If in case we use action path’s (Defined in struts config xml file) directly in form tags name attribute of validation.xml, then we should extend ValidatorActionForm
Example:
public class BankForm extends ValidatorActionForm { }
<form name="/app/submitBank">
<field property="bankName" depends="required,maxlength">
<arg0 key="bank.errors.bankName" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>128</var-value>
</var>
</field>
</form>
<form name="/app/saveBank">
<field property="bankName" depends="required,maxlength">
<arg0 key="bank.errors.bankName" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>128</var-value>
</var>
</field>
</form>
Note:
When to go for ValidatorActionForm:
One of our modules (i.e.) Request is having many screens, which means we have many action paths in struts-config for this module but the form name is same for every path (RequestForm). Each of the path which leads to a different screen with the same form bean demands a different set of validation. In this scenario we need to go for path based validation rather than form name based validation.
For form name based validation we use:
public class ContactForm extends ValidatorForm { }
public class BankForm extends ValidatorActionForm { }
What's the difference between ValidatorForm and ValidatorActionForm?
In addition to the two standard options for creating Form Beans, Validator provides an advanced feature for tying multiple validation definitions to one Form Bean definition. When you use validatorForm- or DynaValidatorForm-based Form Beans, Validator uses the logical name for the Form Bean from the struts-config.xml file to map the Form Bean to validation definitions in the validation.xml file. This mechanism is great in most cases, but in some scenarios, Form Beans are shared among multiple actions. One action may use all of the Form Bean's fields, and another action may use only a subset of the fields. Because validation definitions are tied to the Form Bean, the action that uses only a subset of the fields has no way of bypassing validations for the unused fields. When the Form Bean is validated, it generates error messages for the unused fields, because Validator has no way of knowing not to validate the unused fields; it simply sees them as missing or invalid.
To solve this problem, Validator provides two additional ActionForm subclasses that allow you to tie validations to actions instead of to Form Beans. That way you can specify which validations to apply to the Form Bean based on which action is using the Form Bean. For concrete Form Beans, you subclass org.apache.struts.validator.ValidatorActionForm, as follows:
public class AddressForm extends ValidatorActionForm { ... }
Struts-config.xml – setting validation ON:
<!-- When user clicks on Save button on the Request.jsp -->
<action
path="/app/saveRequest"
attribute="RequestForm"
name="RequestForm"
scope="session"
validate="true"
input="/jsp/request/jsp/Request.jsp"
type="abc.xyz.ijk.projectName.action.moduleName.SaveRequestAction">
<forward name="success" path="/jsp/request/jsp/Request.jsp" />
</action>
MessageResources.properties:
We define the properties file in the path
WEB-INF\ (or) WEB-INF\classes\resources
We can have
1. Struts provided default error messages,
2. Custom error messages,
3. Custom success messages.
Contents of the properties file includes
# Struts Validator Error Messages
errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
# -- Custom Error Messages
# -- Bank Related Error Messages
bank.errors.bankName=Bank Name
bank.errors.gciNumber=GCI Number
#---Pricing related ErrorMessages
pricing.errors.commentType = PricingCommentType
# Success Messages
benef.msg.success={0} - beneficiary is saved successfully
pricing.msg.success={0} - Pricing is saved successfully
request.msg.update.success={1} - Auto approval logic has successfully run and request status is {0}
Custom Validation:
Tutorial Link:
We go for custom validation whenever the validation goes complex or the ones that we cannot manage in validation.xml. For example to validate a field based on 2 fields and also iterating a list and comparing a field from the list of objects with one of the 2 fields, and the list of validation requirement continues to be complex. In such a case we need to handle the validation in java layer.
We have Validation-rules.xml file which comes as part of struts framework. Here we have a set of pre-defined rules. We can also add our own custom rules which can be implemented in Validation.xml.
Here is how we go about implementing custom validation
# Struts Validator routine
<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.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.double"/>
# Custom Validator routine
<validator name="validateRequest"
classname="abc.xyz.projectName.validator.RequestValidator"
method="validateRequest"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
msg=""
depends=""/>
Example:
Struts Mapping.
<action
path="/app/saveCoronaInfo"
attribute="BankForm"
name="BankForm"
scope="session"
validate="true"
input="/jsp/bank/jsp/CoronaInfo.jsp"
type="com.bofa.gcib.gtem.action.bank.SaveCoronaInfoAction">
<forward name="success" path="/jsp/bank/jsp/CoronaInfoCloseWindow.jsp" />
</action>
Validation.xml
<form name="/app/saveCoronaInfo">
<field property="tbuKey" depends="validateCoronaId"/>
</form>
Validator-rules.xml
<validator name="validateCoronaId"
classname="abc.xyz.projectName.validator.CommonValidator"
method="validateCoronaIdFromBank"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
depends=""
msg="">
</validator>
Now we will learn about writing custom validator class. The method name (validateCoronaIdFromBank) should be specified in the method attribute of validator tag (above in validator-rules.xml)
package abc.xyz.projectName.validator
public class CommonValidator {
public static boolean validateCoronaIdFromBank (
Object form,
ValidatorAction va,
Field field,
ActionMessages messages,
Validator validator,
HttpServletRequest request) {
try {
BankForm objBankForm =(BankForm) form;
List<CoronaBean> objCoronoList = objBankForm.getCoronaList();
Set objCoronoSet = new HashSet();
for(CoronaBean objCoronoBean: objCoronoList) {
boolean result = objCoronoSet.add(objCoronoBean.getTbuKey());
if (result == false) {
ActionMessage em = new ActionMessage("bank.errors.coronoID");
messages.add(field.getKey(),em);
return false;
}
}
}
catch(Exception ex) {
ex.printStackTrace();
return false;
}
return true;
}
Creating custom Date validation.
Validation.xml:
<form name="/app/summaryRequestMaturityDate">
<field property="requestDetailsKey" depends="validateDates">
<arg0 key="request.errors.newMaxMaturityDate"/>
<arg1 key="request.errors.maxMaturityDate"/>
<var>
<var-name>defaultDate1</var-name>
<var-value>newMaxMaturityDateStr</var-value>
</var>
<var>
<var-name>defaultDate2</var-name>
<var-value>maxMaturityDateString</var-value>
</var>
</field>
Validator-rules.xml:
<validator name="validateDates"
classname="com.bofa.gcib.gtem.validator.RequestValidator"
method="validateDates"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
depends=""
msg="">
</validator>
public class RequestValidator {
public static boolean validateDates (
Object form,
ValidatorAction va,
Field field,
ActionMessages messages,
Validator validator,
HttpServletRequest request) {
try {
String date1 = null;
String date2 = null;
Date utildate2=null;
Date utildate1=null;
if(field.getVarValue("defaultDate1")!=null && !(("").equals(field.getVarValue("defaultDate1")))){
date1 = field.getVarValue("defaultDate1");
utildate1= DateUtility.stringToUtilDate(ValidatorUtils.getValueAsString(form, date1));
}
Explanation:
date1 = field.getVarValue("defaultDate1");
defaultDate1 is the key and newMaxMaturityDateStr is the value specified in validation.xml. The above line of code will get the value.
ValidatorUtils.getValueAsString(form, date1)
By passing the form object, since the date1 property (newMaxMaturityDateStr) is part of the form object, the method getValueAsString() gets the real data from the form object, in this case the date is fetched and returned as a string.
Regex:
To restrict textbox values only to characters of both cases.
^[A-Za-z]*$
To restrict textbox values only to numbers, comma and dot.
^[0-9,.]*$
To restrict zero (0) in a textbox.
^[^0]*$
Validation.xml – Examples:
Case - 1: Have a textbox and user should not enter the value 0.
IF 0 THEN validation error message should be thrown.
Solution:
The field requestAbcKey is valid only when it is NOT 0. For example when user enters 0 for this textbox then a validation message should be thrown.
Validation.xml
<field property="requestAbcKey" depends="validwhen">
<arg0 key="request.errors.requestAbcKey"/>
<var>
<var-name>test</var-name>
<var-value>(requestAbcKey != 0)</var-value>
</var>
</field>
Case - 2: This particular field (importerName) is mandatory (required) only if the field tbuandtxnCode’s value is EQUAL to 2.
<field property="importerName" depends="requiredif">
<arg0 key="request.errors.importerName"/>
<var>
<var-name>field[0]</var-name>
<var-value>tbuandtxnCode</var-value>
</var>
<var>
<var-name>fieldTest[0]</var-name>
<var-value>EQUAL</var-value>
</var>
<var>
<var-name>fieldValue[0]</var-name>
<var-value>2</var-value>
</var>
</field>
Instead of 2 in <var-value>2</var-value> we can also give strings like XYZ and char values like Y or N.
Case - 3: This particular field (requestAvailableKey) is mandatory (required) only if the field paymenttermsCheck’s value is EQUAL to 2 AND requestStatusKey is NOT EQUAL to 0
<field property="requestAvaiableKey" depends="requiredif">
<arg0 key="request.errors.requestAvaiableKey"/>
<var>
<var-name>field[0]</var-name>
<var-value>paymenttermsCheck</var-value>
</var>
<var>
<var-name>fieldTest[0]</var-name>
<var-value>EQUAL</var-value>
</var>
<var>
<var-name>fieldValue[0]</var-name>
<var-value>2</var-value>
</var>
<var>
<var-name>field[1]</var-name>
<var-value>requestStatusKey</var-value>
</var>
<var>
<var-name>fieldTest[1]</var-name>
<var-value>NOTEQUAL</var-value>
</var>
<var>
<var-name>fieldValue[1]</var-name>
<var-value>0</var-value>
</var>
<var>
<var-name>fieldJoin</var-name>
<var-value>AND</var-value>
</var>
</field>
Case - 4: This particular field’s (otherAbc) length shouldn’t be greater than 4000 characters
<field property="otherAbc" depends="maxlength">
<arg0 key="request.errors.otherAbc"/>
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>4000</var-value>
</var>
</field>
Case - 5: This particular field is a mandatory field.
If we say depends="required", then automatically is required. is added to the end of the error message.
Message Content:
tbu.errors.division = Division
<field property="division" depends="required">
<arg0 key="tbu.errors.division" />
</field>
Division is required.
Case - 6: In this particular field (textbox) user should enter only digits between 0 – 9 along with special characters comma and dot. If user enters anything else we need to get validation messages.
<field property="strAbcLimit" depends="mask">
<arg0 key="beneficiary.errors. strAbcLimit " />
<var>
<var-name>mask</var-name>
<var-value>^[0-9,.]*$</var-value>
</var>
</field>
Here this (^[]*$) is the standard syntax and what goes inside the square brackets forms the rule for validation.
Case - 7: This is a special case where we iterate a list which contains objects of a particular type (Class Bank) which contains few fields. Out of those fields I need to validate 2 fields.
Here systemStatusKey’s value should be equal to 3 and commentTypeKey should not be equal to 0.
<field property="commentTypeKey" indexedListProperty="tbuCommentList" depends="validwhen">
<arg0 key="tbu.errors.commentType" />
<var>
<var-name>test</var-name>
<var-value>
((tbuCommentList[].systemStatusKey == 3) or (tbuCommentList[].commentTypeKey != 0))
</var-value>
</var>
</field>
<field property="description" indexedListProperty="tbuCommentList" depends="validwhen">
<var>
<var-name>test</var-name>
<var-value>
((tbuCommentList[].description != null) or (tbuCommentList[].systemStatusKey == 3))
</var-value>
</var>
</field>
Case - 8: Mandatory check for date field. Have a textbox and an adjacent calendar icon. Date has to be mandatorily selected.
<form name="/app/summaryRequestMaturityDate">
<field property="newMaxMaturityDateStr" depends="required, date">
<arg0 key="request.errors.newMaxMaturityDate" />
<var><var-name>datePatternStrict</var-name>
<var-value>MM/dd/yyyy</var-value></var>
</field>
</form>
The date rule checks to see if the field is a valid date.
The datePatternStrict variable will ensure that 5-29-70 does not work; when the pattern MM-dd-yyyy is used, only 05-29-1970 works (notice the 0 before the 5)
Case - 9: Mandatory check for double field. Have a textbox which should accept only a int/double value and not a string.
To do a validation on double field we need to have the field as String and NOT as double.
<field property="stringDDPLmt" depends="double,maxlength">
<arg0 key="beneficiary.errors.benefDDPLmt" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var>
<var-name>maxlength</var-name>
<var-value>31</var-value>
</var>
</field>
Case - 10: This field is a property which is inside a List of objects and is a required field and a double field.
<field property="strFacilityLimitAmount" indexedListProperty="countryFacilityList" depends="required,double">
<arg0 key="country.errors.facilityLimitAmount" />
</field>
Case - 11: This field is a property which is inside a List of objects and we need to ensure this property contains only integer values and nothing else.
<field property="maxTenor" indexedListProperty="bankFacilityList" depends="integer" >
<arg0 key="bank.errors.maxTenor"/>
</field>
Case - 12: This field is valid only when it is not equal to null or systemstatuskey is equal to 3.
<field property="contactName" indexedListProperty="contactList" depends="validwhen">
<arg0 key="bank.errors.contactName" />
<var>
<var-name>test</var-name>
<var-value>
((contactList[].contactName != null)or(contactList[].systemStatusKey == 3))
</var-value>
</var>
</field>
<field property="countryDivisionKey" depends="validwhen">
<arg0 key="country.errors.countryDivisionKeyMap" />
<var>
<var-name>test</var-name>
<var-value>(countryDivisionKey != 0)</var-value>
</var>
</field>
Case 13:
When the user gives the gci number in a wrong format, we need to throw a validation message with the correct format.
In the below example if the user gives a number greater than 15 digits then we get the message
GCI Number cannot be greater than 15 characters.
Also if the user gives a wrong format, then we need to throw the message
GCI Number should be in the format XX-XXX-XXXX.
This is acheived through <msg> tag.
bank.errors.gciNumber=GCI Number
validation.error.phone.format={0} should be in the format XX-XXX-XXXX.
Error Message: GCI Number should be in the format XX-XXX-XXXX.
<field property="gciNumber" depends="maxlength,mask">
<arg0 key="bank.errors.gciNumber" />
<arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
<var><var-name>maxlength</var-name><var-value>15</var-value></var>
<msg name="mask" key="validation.error.phone.format"/>
<arg0 key="bank.errors.gciNumber"/>
<var>
<var-name>mask</var-name>
<var-value>^\d{2}[-]\d{3}[-]\d{4}$</var-value>
</var>
</field>
validation.error.phone.format={0} should be in the format XX-XXX-XXXX.
bank.errors.gciNumber=GCI Number
Why are we using <msg> in the below case. Why can't we use <arg0>.
Ans: Thats a good question. For
depends="required"
depends="validwhen"
we get a message "is required." at the end of the error message. But here we are having a custom message to be shown to the user.
Thats the reason we are using <msg> here
<field property="gtemReportingFlag" depends="validwhen">
<msg name="validwhen" key="request.errors.gtemReportingYes"/>
<var>
<var-name>test</var-name>
<var-value>(gtemReportingFlag == "Yes")</var-value>
</var>
</field>
Custom Currency Validation.
if(controlType.equals("CURRENCY")) {
try{
CurrencyValidator cv = new CurrencyValidator(true,true);
Object value = cv.validate(answer);
if(value == null) {
status2 = false;
ActionMessage em = new ActionMessage(requiredFormatMessage,false);
messages.add(field.getKey(),em);
}
else {
answer = ABCApplicationUtility.getUSFormat(((BigDecimal)value).longValue());
objQuestionnaireBean.setAnswer(answer);
}
}
catch(Exception ex) {
status2 = false;
ActionMessage em = new ActionMessage(requiredFormatMessage,false);
messages.add(field.getKey(),em);
}
}
import java.text.NumberFormat;
public static final NumberFormat usFormat = NumberFormat.getInstance(Locale.US);
public static String getUSFormat(Object obj) {
if(obj !=null ) {
return usFormat.format(obj);
}
return null;
}
Custom Date validation – Throwing error message.
Validation.xml
<field property="requestDetailsKey" depends="validateDates">
<arg0 key="request.errors.lcExpDate"/>
<arg1 key="request.errors.latestShipDate"/>
<var>
<var-name>defaultDate1</var-name>
<var-value>lcExpDateString</var-value>
</var>
<var>
<var-name>defaultDate2</var-name>
<var-value>latestShipDateString</var-value>
</var>
</field>
Custom Validation Class:
public static boolean validateDates (
Object form,
ValidatorAction va,
Field field,
ActionMessages messages,
Validator validator,
HttpServletRequest request) {
try {
String date1 = null;
String date2 = null;
Date utildate2=null;
Date utildate1=null;
if(field.getVarValue("defaultDate1")!=null && !(("").equals(field.getVarValue("defaultDate1")))){
date1 = field.getVarValue("defaultDate1");
utildate1= DateUtility.stringToUtilDate(ValidatorUtils.getValueAsString(form, date1));
}
if(field.getVarValue("defaultDate2")!=null && !(("").equals(field.getVarValue("defaultDate2")))){
date2 = field.getVarValue("defaultDate2");
utildate2= DateUtility.stringToUtilDate(ValidatorUtils.getValueAsString(form, date2));
}
if(utildate1.before(utildate2))
{
ActionMessage em = new ActionMessage("errors.compareDates",Resources.getMessage(request,field.getArg(0).getKey()),Resources.getMessage(request,field.getArg(1).getKey()));
messages.add(field.getKey(),em);
return false;
}
MessageResources.properties
errors.compareDates={0} can not be less than {1}
As you can see we have two inputs [ {0} and {1} ] which are to be replaced with values from ActionMessage class.
Resources.getMessage(request,field.getArg(0).getKey())
The above line will fetch request.errors.lcExpDate from validation.xml and will replace the [{0}] in the error message which will be shown to the user.
Excerpt from Validation.xml
<arg0 key="request.errors.lcExpDate"/>
<arg1 key="request.errors.latestShipDate"/>
3 Comments:
Great work!
I have been trying case 2, for validation, its not at all working.
does it require something else, please advise. thanks in advance.
Case - 2: This particular field (importerName) is mandatory (required) only if the field tbuandtxnCode’s value is EQUAL to 2
Needed to compose you a very little word to thank you yet again regarding the nice suggestions you’ve contributed here.
Best Java Training Institute Chennai
Post a Comment
<< Home