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