Monday, February 3, 2014

Doing entity validations at runtime

I was recently given a requirement to drive the entity attribute validations at runtime rather than at design time, with the following criteria.
  1. Ability to change fields to required at runtime.
  2. To be able to define validation message for field at runtime
  3. Define validations such as field must be numeric, or contain only alphabets (Used Regular expression for this)
  4. Localization of validation messages
To accomplish this we can use database dictionary views along with custom database tables to store the validations and perform validations at runtime. The data model diagram is shown below.
DEMO_RUNTIME_VALThe key thing then is to use database dictionary views along with these database tables to validate attributes of the entity for a pattern (regular expression).

The class that contains the method to perform validations is CustomEntityImpl and each entity that one wants to enable for runtime validation must extend this class and call its validateCustomEntity method in its entity method validator.

    protected Boolean validateCustomEntity(){

ResourceBundleDef rbd=getEntityDef().getResourceBundleDef();
PropertiesBundleDef pbd=null;
if(rbd==null){
pbd = new PropertiesBundleDef(this.getEntityDef());
pbd.setPropertiesFile("com.blogspot.ramannanda.demos.validationapp.model.ModelBundle");
rbd=pbd;
}
ArrayList<RowValException> exceptions=new ArrayList<RowValException>();
EntityImpl entObj= this;
String tableName=this.getEntityDef().getSource();
tableName=tableName.substring(tableName.lastIndexOf(".")+1,tableName.length());
AttributeDef[] attrDefs= this.getEntityDef().getAttributeDefs();

ValidationsVOImpl vo=(ValidationsVOImpl) this.getDBTransaction().getRootApplicationModule().findViewObject("ValidationsVO12");
ViewCriteriaManager vcm=vo.getViewCriteriaManager();
ViewCriteria vc=vcm.getViewCriteria("ValidationsVOCriteria");
vo.setbTableName(tableName);
vo.applyViewCriteria(vc, false);
vo.executeQuery();
RowSetIterator it=vo.createRowSetIterator(null);
ArrayList<AttrValException> attrValExceptions=new ArrayList<AttrValException>();
while(it.hasNext()){
ValidationsVORowImpl row=(ValidationsVORowImpl)it.next();
String pattern=row.getValidationPattern();
String fieldName=row.getAttribName();

for(int i=0;i<attrDefs.length;i++){
String attrName=attrDefs[i].getName();
String columnName=attrDefs[i].getColumnName();
if(columnName.equals(fieldName)){
String attribRequired=row.getAttribRequired();
Object attribValue=entObj.getAttribute(attrName);

ArrayList<JboException> list=new ArrayList<JboException>(2);
if(attribRequired.equalsIgnoreCase("Y"))
{
if(attribValue==null || ((String)attribValue).isEmpty()){
String errorMessageKey=row.getRequiredMsgKey();
if(errorMessageKey!=null&&!errorMessageKey.isEmpty()){
JboException exception=new JboException(getMessageForKey(errorMessageKey),null,new Object[]{attrName});
list.add(exception);
}
else{
AttrSetValException exception=
new AttrSetValException(AttrValException.TYP_DEF_ENTITY_OBJECT, rbd,
"DEFAULT_REQUIRED_ERROR", entObj.getStructureDef().getFullName(),
attrName,attribValue, null);
list.add(exception);
}

}
}
if(((attribValue!=null)&& !((String)attribValue).isEmpty())){
boolean result=validatePattern(pattern,(String)entObj.getAttribute(attrName));
if(!result){

String errorMessageKey=row.getValidationMsgKey();
if(errorMessageKey!=null&&!errorMessageKey.isEmpty()){
JboException exception=new JboException(getMessageForKey(errorMessageKey),null,new Object[]{attrName,attribValue});
list.add(exception);
list.add(exception);
}
else{
AttrSetValException exception=
new AttrSetValException(AttrValException.TYP_DEF_ENTITY_OBJECT, rbd,
"DEFAULT_INCORRECT_VAL_ERROR", entObj.getStructureDef().getFullName(),
attrName,attribValue, null);
list.add(exception);
}
}
}
if(list.size()>0){
AttrValException ave =
new AttrValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED, entObj.getStructureDef().getFullName(),
attrName,attribValue, list, false);
attrValExceptions.add(ave);
}


}
}

}
it.closeRowSetIterator();
if(attrValExceptions.size()>0){
RowValException rowValException= new RowValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED,
entObj.getStructureDef().getFullName(), entObj.getKey(), attrValExceptions);
exceptions.add(rowValException);
}



if(exceptions.size()>0){
throw new
RowValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED,
getStructureDef().getFullName(), getKey(), exceptions);
}
return true;

}


/**
* This method returns false or true depending upon whether the field is valid or not
* @param patternString
* @param value
*/
private boolean validatePattern(String patternString, String value) {
Pattern pattern =Pattern.compile(patternString);
Matcher matcher= pattern.matcher(value);
return matcher.matches();

}



private String getMessageForKey(String msgKey){
Locale defaultLocale=Locale.US;
String defaultMessage=null;
String localMessage=null;
Locale locale = this.getDBTransaction().getSession().
getLocale();
String localString=locale.toLanguageTag();
ValidationMessagesVOImpl vo=(ValidationMessagesVOImpl) this.getDBTransaction().getRootApplicationModule().findViewObject("ValidationMessagesVO12");
ViewCriteriaManager vcm=vo.getViewCriteriaManager();
ViewCriteria vc=vcm.getViewCriteria("ValidationMessagesVOCriteria1");
vo.setbMessageKey(msgKey);
vo.applyViewCriteria(vc, false);
vo.executeQuery();
RowSetIterator it=vo.createRowSetIterator(null);
while(it.hasNext()){
ValidationMessagesVORowImpl row=(ValidationMessagesVORowImpl) it.next();
if(row.getMessageLocale().equals(defaultLocale.toLanguageTag())){
defaultMessage=row.getMessageText();
}
if(row.getMessageLocale().equals(localString)){
localMessage=row.getMessageText();
break;
}
}
it.closeRowSetIterator();
if(localMessage==null){
return defaultMessage;
}
return localMessage;
}





In the sample application i have enabled a required and pattern constraint as shown in the following screenshots for dummy entity object’s attribute.





runtimevalidation_defineruntimevalidation_define_messagesruntimevalidation_define_1


runtimevalidationerror   





The application source and sql script can be downloaded from the below mentioned link.


Source Code





Note: If you want to test the application you must create tables under user “raman”.





Edit: To skip validation for existing attribute that has not been modified you can use the following snippet.


                                       Object attribValue=getAttribute(attrName);
int attribIndex=getAttributeIndexOf(attrName);
Object oldAttribValue=getPostedAttribute(attribIndex);
if(attribValue==null || !attribValue.equals(oldAttribValue)){
                                       ArrayList<JboException> list=new ArrayList<JboException>(2);
if(attribRequired.equalsIgnoreCase("Y"))
{
....................