View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the Educational Community License, Version 2.0 (the "License"); you may
3    * not use this file except in compliance with the License. You may obtain a copy of the License at
4    * http://www.osedu.org/licenses/ECL-2.0 Unless required by applicable law or agreed to in writing, software distributed
5    * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
6    * implied. See the License for the specific language governing permissions and limitations under the License.
7    */
8   
9   package org.kuali.student.common.validator;
10  
11  import java.lang.reflect.InvocationTargetException;
12  import java.util.ArrayList;
13  import java.util.Collection;
14  import java.util.Date;
15  import java.util.HashMap;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.Stack;
20  
21  import org.apache.commons.beanutils.PropertyUtils;
22  import org.apache.log4j.Logger;
23  import org.kuali.student.common.dictionary.dto.CaseConstraint;
24  import org.kuali.student.common.dictionary.dto.CommonLookupParam;
25  import org.kuali.student.common.dictionary.dto.Constraint;
26  import org.kuali.student.common.dictionary.dto.DataType;
27  import org.kuali.student.common.dictionary.dto.FieldDefinition;
28  import org.kuali.student.common.dictionary.dto.LookupConstraint;
29  import org.kuali.student.common.dictionary.dto.MustOccurConstraint;
30  import org.kuali.student.common.dictionary.dto.ObjectStructureDefinition;
31  import org.kuali.student.common.dictionary.dto.RequiredConstraint;
32  import org.kuali.student.common.dictionary.dto.ValidCharsConstraint;
33  import org.kuali.student.common.dictionary.dto.WhenConstraint;
34  import org.kuali.student.common.messages.dto.Message;
35  import org.kuali.student.common.messages.service.MessageService;
36  import org.kuali.student.common.search.dto.SearchParam;
37  import org.kuali.student.common.search.dto.SearchRequest;
38  import org.kuali.student.common.search.dto.SearchResult;
39  import org.kuali.student.common.search.service.SearchDispatcher;
40  import org.kuali.student.common.util.MessageUtils;
41  import org.kuali.student.common.validation.dto.ValidationResultInfo;
42  import org.kuali.student.common.validation.dto.ValidationResultInfo.ErrorLevel;
43  import org.springframework.beans.BeanUtils;
44  
45  public class DefaultValidatorImpl extends BaseAbstractValidator {
46      final static Logger LOG = Logger.getLogger(DefaultValidatorImpl.class);
47  
48      private MessageService messageService = null;
49  
50      private SearchDispatcher searchDispatcher;
51  
52      private String messageLocaleKey = "en";
53  
54      private String messageGroupKey = "validation";
55  
56      private DateParser dateParser = new ServerDateParser();
57  
58      private boolean serverSide = true;
59  
60      public MessageService getMessageService() {
61          return messageService;
62      }
63  
64      public void setMessageService(MessageService messageService) {
65          this.messageService = messageService;
66      }
67  
68      public String getMessageLocaleKey() {
69          return messageLocaleKey;
70      }
71  
72      public void setMessageLocaleKey(String messageLocaleKey) {
73          this.messageLocaleKey = messageLocaleKey;
74      }
75  
76      public String getMessageGroupKey() {
77          return messageGroupKey;
78      }
79  
80      public void setMessageGroupKey(String messageGroupKey) {
81          this.messageGroupKey = messageGroupKey;
82      }
83  
84      public void setDateParser(DateParser dateParser) {
85          this.dateParser = dateParser;
86      }
87  
88  	/**
89       * @return the serverSide
90       */
91      public boolean isServerSide() {
92          return serverSide;
93      }
94  
95      /**
96       * @param serverSide
97       *            the serverSide to set
98       */
99      public void setServerSide(boolean serverSide) {
100         this.serverSide = serverSide;
101     }
102 
103     /**
104      * @return the dateParser
105      */
106     public DateParser getDateParser() {
107         return dateParser;
108     }
109 
110     /**
111      * Validate Object and all its nested child objects for given type and state
112      *
113      * @param data
114      * @param objStructure
115      * @return
116      */
117     public List<ValidationResultInfo> validateObject(Object data, ObjectStructureDefinition objStructure) {
118 
119         List<ValidationResultInfo> results = new ArrayList<ValidationResultInfo>();
120     	Stack<String> elementStack = new Stack<String>();
121 
122        validateObject(results, data, objStructure, elementStack, data, objStructure, true);
123        
124        return results;
125     }
126 
127     private void validateObject(List<ValidationResultInfo> results, Object data, ObjectStructureDefinition objStructure, Stack<String> elementStack,  Object rootData, ObjectStructureDefinition rootObjStructure, boolean isRoot) {
128 
129         ConstraintDataProvider dataProvider = new BeanConstraintDataProvider();
130         dataProvider.initialize(data);
131 
132         // Push object structure to the top of the stack
133         StringBuilder objXPathElement = new StringBuilder(dataProvider.getPath());
134 
135         if(!isRoot && !objXPathElement.toString().isEmpty()){
136         	elementStack.push(objXPathElement.toString());
137         }
138 
139         /*
140          * Do nothing if the object to be validated is not type/state or if the objectstructure with constraints is not
141          * provided
142          */
143         if (null == objStructure) {
144             return;
145         }
146 
147         for (FieldDefinition f : objStructure.getAttributes()) {
148             validateField(results, f, objStructure, dataProvider, elementStack, rootData, rootObjStructure);
149 
150             // Use Custom Validators
151             if (f.getCustomValidatorClass() != null || f.isServerSide() && serverSide) {
152             	Validator customValidator = validatorFactory.getValidator(f.getCustomValidatorClass());
153             	if(customValidator==null){
154             		throw new RuntimeException("Custom Validator "+f.getCustomValidatorClass()+" was not configured in this context");
155             	}
156             	List<ValidationResultInfo> l = customValidator.validateObject(f,data, objStructure,elementStack);
157             	results.addAll(l);
158             }
159         }
160         if(!isRoot && !objXPathElement.toString().isEmpty()){
161         	elementStack.pop();
162         }
163 
164         /* All Field validations are returned right now */
165         // List<ValidationResultInfo> resultsBuffer = new
166         // ArrayList<ValidationResultInfo>();
167         // for (ValidationResultContainer vc : results) {
168         // if (skipFields.contains(vc.getElement()) == false) {
169         // resultsBuffer.add(vc);
170         // }
171         // }
172         // results = resultsBuffer;
173     }
174 
175     public void validateField(List<ValidationResultInfo> results, FieldDefinition field, ObjectStructureDefinition objStruct, ConstraintDataProvider dataProvider, Stack<String> elementStack,  Object rootData, ObjectStructureDefinition rootObjectStructure) {
176 
177         Object value = dataProvider.getValue(field.getName());
178 
179         // Handle null values in field
180         if (value == null || "".equals(value.toString().trim())) {
181             processConstraint(results, field, objStruct, value, dataProvider, elementStack, rootData, rootObjectStructure);
182             return; //no need to do further processing
183         }
184 
185         /*
186          * For complex object structures only the following constraints apply 1. TypeStateCase 2. MinOccurs 3. MaxOccurs
187          */
188         if (DataType.COMPLEX.equals(field.getDataType())) {
189             ObjectStructureDefinition nestedObjStruct = null;
190 
191             if (null != field.getDataObjectStructure()) {
192                 nestedObjStruct = field.getDataObjectStructure();
193             }
194 
195             elementStack.push(field.getName());
196 //           	beanPathStack.push(field.isDynamic()?"attributes("+field.getName()+")":field.getName());
197 
198             if (value instanceof Collection) {
199 
200                 String xPathForCollection = getElementXpath(elementStack) + "/*";
201 
202                 int i=0;
203                 for (Object o : (Collection<?>) value) {
204                 	elementStack.push(Integer.toString(i));
205 //                	beanPathStack.push(!beanPathStack.isEmpty()?beanPathStack.pop():""+"["+i+"]");
206                     processNestedObjectStructure(results, o, nestedObjStruct, field, elementStack, rootData, rootObjectStructure);
207 //                    beanPathStack.pop();
208 //                    beanPathStack.push(field.isDynamic()?"attributes("+field.getName()+")":field.getName());
209                     elementStack.pop();
210                     i++;
211                 }
212                 if (field.getMinOccurs() != null && field.getMinOccurs() > ((Collection<?>) value).size()) {
213                     ValidationResultInfo valRes = new ValidationResultInfo(xPathForCollection, value);
214                     valRes.setError(MessageUtils.interpolate(getMessage("validation.minOccurs"), toMap(field)));
215                     results.add(valRes);
216                 }
217 
218                 Integer maxOccurs = tryParse(field.getMaxOccurs());
219                 if (maxOccurs != null && maxOccurs < ((Collection<?>) value).size()) {
220                     ValidationResultInfo valRes = new ValidationResultInfo(xPathForCollection, value);
221                     valRes.setError(MessageUtils.interpolate(getMessage("validation.maxOccurs"), toMap(field)));
222                     results.add(valRes);
223                 }
224             } else {
225                 if (null != value) {
226                     processNestedObjectStructure(results, value, nestedObjStruct, field, elementStack, rootData, rootObjectStructure);
227                 } else {
228                     if (field.getMinOccurs() != null && field.getMinOccurs() > 0) {
229                         ValidationResultInfo val = new ValidationResultInfo(getElementXpath(elementStack), value);
230                         if(field.getLabelKey()!=null){
231                         	val.setError(getMessage(field.getLabelKey()));
232                         } else {
233                         	val.setError(getMessage("validation.required"));
234                         }
235                         results.add(val);
236                     }
237                 }
238             }
239             
240 //            beanPathStack.pop();
241             elementStack.pop();
242 
243         } else { // If non complex data type
244 
245             if (value instanceof Collection) {
246 
247                 if(((Collection<?>)value).isEmpty()){
248                     processConstraint(results, field, objStruct, "", dataProvider, elementStack, rootData, rootObjectStructure);
249                 }
250 
251             	int i = 0;
252                 for (Object o : (Collection<?>) value) {
253                 	//This is tricky, change the field name to the index in the elementStack(this is for lists of non complex types)
254                 	elementStack.push(field.getName());
255                 	FieldDefinition tempField = new FieldDefinition();
256                 	BeanUtils.copyProperties(field, tempField);
257                 	tempField.setName(Integer.toBinaryString(i));
258                 	processConstraint(results, tempField, objStruct, o, dataProvider, elementStack, rootData, rootObjectStructure);
259                 	elementStack.pop();
260                     i++;
261                 }
262 
263                 String xPath = getElementXpath(elementStack) + "/" + field.getName() + "/*";
264                 if (field.getMinOccurs() != null && field.getMinOccurs() > ((Collection<?>) value).size()) {
265                     ValidationResultInfo valRes = new ValidationResultInfo(xPath, value);
266                     valRes.setError(MessageUtils.interpolate(getMessage("validation.minOccurs"), toMap(field)));
267                     results.add(valRes);
268                 }
269 
270                 Integer maxOccurs = tryParse(field.getMaxOccurs());
271                 if (maxOccurs != null && maxOccurs < ((Collection<?>) value).size()) {
272                     ValidationResultInfo valRes = new ValidationResultInfo(xPath, value);
273                     valRes.setError(MessageUtils.interpolate(getMessage("validation.maxOccurs"), toMap(field)));
274                     results.add(valRes);
275                 }
276             } else {
277                 processConstraint(results, field, objStruct, value, dataProvider, elementStack, rootData, rootObjectStructure);
278             }
279 
280         }
281     }
282 
283     protected Integer tryParse(String s) {
284         Integer result = null;
285         if (s != null) {
286             try {
287                 result = Integer.valueOf(s);
288             } catch (NumberFormatException e) {
289                 // do nothing
290             }
291         }
292         return result;
293     }
294 
295     protected void processNestedObjectStructure(List<ValidationResultInfo> results, Object value, ObjectStructureDefinition nestedObjStruct, FieldDefinition field, Stack<String> elementStack,  Object rootData, ObjectStructureDefinition rootObjStructure) {
296         validateObject(results, value, nestedObjStruct, elementStack, rootData, rootObjStructure, false);
297     }
298 
299     protected void processConstraint(List<ValidationResultInfo> valResults, FieldDefinition field, ObjectStructureDefinition objStructure, Object value, ConstraintDataProvider dataProvider, Stack<String> elementStack,  Object rootData, ObjectStructureDefinition rootObjStructure) {
300 
301         // Process Case Constraint
302         // Case Constraint are only evaluated on the field. Nested case constraints are currently ignored
303         Constraint caseConstraint = processCaseConstraint(valResults, field.getCaseConstraint(), objStructure, value, dataProvider, elementStack, rootData, rootObjStructure);
304 
305         Constraint constraint = (null != caseConstraint) ? caseConstraint : field;
306 
307         processBaseConstraints(valResults, constraint, field, value, elementStack);
308         
309         // Stop other checks if value is null
310         if (value == null || "".equals(value.toString().trim())) {
311             return;
312         }
313 
314         String elementPath = getElementXpath(elementStack) + "/" + field.getName();
315 
316         // Process Valid Chars
317         if (null != constraint.getValidChars()) {
318             ValidationResultInfo val = processValidCharConstraint(elementPath, constraint.getValidChars(), dataProvider, value);
319             if (null != val) {
320                 valResults.add(val);
321             }
322         }
323 
324         // Process Require Constraints (only if this field has value)
325         if (value != null && !"".equals(value.toString().trim())) {
326             if (null != constraint.getRequireConstraint() && constraint.getRequireConstraint().size() > 0) {
327                 for (RequiredConstraint rc : constraint.getRequireConstraint()) {
328                     ValidationResultInfo val = processRequireConstraint(elementPath, rc, field, objStructure, dataProvider);
329                     if (null != val) {
330                         valResults.add(val);
331                         //FIXME: For clarity, might be better to handle this in the processRequireConstraint method instead.
332                         processCrossFieldWarning(valResults, rc, val.getErrorLevel(), field.getName());
333                     }
334                 }
335             }
336         }
337 
338         // Process Occurs Constraint
339         if (null != constraint.getOccursConstraint() && constraint.getOccursConstraint().size() > 0) {
340             for (MustOccurConstraint oc : constraint.getOccursConstraint()) {
341                 ValidationResultInfo val = processOccursConstraint(elementPath, oc, field, objStructure, dataProvider);
342                 if (null != val) {
343                     valResults.add(val); 
344                 }
345             }
346         }
347 
348         // Process lookup Constraint
349         if (null != constraint.getLookupDefinition()) {
350             processLookupConstraint(valResults, constraint.getLookupDefinition(), field, elementStack, dataProvider,objStructure, rootData, rootObjStructure, value);
351         }
352     }
353 
354     protected ValidationResultInfo processRequireConstraint(String element, RequiredConstraint constraint, FieldDefinition field, ObjectStructureDefinition objStructure, ConstraintDataProvider dataProvider) {
355 
356         ValidationResultInfo val = null;
357 
358         String fieldName = constraint.getFieldPath();// TODO parse fieldname from here
359         Object fieldValue = dataProvider.getValue(fieldName);
360 
361         boolean result = true;
362 
363         if (fieldValue instanceof java.lang.String) {
364             result = hasText((String) fieldValue);
365         } else if (fieldValue instanceof Collection) {
366             result = (((Collection<?>) fieldValue).size() > 0);
367         } else {
368             result = (null != fieldValue) ? true : false;
369         }
370 
371         if (!result) {
372             Map<String, Object> rMap = new HashMap<String, Object>();
373             rMap.put("field1", field.getName());
374             rMap.put("field2", fieldName);
375             val = new ValidationResultInfo(element, fieldValue);
376             val.setMessage(MessageUtils.interpolate(getMessage("validation.requiresField"), rMap));
377             val.setLevel(constraint.getErrorLevel());                       
378         }
379 
380         return val;
381     }
382 
383     /**
384      * Process caseConstraint tag and sets any of the base constraint items if any of the when condition matches
385      *
386      * @param caseConstraint
387      * @param caseConstraint
388      * @param field
389      */
390     protected Constraint processCaseConstraint(List<ValidationResultInfo> valResults, CaseConstraint caseConstraint, ObjectStructureDefinition objStructure, Object value, ConstraintDataProvider dataProvider, Stack<String> elementStack,  Object rootData, ObjectStructureDefinition rootObjStructure) {
391 
392         if (null == caseConstraint) {
393             return null;
394         }
395 
396         String operator = (hasText(caseConstraint.getOperator())) ? caseConstraint.getOperator() : "EQUALS";
397         FieldDefinition caseField = null;
398         boolean absolutePath = false;
399         if(hasText(caseConstraint.getFieldPath())){
400         	if(caseConstraint.getFieldPath().startsWith("/")){
401         		absolutePath = true;
402         		caseField = ValidatorUtils.getField(caseConstraint.getFieldPath().substring(1), rootObjStructure);
403         	}else{
404         		caseField = ValidatorUtils.getField(caseConstraint.getFieldPath(), objStructure); 
405         	}
406         }
407 
408         // TODO: What happens when the field is not in the dataProvider?
409         Object fieldValue = value;
410         if(caseField!=null){
411         	if(absolutePath){
412         		try {
413         			if(caseField.isDynamic()){
414         				//Pull the value from the dynamic attribute map
415         				//TODO There needs to be some mapping from PropertyUtils to the KS path
416         				//Until then, this will only work for root level properties
417         				Map<String,String> attributes = (Map<String,String>) PropertyUtils.getNestedProperty(rootData, "attributes");
418         				if(attributes!=null){
419         					fieldValue = attributes.get(caseConstraint.getFieldPath().substring(1));
420         				}
421         			}else{
422         				fieldValue = PropertyUtils.getNestedProperty(rootData, caseConstraint.getFieldPath().substring(1));
423         			}
424 				} catch (IllegalAccessException e) {
425 				} catch (InvocationTargetException e) {
426 				} catch (NoSuchMethodException e) {
427 				}
428         	}else{
429             	fieldValue = dataProvider.getValue(caseField.getName());
430         	}
431         }
432         DataType fieldDataType = (null != caseField ? caseField.getDataType():null);
433 
434         // If fieldValue is null then skip Case check
435         if(null == fieldValue) {
436             return null;
437         }
438 
439         // Extract value for field Key
440         for (WhenConstraint wc : caseConstraint.getWhenConstraint()) {
441 
442         	if(hasText(wc.getValuePath())){
443         		Object whenValue = null;
444         		if(wc.getValuePath().startsWith("/")){
445         			try {
446         				whenValue = PropertyUtils.getNestedProperty(rootData, wc.getValuePath().substring(1));
447 					} catch (IllegalAccessException e) {
448 					} catch (InvocationTargetException e) {
449 					} catch (NoSuchMethodException e) {
450 					}
451         		}else{
452         			whenValue = dataProvider.getValue(wc.getValuePath());
453         		}
454         		if (ValidatorUtils.compareValues(fieldValue, whenValue, fieldDataType, operator, caseConstraint.isCaseSensitive(), dateParser) && null != wc.getConstraint()) {                    
455         			Constraint constraint = wc.getConstraint();
456         			if (constraint.getCaseConstraint() != null){
457         				return processCaseConstraint(valResults, constraint.getCaseConstraint(), objStructure, value, dataProvider, elementStack, rootData, rootObjStructure);
458         			} else {
459         				processCrossFieldWarning(valResults, caseConstraint, constraint, value, constraint.getErrorLevel());
460         				return constraint;
461         			}
462                 }
463         	}else{
464         		List<Object> whenValueList = wc.getValues();
465             
466 	            for (Object whenValue : whenValueList) {
467 	                if (ValidatorUtils.compareValues(fieldValue, whenValue, fieldDataType, operator, caseConstraint.isCaseSensitive(), dateParser) && null != wc.getConstraint()) {
468 	        			Constraint constraint = wc.getConstraint();
469 	        			if (constraint.getCaseConstraint() != null){
470 	        				return processCaseConstraint(valResults, constraint.getCaseConstraint(), objStructure, value, dataProvider, elementStack, rootData, rootObjStructure);
471 	        			} else {
472 	        				processCrossFieldWarning(valResults, caseConstraint, constraint, value, constraint.getErrorLevel());	        				
473 	        				return constraint;
474 	        			}
475 	                }
476 	            }
477         	}
478         }
479 
480         return null;
481     }
482 
483     public ValidationResultInfo processValidCharConstraint(String element, ValidCharsConstraint vcConstraint, ConstraintDataProvider dataProvider, Object value) {
484 
485         ValidationResultInfo val = null;
486 
487         StringBuilder fieldValue = new StringBuilder();
488         String validChars = vcConstraint.getValue();
489 
490         fieldValue.append(ValidatorUtils.getString(value));
491 
492         int typIdx = validChars.indexOf(":");
493         String processorType = "regex";
494         if (-1 == typIdx) {
495             validChars = "[" + validChars + "]*";
496         } else {
497             processorType = validChars.substring(0, typIdx);
498             validChars = validChars.substring(typIdx + 1);
499         }
500 
501         if ("regex".equalsIgnoreCase(processorType)) {
502             if (fieldValue == null || !fieldValue.toString().matches(validChars)) {
503             	val = new ValidationResultInfo(element, fieldValue);
504                 if(vcConstraint.getLabelKey()!=null){
505                 	val.setError(getMessage(vcConstraint.getLabelKey()));
506                 }else{
507                 	val.setError(getMessage("validation.validCharsFailed"));
508                 }
509             }
510         }
511 
512         return val;
513     }
514 
515     /**
516      * Computes if all the filed required in the occurs clause are between the min and max
517      *
518      * @param valResults
519      * @param constraint
520      * @param field
521      * @param type
522      * @param state
523      * @param objStructure
524      * @param dataProvider
525      * @return
526      */
527     protected ValidationResultInfo processOccursConstraint(String element, MustOccurConstraint constraint, FieldDefinition field, ObjectStructureDefinition objStructure, ConstraintDataProvider dataProvider) {
528 
529         boolean result = false;
530         int trueCount = 0;
531 
532         ValidationResultInfo val = null;
533 
534         for (RequiredConstraint rc : constraint.getRequiredFields()) {
535             trueCount += (processRequireConstraint("", rc, field, objStructure, dataProvider) != null) ? 1 : 0;
536         }
537 
538         for (MustOccurConstraint oc : constraint.getOccurs()) {
539             trueCount += (processOccursConstraint("", oc, field, objStructure, dataProvider) != null) ? 1 : 0;
540         }
541 
542         result = (trueCount >= constraint.getMin() && trueCount <= constraint.getMax()) ? true : false;
543 
544         if (!result) {
545          // TODO: figure out what data should go here instead of null
546             val = new ValidationResultInfo(element, null);
547             val.setMessage(getMessage("validation.occurs"));
548             val.setLevel(constraint.getErrorLevel());
549         }
550 
551         return val;
552     }
553 
554     // TODO: Implement lookup constraint
555     protected void processLookupConstraint(List<ValidationResultInfo> valResults, LookupConstraint lookupConstraint, FieldDefinition field, Stack<String> elementStack, ConstraintDataProvider dataProvider, ObjectStructureDefinition objStructure, Object rootData, ObjectStructureDefinition rootObjStructure, Object value) {
556         if (lookupConstraint == null) {
557             return;
558         }
559 
560         // Create search params based on the param mapping
561         List<SearchParam> params = new ArrayList<SearchParam>();
562 
563         for (CommonLookupParam paramMapping : lookupConstraint.getParams()) {
564         	//Skip params that are the search param id key
565             if(lookupConstraint.getSearchParamIdKey()!=null&&lookupConstraint.getSearchParamIdKey().equals(paramMapping.getKey())){
566             	continue;
567             }
568         	
569             SearchParam param = new SearchParam();
570 
571             param.setKey(paramMapping.getKey());
572 
573             // If the value of the search param comes form another field then get it
574             if (paramMapping.getFieldPath() != null && !paramMapping.getFieldPath().isEmpty()) {
575                 FieldDefinition lookupField = null;
576             	boolean absolutePath = false;
577                 if(hasText(paramMapping.getFieldPath())){
578                 	if(paramMapping.getFieldPath().startsWith("/")){
579                 		absolutePath = true;
580                 		lookupField = ValidatorUtils.getField(paramMapping.getFieldPath().substring(1), rootObjStructure);
581                 	}else{
582                 		lookupField = ValidatorUtils.getField(paramMapping.getFieldPath(), objStructure); 
583                 	}
584                 }
585                 Object fieldValue = null;
586                 if(lookupField!=null){
587                 	if(absolutePath){
588                 		try {
589                 			if(lookupField.isDynamic()){
590                 				//Pull the value from the dynamic attribute map
591                 				//Until then, this will only work for root level properties
592                 				Map<String,String> attributes = (Map<String,String>) PropertyUtils.getNestedProperty(rootData, "attributes");
593                 				if(attributes!=null){
594                 					fieldValue = attributes.get(paramMapping.getFieldPath().substring(1));
595                 				}
596                 			}else{
597                 				fieldValue = PropertyUtils.getNestedProperty(rootData, paramMapping.getFieldPath().substring(1));
598                 			}
599         				} catch (IllegalAccessException e) {
600         				} catch (InvocationTargetException e) {
601         				} catch (NoSuchMethodException e) {
602         				}
603                 	}else{
604                     	fieldValue = dataProvider.getValue(lookupField.getName());
605                 	}
606                 }else{
607                 	fieldValue = dataProvider.getValue(paramMapping.getFieldPath());
608                 }
609                 
610                 if (fieldValue instanceof String) {
611                     param.setValue((String) fieldValue);
612                 } else if (fieldValue instanceof List<?>) {
613                     param.setValue((List<String>) fieldValue);
614                 }
615             } else if (paramMapping.getDefaultValueString() != null) {
616                 param.setValue(paramMapping.getDefaultValueString());
617             } else {
618                 param.setValue(paramMapping.getDefaultValueList());
619             }
620             params.add(param);
621         }
622 
623         if(lookupConstraint.getSearchParamIdKey()!=null){
624         	SearchParam param = new SearchParam();
625         	param.setKey(lookupConstraint.getSearchParamIdKey());
626             if (value instanceof String) {
627                 param.setValue((String) value);
628             } else if (value instanceof List<?>) {
629                 param.setValue((List<String>) value);
630             }
631         	params.add(param);
632         }
633         
634         SearchRequest searchRequest = new SearchRequest();
635         searchRequest.setMaxResults(1);
636         searchRequest.setStartAt(0);
637         searchRequest.setNeededTotalResults(false);
638         searchRequest.setSearchKey(lookupConstraint.getSearchTypeId());
639         searchRequest.setParams(params);
640 
641         SearchResult searchResult = null;
642         try {
643             searchResult = searchDispatcher.dispatchSearch(searchRequest);
644         } catch (Exception e) {
645             LOG.info("Error calling Search", e);
646         }
647         //If there are no search results then make a validation result
648         if (searchResult == null || searchResult.getRows() == null || searchResult.getRows().isEmpty()) {
649             ValidationResultInfo val = new ValidationResultInfo(getElementXpath(elementStack) + "/" + field.getName(), value);
650             val.setLevel(lookupConstraint.getErrorLevel());
651             val.setMessage(getMessage("validation.lookup"));
652             valResults.add(val);
653         	processCrossFieldWarning(valResults, lookupConstraint, lookupConstraint.getErrorLevel());
654         }
655     }
656 
657     protected void processBaseConstraints(List<ValidationResultInfo> valResults, Constraint constraint, FieldDefinition field, Object value, Stack<String> elementStack) {
658     	DataType dataType = field.getDataType();
659     	String name = field.getName();
660 
661     	if (value == null || "".equals(value.toString().trim())) {
662             if (constraint.getMinOccurs() != null && constraint.getMinOccurs() > 0) {
663                 ValidationResultInfo val = new ValidationResultInfo(getElementXpath(elementStack) + "/" + name, value);
664                 if(constraint.getLabelKey()!=null){
665                 	val.setError(getMessage(constraint.getLabelKey()));
666                 } else {
667                 	val.setMessage(getMessage("validation.required"));
668                 }
669                 val.setLevel(constraint.getErrorLevel());
670                 valResults.add(val);
671             }
672             return;
673         }
674 
675         String elementPath = getElementXpath(elementStack) + "/" + name;
676 
677         if (DataType.STRING.equals(dataType)) {
678             validateString(value, constraint, elementPath, valResults);
679         } else if (DataType.INTEGER.equals(dataType)) {
680             validateInteger(value, constraint, elementPath, valResults);
681         } else if (DataType.LONG.equals(dataType)) {
682             validateLong(value, constraint, elementPath, valResults);
683         } else if (DataType.DOUBLE.equals(dataType)) {
684             validateDouble(value, constraint, elementPath, valResults);
685         } else if (DataType.FLOAT.equals(dataType)) {
686             validateFloat(value, constraint, elementPath, valResults);
687         } else if (DataType.BOOLEAN.equals(dataType)) {
688             validateBoolean(value, constraint, elementPath, valResults);
689         } else if (DataType.DATE.equals(dataType)) {
690             validateDate(value, constraint, elementPath, valResults, dateParser);
691         }
692     }
693     
694     /**
695      * This adds a warning on the related field when a processed case-constraint results in a warning
696      * 
697      * @param valResults
698      * @param crossConstraint
699      * @param field
700      */
701     protected void processCrossFieldWarning(List<ValidationResultInfo> valResults, CaseConstraint crossConstraint, Constraint constraint, Object value, ErrorLevel errorLevel){
702     	if ((ErrorLevel.WARN == errorLevel || ErrorLevel.ERROR == errorLevel) && (value == null || "".equals(value.toString().trim()))) {
703             if (constraint.getMinOccurs() != null && constraint.getMinOccurs() > 0) {
704 
705 	            String crossFieldPath = crossConstraint.getFieldPath();
706 	            String crossFieldMessageId = crossConstraint.getFieldPathMessageId() == null ? 
707 	            		"validation.required":crossConstraint.getFieldPathMessageId();            
708 	            addCrossFieldWarning(valResults, crossFieldPath, getMessage(crossFieldMessageId), errorLevel);
709             }
710     	}
711     }
712 
713     /**
714      * This adds a warning on the related field when a processed case-constraint results in a warning
715      * 
716      * @param valResults
717      * @param requiredConstraint
718      * @param field
719      */
720     protected void processCrossFieldWarning(List<ValidationResultInfo> valResults, RequiredConstraint requiredConstraint, ErrorLevel errorLevel, String field){
721     	if ((ErrorLevel.WARN == errorLevel || ErrorLevel.ERROR == errorLevel) && requiredConstraint != null){
722             String crossFieldPath = requiredConstraint.getFieldPath();
723             String crossFieldMessageId = requiredConstraint.getFieldPathMessageId() == null ? 
724             		"validation.required":requiredConstraint.getFieldPathMessageId();
725             addCrossFieldWarning(valResults, crossFieldPath, getMessage(crossFieldMessageId), errorLevel);
726     	}
727     }
728 
729     /**
730      * This adds a warning on the related field when a processed lookup-constraint results in a warning
731      * 
732      * @param valResults
733      * @param lookupConstraint
734      */
735     protected void processCrossFieldWarning(List<ValidationResultInfo> valResults, LookupConstraint lookupConstraint, ErrorLevel errorLevel){
736     	if ((ErrorLevel.WARN == errorLevel || ErrorLevel.ERROR == errorLevel) && lookupConstraint != null){
737     		for(CommonLookupParam param:lookupConstraint.getParams()){
738     			if(param.getFieldPath()!=null && !param.getFieldPath().isEmpty()){
739 		            String crossFieldPath = param.getFieldPath();
740 		            String crossFieldMessageId = param.getFieldPathMessageId() == null ? 
741 		            		"validation.lookup.cause":param.getFieldPathMessageId();
742 		            addCrossFieldWarning(valResults, crossFieldPath, getMessage(crossFieldMessageId), errorLevel);
743     			}
744     		}
745     	}
746     }
747     
748     protected void addCrossFieldWarning(List<ValidationResultInfo> valResults, String crossFieldPath, String message, ErrorLevel errorLevel){
749     	//Check to see if the exact same validation message already exists on referenced field
750     	boolean warnAlreadyExists = false;
751     	for (ValidationResultInfo vr:valResults){
752     		if (vr.getElement().equals(crossFieldPath) && vr.getMessage().equals(message)){
753     			warnAlreadyExists = true;
754     		}
755     	}
756     	
757     	//Only add this warning, if it hasn't been already added
758     	if (!warnAlreadyExists){
759 	    	ValidationResultInfo val = new ValidationResultInfo(crossFieldPath, null);
760 			val.setMessage(message);
761 	        val.setLevel(errorLevel);
762 	        valResults.add(val);
763     	}
764     }
765     
766     protected void validateBoolean(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
767         if (!(value instanceof Boolean)) {
768             try {
769                 Boolean.valueOf(value.toString());
770             } catch (Exception e) {
771                 ValidationResultInfo val = new ValidationResultInfo(element, value);
772                 val.setError(getMessage("validation.mustBeBoolean"));
773                 results.add(val);
774             }
775         }
776     }
777 
778     protected void validateDouble(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
779         Double v = null;
780 
781         ValidationResultInfo val = new ValidationResultInfo(element, value);
782 
783         if (value instanceof Number) {
784             v = ((Number) value).doubleValue();
785         } else {
786             try {
787                 v = Double.valueOf(value.toString());
788             } catch (Exception e) {
789                 val.setError(getMessage("validation.mustBeDouble"));
790             }
791         }
792 
793         if (val.isOk()) {
794             Double maxValue = ValidatorUtils.getDouble(constraint.getInclusiveMax());
795             Double minValue = ValidatorUtils.getDouble(constraint.getExclusiveMin());
796 
797             if (maxValue != null && minValue != null) {
798                 // validate range
799                 if (v > maxValue || v < minValue) {
800                     val.setError(MessageUtils.interpolate(getMessage("validation.outOfRange"), toMap(constraint)));
801                 }
802             } else if (maxValue != null) {
803                 if (v > maxValue) {
804                     val.setError(MessageUtils.interpolate(getMessage("validation.maxValueFailed"), toMap(constraint)));
805                 }
806             } else if (minValue != null) {
807                 if (v < minValue) {
808                     val.setError(MessageUtils.interpolate(getMessage("validation.minValueFailed"), toMap(constraint)));
809                 }
810             }
811         }
812 
813         if (!val.isOk()) {
814             results.add(val);
815         }
816     }
817 
818     protected void validateFloat(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
819         Float v = null;
820 
821         ValidationResultInfo val = new ValidationResultInfo(element, value);
822         if (value instanceof Number) {
823             v = ((Number) value).floatValue();
824         } else {
825             try {
826                 v = Float.valueOf(value.toString());
827             } catch (Exception e) {
828                 val.setError(getMessage("validation.mustBeFloat"));
829             }
830         }
831 
832         if (val.isOk()) {
833             Float maxValue = ValidatorUtils.getFloat(constraint.getInclusiveMax());
834             Float minValue = ValidatorUtils.getFloat(constraint.getExclusiveMin());
835 
836             if (maxValue != null && minValue != null) {
837                 // validate range
838                 if (v > maxValue || v < minValue) {
839                     val.setError(MessageUtils.interpolate(getMessage("validation.outOfRange"), toMap(constraint)));
840                 }
841             } else if (maxValue != null) {
842                 if (v > maxValue) {
843                     val.setError(MessageUtils.interpolate(getMessage("validation.maxValueFailed"), toMap(constraint)));
844                 }
845             } else if (minValue != null) {
846                 if (v < minValue) {
847                     val.setError(MessageUtils.interpolate(getMessage("validation.minValueFailed"), toMap(constraint)));
848                 }
849             }
850         }
851 
852         if (!val.isOk()) {
853             results.add(val);
854         }
855     }
856 
857     protected void validateLong(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
858         Long v = null;
859 
860         ValidationResultInfo val = new ValidationResultInfo(element, value);
861         if (value instanceof Number) {
862             v = ((Number) value).longValue();
863         } else {
864             try {
865                 v = Long.valueOf(value.toString());
866             } catch (Exception e) {
867                 val.setError(getMessage("validation.mustBeLong"));
868             }
869         }
870 
871         if (val.isOk()) {
872             Long maxValue = ValidatorUtils.getLong(constraint.getInclusiveMax());
873             Long minValue = ValidatorUtils.getLong(constraint.getExclusiveMin());
874 
875             if (maxValue != null && minValue != null) {
876                 // validate range
877                 if (v > maxValue || v < minValue) {
878                     val.setError(MessageUtils.interpolate(getMessage("validation.outOfRange"), toMap(constraint)));
879                 }
880             } else if (maxValue != null) {
881                 if (v > maxValue) {
882                     val.setError(MessageUtils.interpolate(getMessage("validation.maxValueFailed"), toMap(constraint)));
883                 }
884             } else if (minValue != null) {
885                 if (v < minValue) {
886                     val.setError(MessageUtils.interpolate(getMessage("validation.minValueFailed"), toMap(constraint)));
887                 }
888             }
889         }
890 
891         if (!val.isOk()) {
892             results.add(val);
893         }
894 
895     }
896 
897     protected void validateInteger(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
898         Integer v = null;
899 
900         ValidationResultInfo val = new ValidationResultInfo(element, value);
901 
902         if (value instanceof Number) {
903             v = ((Number) value).intValue();
904         } else {
905             try {
906                 v = Integer.valueOf(value.toString());
907             } catch (Exception e) {
908                 val.setError(getMessage("validation.mustBeInteger"));
909             }
910         }
911 
912         if (val.isOk()) {
913             Integer maxValue = ValidatorUtils.getInteger(constraint.getInclusiveMax());
914             Integer minValue = ValidatorUtils.getInteger(constraint.getExclusiveMin());
915 
916             if (maxValue != null && minValue != null) {
917                 // validate range
918                 if (v > maxValue || v < minValue) {
919                     val.setError(MessageUtils.interpolate(getMessage("validation.outOfRange"), toMap(constraint)));
920                 }
921             } else if (maxValue != null) {
922                 if (v > maxValue) {
923                     val.setError(MessageUtils.interpolate(getMessage("validation.maxValueFailed"), toMap(constraint)));
924                 }
925             } else if (minValue != null) {
926                 if (v < minValue) {
927                     val.setError(MessageUtils.interpolate(getMessage("validation.minValueFailed"), toMap(constraint)));
928                 }
929             }
930         }
931 
932         if (!val.isOk()) {
933             results.add(val);
934         }
935     }
936 
937     protected void validateDate(Object value, Constraint constraint, String element, List<ValidationResultInfo> results, DateParser dateParser) {
938         ValidationResultInfo val = new ValidationResultInfo(element, value);
939 
940         Date v = null;
941 
942         if (value instanceof Date) {
943             v = (Date) value;
944         } else {
945             try {
946                 v = dateParser.parseDate(value.toString());
947             } catch (Exception e) {
948                 val.setError(getMessage("validation.mustBeDate"));
949             }
950         }
951 
952         if (val.isOk()) {
953             Date maxValue = ValidatorUtils.getDate(constraint.getInclusiveMax(), dateParser);
954             Date minValue = ValidatorUtils.getDate(constraint.getExclusiveMin(), dateParser);
955 
956             if (maxValue != null && minValue != null) {
957                 // validate range
958                 if (v.getTime() > maxValue.getTime() || v.getTime() < minValue.getTime()) {
959                     val.setError(MessageUtils.interpolate(getMessage("validation.outOfRange"), toMap(constraint)));
960                 }
961             } else if (maxValue != null) {
962                 if (v.getTime() > maxValue.getTime()) {
963                     val.setError(MessageUtils.interpolate(getMessage("validation.maxValueFailed"), toMap(constraint)));
964                 }
965             } else if (minValue != null) {
966                 if (v.getTime() < minValue.getTime()) {
967                     val.setError(MessageUtils.interpolate(getMessage("validation.minValueFailed"), toMap(constraint)));
968                 }
969             }
970         }
971 
972         if (!val.isOk()) {
973             results.add(val);
974         }
975     }
976 
977     protected void validateString(Object value, Constraint constraint, String element, List<ValidationResultInfo> results) {
978 
979         if (value == null) {
980             value = "";
981         }
982         String s = value.toString().trim();
983 
984         ValidationResultInfo val = new ValidationResultInfo(element, value);
985 
986         Integer maxLength = tryParse(constraint.getMaxLength());
987         if (maxLength != null && constraint.getMinLength() != null && constraint.getMinLength() > 0) {
988             if (s.length() > maxLength || s.length() < constraint.getMinLength()) {
989                 val.setError(MessageUtils.interpolate(getMessage("validation.lengthOutOfRange"), toMap(constraint)));
990             }
991         } else if (maxLength != null) {
992             if (s.length() > Integer.parseInt(constraint.getMaxLength())) {
993                 val.setError(MessageUtils.interpolate(getMessage("validation.maxLengthFailed"), toMap(constraint)));
994             }
995         } else if (constraint.getMinLength() != null && constraint.getMinLength() > 0) {
996             if (s.length() < constraint.getMinLength()) {
997                 val.setError(MessageUtils.interpolate(getMessage("validation.minLengthFailed"), toMap(constraint)));
998             }
999         }
1000 
1001         if (!val.isOk()) {
1002             results.add(val);
1003         }
1004     }
1005 
1006     protected String getMessage(String messageId) {
1007         if (null == messageService) {
1008             return messageId;
1009         }
1010 
1011         Message msg = messageService.getMessage(messageLocaleKey, messageGroupKey, messageId);
1012 
1013         return msg.getValue();
1014     }
1015 
1016     protected String getElementXpath(Stack<String> elementStack) {
1017         StringBuilder xPath = new StringBuilder();
1018         Iterator<String> itr = elementStack.iterator();
1019         while (itr.hasNext()) {
1020             xPath.append(itr.next());
1021             if(itr.hasNext()){
1022             	xPath.append("/");
1023             }
1024         }
1025 
1026         return xPath.toString();
1027     }
1028 
1029     /*
1030      * Homemade has text so we dont need outside libs.
1031      */
1032     protected boolean hasText(String string) {
1033 
1034         if (string == null || string.length() < 1) {
1035             return false;
1036         }
1037         int stringLength = string.length();
1038 
1039         for (int i = 0; i < stringLength; i++) {
1040             char currentChar = string.charAt(i);
1041             if (' ' != currentChar || '\t' != currentChar || '\n' != currentChar) {
1042                 return true;
1043             }
1044         }
1045 
1046         return false;
1047     }
1048 
1049     protected Map<String, Object> toMap(Constraint c) {
1050         Map<String, Object> result = new HashMap<String, Object>();
1051         result.put("minOccurs", c.getMinOccurs());
1052         result.put("maxOccurs", c.getMaxOccurs());
1053         result.put("minLength", c.getMinLength());
1054         result.put("maxLength", c.getMaxLength());
1055         result.put("minValue", c.getExclusiveMin());
1056         result.put("maxValue", c.getInclusiveMax());
1057         // result.put("dataType", c.getDataType());
1058 
1059         return result;
1060     }
1061 
1062     public SearchDispatcher getSearchDispatcher() {
1063         return searchDispatcher;
1064     }
1065 
1066     public void setSearchDispatcher(SearchDispatcher searchDispatcher) {
1067         this.searchDispatcher = searchDispatcher;
1068     }
1069 
1070 	@Override
1071 	public List<ValidationResultInfo> validateObject(FieldDefinition field,
1072 			Object o, ObjectStructureDefinition objStructure,Stack<String> elementStack) {
1073 		return null;
1074 	}
1075 }