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