View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.service.impl;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.ArrayUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.kuali.rice.core.api.mo.common.active.MutableInactivatable;
22  import org.kuali.rice.core.api.util.RiceKeyConstants;
23  import org.kuali.rice.krad.bo.BusinessObject;
24  import org.kuali.rice.krad.bo.PersistableBusinessObject;
25  import org.kuali.rice.krad.datadictionary.CollectionDefinition;
26  import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition;
27  import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
28  import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
29  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
30  import org.kuali.rice.krad.datadictionary.ReferenceDefinition;
31  import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
32  import org.kuali.rice.krad.datadictionary.state.StateMapping;
33  import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
34  import org.kuali.rice.krad.datadictionary.validation.DictionaryObjectAttributeValueReader;
35  import org.kuali.rice.krad.datadictionary.validation.ErrorLevel;
36  import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable;
37  import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
38  import org.kuali.rice.krad.datadictionary.validation.constraint.provider.ConstraintProvider;
39  import org.kuali.rice.krad.datadictionary.validation.processor.CollectionConstraintProcessor;
40  import org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor;
41  import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult;
42  import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
43  import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
44  import org.kuali.rice.krad.document.Document;
45  import org.kuali.rice.krad.document.TransactionalDocument;
46  import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
47  import org.kuali.rice.krad.service.BusinessObjectService;
48  import org.kuali.rice.krad.service.DataDictionaryService;
49  import org.kuali.rice.krad.service.DictionaryValidationService;
50  import org.kuali.rice.krad.service.DocumentDictionaryService;
51  import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
52  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
53  import org.kuali.rice.krad.service.PersistenceService;
54  import org.kuali.rice.krad.service.PersistenceStructureService;
55  import org.kuali.rice.krad.uif.UifConstants;
56  import org.kuali.rice.krad.uif.util.ConstraintStateUtils;
57  import org.kuali.rice.krad.util.ErrorMessage;
58  import org.kuali.rice.krad.util.GlobalVariables;
59  import org.kuali.rice.krad.util.MessageMap;
60  import org.kuali.rice.krad.util.ObjectUtils;
61  import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
62  
63  import java.beans.PropertyDescriptor;
64  import java.lang.reflect.InvocationTargetException;
65  import java.util.Collection;
66  import java.util.IdentityHashMap;
67  import java.util.Iterator;
68  import java.util.LinkedList;
69  import java.util.List;
70  import java.util.Map;
71  import java.util.Queue;
72  import java.util.Set;
73  
74  /**
75   * Validates Documents, Business Objects, and Attributes against the data dictionary. Including min, max lengths, and
76   * validating expressions. This is the default, Kuali delivered implementation.
77   *
78   * KULRICE - 3355 Modified to prevent infinite looping (to maxDepth) scenario when a parent references a child which
79   * references a parent
80   *
81   * @author Kuali Rice Team (rice.collab@kuali.org)
82   */
83  public class DictionaryValidationServiceImpl implements DictionaryValidationService {
84      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
85              DictionaryValidationServiceImpl.class);
86  
87      /**
88       * Constant defines a validation method for an attribute value.
89       * <p>Value is "validate"
90       */
91      public static final String VALIDATE_METHOD = "validate";
92  
93      protected DataDictionaryService dataDictionaryService;
94      protected BusinessObjectService businessObjectService;
95      protected PersistenceService persistenceService;
96      protected DocumentDictionaryService documentDictionaryService;
97      protected WorkflowAttributePropertyResolutionService workflowAttributePropertyResolutionService;
98      protected PersistenceStructureService persistenceStructureService;
99  
100     @SuppressWarnings("unchecked")
101     private List<CollectionConstraintProcessor> collectionConstraintProcessors;
102     @SuppressWarnings("unchecked")
103     private List<ConstraintProvider> constraintProviders;
104     @SuppressWarnings("unchecked")
105     private List<ConstraintProcessor> elementConstraintProcessors;
106 
107     /**
108      * creates a new IdentitySet.
109      *
110      * @return a new Set
111      */
112     protected final Set<BusinessObject> newIdentitySet() {
113         return java.util.Collections.newSetFromMap(new IdentityHashMap<BusinessObject, Boolean>());
114     }
115 
116     /**
117      * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object)
118      */
119     public DictionaryValidationResult validate(Object object) {
120         return validate(object, object.getClass().getName(), (String) null, true);
121     }
122 
123     /**
124      * @see org.kuali.rice.krad.service.DictionaryValidationService#validate(java.lang.Object, java.lang.String,
125      *      java.lang.String, boolean)
126      */
127     public DictionaryValidationResult validate(Object object, String entryName, String attributeName,
128             boolean doOptionalProcessing) {
129         StateMapping stateMapping = null;
130         String validationState = null;
131         DataDictionaryEntry entry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(entryName);
132         if (entry != null) {
133             stateMapping = entry.getStateMapping();
134             if (stateMapping != null) {
135                 validationState = stateMapping.getCurrentState(object);
136             }
137         }
138 
139         AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry);
140         attributeValueReader.setAttributeName(attributeName);
141         return validate(attributeValueReader, doOptionalProcessing, validationState, stateMapping);
142     }
143 
144     /**
145      * @see DictionaryValidationService#validateAgainstNextState(Object)
146      */
147     @Override
148     public DictionaryValidationResult validateAgainstNextState(Object object) {
149         String entryName = object.getClass().getName();
150         StateMapping stateMapping = null;
151         String validationState = null;
152         DataDictionaryEntry entry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(entryName);
153         if (entry != null) {
154             stateMapping = entry.getStateMapping();
155             if (stateMapping != null) {
156                 validationState = stateMapping.getNextState(object);
157             }
158         }
159         AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry);
160         return validate(attributeValueReader, true, validationState, stateMapping);
161     }
162 
163     /**
164      * @see DictionaryValidationService#validateAgainstState(Object, String)
165      */
166     @Override
167     public DictionaryValidationResult validateAgainstState(Object object, String validationState) {
168         String entryName = object.getClass().getName();
169         StateMapping stateMapping = null;
170         DataDictionaryEntry entry = getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(entryName);
171         if (entry != null) {
172             stateMapping = entry.getStateMapping();
173             if (stateMapping != null && StringUtils.isBlank(validationState)) {
174                 validationState = stateMapping.getCurrentState(object);
175             }
176         }
177 
178         AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry);
179         return validate(attributeValueReader, true, validationState, stateMapping);
180     }
181 
182     /**
183      * @see DictionaryValidationService#validate(Object, String, DataDictionaryEntry, boolean)
184      */
185     @Override
186     public DictionaryValidationResult validate(Object object, String entryName, DataDictionaryEntry entry,
187             boolean doOptionalProcessing) {
188         StateMapping stateMapping = null;
189         String validationState = null;
190         if (entry != null) {
191             stateMapping = entry.getStateMapping();
192             if (stateMapping != null) {
193                 validationState = stateMapping.getCurrentState(object);
194             }
195         }
196         AttributeValueReader attributeValueReader = new DictionaryObjectAttributeValueReader(object, entryName, entry);
197         return validate(attributeValueReader, doOptionalProcessing, validationState, stateMapping);
198     }
199 
200     /**
201      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocument(org.kuali.rice.krad.document.Document)
202      */
203     @Override
204     public void validateDocument(Document document) {
205         String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
206 
207         validate(document, documentEntryName, (String) null, true);
208     }
209 
210     /**
211      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAttribute(org.kuali.rice.krad.document.Document,
212      *      java.lang.String, java.lang.String)
213      */
214     @Override
215     public void validateDocumentAttribute(Document document, String attributeName, String errorPrefix) {
216         String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
217 
218         validate(document, documentEntryName, attributeName, true);
219     }
220 
221     /**
222      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document,
223      *      int, boolean)
224      */
225     @Override
226     public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth,
227             boolean validateRequired) {
228         validateDocumentAndUpdatableReferencesRecursively(document, maxDepth, validateRequired, false);
229     }
230 
231     /**
232      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDocumentAndUpdatableReferencesRecursively(org.kuali.rice.krad.document.Document,
233      *      int, boolean, boolean)
234      */
235     @Override
236     public void validateDocumentAndUpdatableReferencesRecursively(Document document, int maxDepth,
237             boolean validateRequired, boolean chompLastLetterSFromCollectionName) {
238         String documentEntryName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
239         validate(document, documentEntryName, (String) null, true);
240 
241         if (maxDepth > 0) {
242             validateUpdatabableReferencesRecursively(document, maxDepth - 1, validateRequired,
243                     chompLastLetterSFromCollectionName, newIdentitySet());
244         }
245     }
246 
247     protected void validateUpdatabableReferencesRecursively(BusinessObject businessObject, int maxDepth,
248             boolean validateRequired, boolean chompLastLetterSFromCollectionName, Set<BusinessObject> processedBOs) {
249         // if null or already processed, return
250         if (ObjectUtils.isNull(businessObject) || processedBOs.contains(businessObject)) {
251             return;
252         }
253         processedBOs.add(businessObject);  // add bo to list to prevent excessive looping
254         Map<String, Class> references = persistenceStructureService.listReferenceObjectFields(
255                 businessObject.getClass());
256         for (String referenceName : references.keySet()) {
257             if (persistenceStructureService.isReferenceUpdatable(businessObject.getClass(), referenceName)) {
258                 Object referenceObj = ObjectUtils.getPropertyValue(businessObject, referenceName);
259 
260                 if (ObjectUtils.isNull(referenceObj) || !(referenceObj instanceof PersistableBusinessObject)) {
261                     continue;
262                 }
263 
264                 BusinessObject referenceBusinessObject = (BusinessObject) referenceObj;
265                 GlobalVariables.getMessageMap().addToErrorPath(referenceName);
266                 validateBusinessObject(referenceBusinessObject, validateRequired);
267                 if (maxDepth > 0) {
268                     validateUpdatabableReferencesRecursively(referenceBusinessObject, maxDepth - 1, validateRequired,
269                             chompLastLetterSFromCollectionName, processedBOs);
270                 }
271                 GlobalVariables.getMessageMap().removeFromErrorPath(referenceName);
272             }
273         }
274         Map<String, Class> collections = persistenceStructureService.listCollectionObjectTypes(
275                 businessObject.getClass());
276         for (String collectionName : collections.keySet()) {
277             if (persistenceStructureService.isCollectionUpdatable(businessObject.getClass(), collectionName)) {
278                 Object listObj = ObjectUtils.getPropertyValue(businessObject, collectionName);
279 
280                 if (ObjectUtils.isNull(listObj)) {
281                     continue;
282                 }
283 
284                 if (!(listObj instanceof List)) {
285                     if (LOG.isInfoEnabled()) {
286                         LOG.info("The reference named " + collectionName + " of BO class " +
287                                 businessObject.getClass().getName() +
288                                 " should be of type java.util.List to be validated properly.");
289                     }
290                     continue;
291                 }
292 
293                 List list = (List) listObj;
294 
295                 //should we materialize the proxied collection or just skip validation here assuming an unmaterialized objects are valid?
296                 ObjectUtils.materializeObjects(list);
297 
298                 for (int i = 0; i < list.size(); i++) {
299                     final Object o = list.get(i);
300                     if (ObjectUtils.isNotNull(o) && o instanceof PersistableBusinessObject) {
301                         final BusinessObject element = (BusinessObject) o;
302 
303                         final String errorPathAddition;
304                         if (chompLastLetterSFromCollectionName) {
305                             errorPathAddition = StringUtils.chomp(collectionName, "s")
306                                     + "["
307                                     + Integer.toString(i)
308                                     + "]";
309                         } else {
310                             errorPathAddition = collectionName + "[" + Integer.toString(i) + "]";
311                         }
312 
313                         GlobalVariables.getMessageMap().addToErrorPath(errorPathAddition);
314                         validateBusinessObject(element, validateRequired);
315                         if (maxDepth > 0) {
316                             validateUpdatabableReferencesRecursively(element, maxDepth - 1, validateRequired,
317                                     chompLastLetterSFromCollectionName, processedBOs);
318                         }
319                         GlobalVariables.getMessageMap().removeFromErrorPath(errorPathAddition);
320                     }
321                 }
322             }
323         }
324     }
325 
326     /**
327      * @see org.kuali.rice.krad.service.DictionaryValidationService#isBusinessObjectValid(org.kuali.rice.krad.bo.BusinessObject)
328      */
329     public boolean isBusinessObjectValid(BusinessObject businessObject) {
330         return isBusinessObjectValid(businessObject, null);
331     }
332 
333     /**
334      * @see org.kuali.rice.krad.service.DictionaryValidationService#isBusinessObjectValid(org.kuali.rice.krad.bo.BusinessObject,
335      *      String)
336      */
337     public boolean isBusinessObjectValid(BusinessObject businessObject, String prefix) {
338         final MessageMap errorMap = GlobalVariables.getMessageMap();
339         int originalErrorCount = errorMap.getErrorCount();
340 
341         errorMap.addToErrorPath(prefix);
342         validateBusinessObject(businessObject);
343         errorMap.removeFromErrorPath(prefix);
344 
345         return errorMap.getErrorCount() == originalErrorCount;
346     }
347 
348     /**
349      * @param businessObject - business object to validate
350      */
351     public void validateBusinessObjectsRecursively(BusinessObject businessObject, int depth) {
352         if (ObjectUtils.isNull(businessObject)) {
353             return;
354         }
355 
356         // validate primitives and any specific bo validation
357         validateBusinessObject(businessObject);
358 
359         // call method to recursively find business objects and validate
360         validateBusinessObjectsFromDescriptors(businessObject, PropertyUtils.getPropertyDescriptors(
361                 businessObject.getClass()), depth);
362     }
363 
364     /**
365      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateBusinessObject(org.kuali.rice.krad.bo.BusinessObject)
366      */
367     @Override
368     public void validateBusinessObject(BusinessObject businessObject) {
369         validateBusinessObject(businessObject, true);
370     }
371 
372     /**
373      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateBusinessObject(org.kuali.rice.krad.bo.BusinessObject,
374      *      boolean)
375      */
376     @Override
377     public void validateBusinessObject(BusinessObject businessObject, boolean validateRequired) {
378         if (ObjectUtils.isNull(businessObject)) {
379             return;
380         }
381 
382         validate(businessObject, businessObject.getClass().getName(), (String) null, validateRequired);
383     }
384 
385     /**
386      * iterates through the property descriptors looking for business objects or lists of business objects. calls
387      * validate method
388      * for each bo found
389      *
390      * @param object
391      * @param propertyDescriptors
392      */
393     protected void validateBusinessObjectsFromDescriptors(Object object, PropertyDescriptor[] propertyDescriptors,
394             int depth) {
395         for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
396             // validate the properties that are descended from BusinessObject
397             if (propertyDescriptor.getPropertyType() != null &&
398                     PersistableBusinessObject.class.isAssignableFrom(propertyDescriptor.getPropertyType()) &&
399                     ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()) != null) {
400                 BusinessObject bo = (BusinessObject) ObjectUtils.getPropertyValue(object, propertyDescriptor.getName());
401                 if (depth == 0) {
402                     GlobalVariables.getMessageMap().addToErrorPath(propertyDescriptor.getName());
403                     validateBusinessObject(bo);
404                     GlobalVariables.getMessageMap().removeFromErrorPath(propertyDescriptor.getName());
405                 } else {
406                     validateBusinessObjectsRecursively(bo, depth - 1);
407                 }
408             }
409 
410             /*
411              * if property is a List, then walk the list and do the validation on each contained object that is a descendent of
412              * BusinessObject
413              */
414             else if (propertyDescriptor.getPropertyType() != null &&
415                     (List.class).isAssignableFrom(propertyDescriptor.getPropertyType()) &&
416                     ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()) != null) {
417                 List propertyList = (List) ObjectUtils.getPropertyValue(object, propertyDescriptor.getName());
418                 for (int j = 0; j < propertyList.size(); j++) {
419                     if (propertyList.get(j) != null && propertyList.get(j) instanceof PersistableBusinessObject) {
420                         if (depth == 0) {
421                             GlobalVariables.getMessageMap().addToErrorPath(StringUtils.chomp(
422                                     propertyDescriptor.getName(), "s") + "[" +
423                                     (new Integer(j)).toString() + "]");
424                             validateBusinessObject((BusinessObject) propertyList.get(j));
425                             GlobalVariables.getMessageMap().removeFromErrorPath(StringUtils.chomp(
426                                     propertyDescriptor.getName(), "s") + "[" +
427                                     (new Integer(j)).toString() + "]");
428                         } else {
429                             validateBusinessObjectsRecursively((BusinessObject) propertyList.get(j), depth - 1);
430                         }
431                     }
432                 }
433             }
434         }
435     }
436 
437     /**
438      * calls validate format and required check for the given propertyDescriptor
439      *
440      * @param entryName
441      * @param object
442      * @param propertyDescriptor
443      * @param errorPrefix
444      * @deprecated since 1.1
445      */
446     @Deprecated
447     public void validatePrimitiveFromDescriptor(String entryName, Object object, PropertyDescriptor propertyDescriptor,
448             String errorPrefix, boolean validateRequired) {
449 
450         // validate the primitive attributes if defined in the dictionary
451         if (null != propertyDescriptor) {
452             validate(object, entryName, propertyDescriptor.getName(), validateRequired);
453         }
454     }
455 
456     /**
457      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExists(org.kuali.rice.krad.bo.BusinessObject,
458      *      org.kuali.rice.krad.datadictionary.ReferenceDefinition)
459      */
460     public boolean validateReferenceExists(BusinessObject bo, ReferenceDefinition reference) {
461         return validateReferenceExists(bo, reference.getAttributeName());
462     }
463 
464     /**
465      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExists(org.kuali.rice.krad.bo.BusinessObject,
466      *      java.lang.String)
467      */
468     public boolean validateReferenceExists(BusinessObject bo, String referenceName) {
469 
470         // attempt to retrieve the specified object from the db
471         BusinessObject referenceBo = businessObjectService.getReferenceIfExists(bo, referenceName);
472 
473         // if it isn't there, then it doesn't exist, return false
474         if (ObjectUtils.isNotNull(referenceBo)) {
475             return true;
476         }
477 
478         // otherwise, it is there, return true
479         return false;
480     }
481 
482     /**
483      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceIsActive(org.kuali.rice.krad.bo.BusinessObject,
484      *      org.kuali.rice.krad.datadictionary.ReferenceDefinition)
485      */
486     public boolean validateReferenceIsActive(BusinessObject bo, ReferenceDefinition reference) {
487         return validateReferenceIsActive(bo, reference.getAttributeName());
488     }
489 
490     /**
491      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceIsActive(org.kuali.rice.krad.bo.BusinessObject,
492      *      String)
493      */
494     public boolean validateReferenceIsActive(BusinessObject bo, String referenceName) {
495 
496         // attempt to retrieve the specified object from the db
497         BusinessObject referenceBo = businessObjectService.getReferenceIfExists(bo, referenceName);
498         if (referenceBo == null) {
499             return false;
500         }
501         if (!(referenceBo instanceof MutableInactivatable) || ((MutableInactivatable) referenceBo).isActive()) {
502             return true;
503         }
504 
505         return false;
506     }
507 
508     /**
509      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExistsAndIsActive(org.kuali.rice.krad.bo.BusinessObject,
510      *      org.kuali.rice.krad.datadictionary.ReferenceDefinition)
511      */
512     public boolean validateReferenceExistsAndIsActive(BusinessObject bo, ReferenceDefinition reference) {
513         boolean success = true;
514         // intelligently use the fieldname from the reference, or get it out
515         // of the dataDictionaryService
516         String displayFieldName;
517         if (reference.isDisplayFieldNameSet()) {
518             displayFieldName = reference.getDisplayFieldName();
519         } else {
520             Class<?> boClass =
521                     reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() : bo.getClass();
522             displayFieldName = dataDictionaryService.getAttributeLabel(boClass,
523                     reference.getAttributeToHighlightOnFail());
524         }
525 
526         if (reference.isCollectionReference()) {
527             success = validateCollectionReferenceExistsAndIsActive(bo, reference, displayFieldName, StringUtils.split(
528                     reference.getCollection(), "."), null);
529         } else {
530             success = validateReferenceExistsAndIsActive(bo, reference.getAttributeName(),
531                     reference.getAttributeToHighlightOnFail(), displayFieldName);
532         }
533         return success;
534     }
535 
536     /**
537      * @param bo the object to get the collection from
538      * @param reference the <code>ReferenceDefinition</code> of the collection to validate
539      * @param displayFieldName the name of the field
540      * @param intermediateCollections array containing the path to the collection as tokens
541      * @param pathToAttributeI the rebuilt path to the ReferenceDefinition.attributeToHighlightOnFail which includes
542      * the
543      * index of
544      * each subcollection
545      * @return
546      */
547     private boolean validateCollectionReferenceExistsAndIsActive(BusinessObject bo, ReferenceDefinition reference,
548             String displayFieldName, String[] intermediateCollections, String pathToAttributeI) {
549         boolean success = true;
550         Collection<PersistableBusinessObject> referenceCollection;
551         String collectionName = intermediateCollections[0];
552         // remove current collection from intermediates
553         intermediateCollections = (String[]) ArrayUtils.removeElement(intermediateCollections, collectionName);
554         try {
555             referenceCollection = (Collection) PropertyUtils.getProperty(bo, collectionName);
556         } catch (Exception e) {
557             throw new RuntimeException(e);
558         }
559         int pos = 0;
560         Iterator<PersistableBusinessObject> iterator = referenceCollection.iterator();
561         while (iterator.hasNext()) {
562             String pathToAttribute = StringUtils.defaultString(pathToAttributeI)
563                     + collectionName
564                     + "["
565                     + (pos++)
566                     + "].";
567             // keep drilling down until we reach the nested collection we want
568             if (intermediateCollections.length > 0) {
569                 success &= validateCollectionReferenceExistsAndIsActive(iterator.next(), reference, displayFieldName,
570                         intermediateCollections, pathToAttribute);
571             } else {
572                 String attributeToHighlightOnFail = pathToAttribute + reference.getAttributeToHighlightOnFail();
573                 success &= validateReferenceExistsAndIsActive(iterator.next(), reference.getAttributeName(),
574                         attributeToHighlightOnFail, displayFieldName);
575             }
576         }
577 
578         return success;
579     }
580 
581     /**
582      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateReferenceExistsAndIsActive(org.kuali.rice.krad.bo.BusinessObject,
583      *      String, String, String)
584      */
585     public boolean validateReferenceExistsAndIsActive(BusinessObject bo, String referenceName,
586             String attributeToHighlightOnFail, String displayFieldName) {
587 
588         // if we're dealing with a nested attribute, we need to resolve down to the BO where the primitive attribute is located
589         // this is primarily to deal with the case of a defaultExistenceCheck that uses an "extension", i.e referenceName
590         // would be extension.attributeName
591         if (ObjectUtils.isNestedAttribute(referenceName)) {
592             String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(referenceName);
593             String nestedAttributePrimitive = ObjectUtils.getNestedAttributePrimitive(referenceName);
594             Object nestedObject = ObjectUtils.getPropertyValue(bo, nestedAttributePrefix);
595             if (!(nestedObject instanceof BusinessObject)) {
596                 throw new ObjectNotABusinessObjectRuntimeException(
597                         "Attribute requested (" + nestedAttributePrefix + ") is of class: " + "'" +
598                                 nestedObject.getClass().getName() + "' and is not a " +
599                                 "descendent of BusinessObject.");
600             }
601             return validateReferenceExistsAndIsActive((BusinessObject) nestedObject, nestedAttributePrimitive,
602                     attributeToHighlightOnFail, displayFieldName);
603         }
604 
605         boolean success = true;
606         boolean exists;
607         boolean active;
608 
609         boolean fkFieldsPopulated = true;
610         // need to check for DD relationship FKs
611         List<String> fkFields = getDataDictionaryService().getRelationshipSourceAttributes(bo.getClass().getName(),
612                 referenceName);
613         if (fkFields != null) {
614             for (String fkFieldName : fkFields) {
615                 Object fkFieldValue = null;
616                 try {
617                     fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName);
618                 }
619                 // if we cant retrieve the field value, then
620                 // it doesnt have a value
621                 catch (IllegalAccessException e) {
622                     fkFieldsPopulated = false;
623                 } catch (InvocationTargetException e) {
624                     fkFieldsPopulated = false;
625                 } catch (NoSuchMethodException e) {
626                     fkFieldsPopulated = false;
627                 }
628 
629                 // test the value
630                 if (fkFieldValue == null) {
631                     fkFieldsPopulated = false;
632                 } else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
633                     if (StringUtils.isBlank((String) fkFieldValue)) {
634                         fkFieldsPopulated = false;
635                     }
636                 }
637             }
638         } else if (bo instanceof PersistableBusinessObject) { // if no DD relationship exists, check the persistence service
639             fkFieldsPopulated = persistenceService.allForeignKeyValuesPopulatedForReference(
640                     (PersistableBusinessObject) bo, referenceName);
641         }
642 
643         // only bother if all the fk fields have values
644         if (fkFieldsPopulated) {
645 
646             // do the existence test
647             exists = validateReferenceExists(bo, referenceName);
648             if (exists) {
649 
650                 // do the active test, if appropriate
651                 if (!(bo instanceof MutableInactivatable) || ((MutableInactivatable) bo).isActive()) {
652                     active = validateReferenceIsActive(bo, referenceName);
653                     if (!active) {
654                         GlobalVariables.getMessageMap().putError(attributeToHighlightOnFail,
655                                 RiceKeyConstants.ERROR_INACTIVE, displayFieldName);
656                         success &= false;
657                     }
658                 }
659             } else {
660                 GlobalVariables.getMessageMap().putError(attributeToHighlightOnFail, RiceKeyConstants.ERROR_EXISTENCE,
661                         displayFieldName);
662                 success &= false;
663             }
664         }
665         return success;
666     }
667 
668     /**
669      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecks(org.kuali.rice.krad.bo.BusinessObject)
670      */
671     public boolean validateDefaultExistenceChecks(BusinessObject bo) {
672         boolean success = true;
673 
674         // get a collection of all the referenceDefinitions setup for this object
675         Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(bo.getClass());
676 
677         // walk through the references, doing the tests on each
678         for (Iterator iter = references.iterator(); iter.hasNext(); ) {
679             ReferenceDefinition reference = (ReferenceDefinition) iter.next();
680 
681             // do the existence and validation testing
682             success &= validateReferenceExistsAndIsActive(bo, reference);
683         }
684         return success;
685     }
686 
687     /**
688      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForNewCollectionItem(org.kuali.rice.krad.bo.BusinessObject,
689      *      org.kuali.rice.krad.bo.BusinessObject, java.lang.String)
690      */
691     public boolean validateDefaultExistenceChecksForNewCollectionItem(BusinessObject bo,
692             BusinessObject newCollectionItem, String collectionName) {
693         boolean success = true;
694 
695         if (StringUtils.isNotBlank(collectionName)) {
696             // get a collection of all the referenceDefinitions setup for this object
697             Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(bo.getClass());
698 
699             // walk through the references, doing the tests on each
700             for (Iterator iter = references.iterator(); iter.hasNext(); ) {
701                 ReferenceDefinition reference = (ReferenceDefinition) iter.next();
702                 if (collectionName != null && collectionName.equals(reference.getCollection())) {
703                     String displayFieldName;
704                     if (reference.isDisplayFieldNameSet()) {
705                         displayFieldName = reference.getDisplayFieldName();
706                     } else {
707                         Class boClass =
708                                 reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() :
709                                         bo.getClass();
710                         displayFieldName = dataDictionaryService.getAttributeLabel(boClass,
711                                 reference.getAttributeToHighlightOnFail());
712                     }
713 
714                     success &= validateReferenceExistsAndIsActive(newCollectionItem, reference.getAttributeName(),
715                             reference.getAttributeToHighlightOnFail(), displayFieldName);
716                 }
717             }
718         }
719 
720         return success;
721     }
722 
723     /**
724      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForTransDoc(org.kuali.rice.krad.document.TransactionalDocument)
725      */
726     public boolean validateDefaultExistenceChecksForTransDoc(TransactionalDocument document) {
727         boolean success = true;
728 
729         // get a collection of all the referenceDefinitions setup for this object
730         Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(document);
731 
732         // walk through the references, doing the tests on each
733         for (Iterator iter = references.iterator(); iter.hasNext(); ) {
734             ReferenceDefinition reference = (ReferenceDefinition) iter.next();
735 
736             // do the existence and validation testing
737             success &= validateReferenceExistsAndIsActive(document, reference);
738         }
739         return success;
740     }
741 
742     /**
743      * @see org.kuali.rice.krad.service.DictionaryValidationService#validateDefaultExistenceChecksForNewCollectionItem(org.kuali.rice.krad.document.TransactionalDocument,
744      *      org.kuali.rice.krad.bo.BusinessObject, String)
745      */
746     public boolean validateDefaultExistenceChecksForNewCollectionItem(TransactionalDocument document,
747             BusinessObject newCollectionItem, String collectionName) {
748         boolean success = true;
749         if (StringUtils.isNotBlank(collectionName)) {
750             // get a collection of all the referenceDefinitions setup for this object
751             Collection references = getDocumentDictionaryService().getDefaultExistenceChecks(document);
752 
753             // walk through the references, doing the tests on each
754             for (Iterator iter = references.iterator(); iter.hasNext(); ) {
755                 ReferenceDefinition reference = (ReferenceDefinition) iter.next();
756                 if (collectionName != null && collectionName.equals(reference.getCollection())) {
757                     String displayFieldName;
758                     if (reference.isDisplayFieldNameSet()) {
759                         displayFieldName = reference.getDisplayFieldName();
760                     } else {
761                         Class boClass =
762                                 reference.isCollectionReference() ? reference.getCollectionBusinessObjectClass() :
763                                         document.getClass();
764                         displayFieldName = dataDictionaryService.getAttributeLabel(boClass,
765                                 reference.getAttributeToHighlightOnFail());
766                     }
767 
768                     success &= validateReferenceExistsAndIsActive(newCollectionItem, reference.getAttributeName(),
769                             reference.getAttributeToHighlightOnFail(), displayFieldName);
770                 }
771             }
772         }
773         return success;
774     }
775 
776     /*
777     * 1.1 validation methods
778     */
779 
780     /**
781      * Validates using the defined AttributeValueReader (which allows access the object being validated) against
782      * the validationState and stateMapping (if specified).
783      *
784      * <p>If state information is null, validates the constraints as stateless (ie all constraints apply regardless of
785      * their states attribute).</p>
786      *
787      * @param valueReader - an object to validate
788      * @param doOptionalProcessing true if the validation should do optional validation (e.g. to check if empty values
789      * are required or not), false otherwise
790      * @param validationState
791      * @param stateMapping
792      * @return
793      */
794     public DictionaryValidationResult validate(AttributeValueReader valueReader, boolean doOptionalProcessing,
795             String validationState, StateMapping stateMapping) {
796         DictionaryValidationResult result = new DictionaryValidationResult();
797 
798         if (valueReader.getAttributeName() == null) {
799             validateObject(result, valueReader, doOptionalProcessing, true, validationState, stateMapping);
800         } else {
801             validateAttribute(result, valueReader, doOptionalProcessing, validationState, stateMapping);
802         }
803 
804         if (result.getNumberOfErrors() > 0) {
805 
806             String[] prefixParams = new String[1];
807             String prefixMessageKey = UifConstants.Messages.STATE_PREFIX;
808             if (stateMapping != null) {
809                 prefixParams[0] = stateMapping.getStateNameMessage(validationState);
810             }
811 
812             if (StringUtils.isBlank(prefixParams[0])) {
813                 prefixMessageKey = null;
814             }
815 
816             for (Iterator<ConstraintValidationResult> iterator = result.iterator(); iterator.hasNext(); ) {
817                 ConstraintValidationResult constraintValidationResult = iterator.next();
818                 if (constraintValidationResult.getStatus().getLevel() >= ErrorLevel.WARN.getLevel()) {
819                     String attributePath = constraintValidationResult.getAttributePath();
820                     if (attributePath == null || attributePath.isEmpty()) {
821                         attributePath = constraintValidationResult.getAttributeName();
822                     }
823 
824                     if (constraintValidationResult.getConstraintLabelKey() != null) {
825                         ErrorMessage errorMessage = new ErrorMessage(constraintValidationResult.getConstraintLabelKey(),
826                                 constraintValidationResult.getErrorParameters());
827                         errorMessage.setMessagePrefixKey(prefixMessageKey);
828                         errorMessage.setMessagePrefixParameters(prefixParams);
829                         GlobalVariables.getMessageMap().putError(attributePath, errorMessage);
830                     } else {
831                         ErrorMessage errorMessage = new ErrorMessage(constraintValidationResult.getErrorKey(),
832                                 constraintValidationResult.getErrorParameters());
833                         errorMessage.setMessagePrefixKey(prefixMessageKey);
834                         errorMessage.setMessagePrefixParameters(prefixParams);
835                         GlobalVariables.getMessageMap().putError(attributePath, errorMessage);
836                     }
837                 }
838             }
839         }
840 
841         return result;
842     }
843 
844     /**
845      * process constraints for the provided value using the element constraint processors
846      *
847      * @param result - used to store the validation results
848      * @param value - the object on which constraints are to be processed - the value of a complex attribute
849      * @param definition - a Data Dictionary definition e.g. {@code ComplexAttributeDefinition}
850      * @param attributeValueReader - a class that encapsulate access to both dictionary metadata and object field
851      * values
852      * @param doOptionalProcessing - true if the validation should do optional validation, false otherwise
853      */
854     protected void processElementConstraints(DictionaryValidationResult result, Object value, Constrainable definition,
855             AttributeValueReader attributeValueReader, boolean doOptionalProcessing, String validationState,
856             StateMapping stateMapping) {
857         processConstraints(result, elementConstraintProcessors, value, definition, attributeValueReader,
858                 doOptionalProcessing, validationState, stateMapping);
859     }
860 
861     /**
862      * process constraints for the provided collection using the collection constraint processors
863      *
864      * @param result - used to store the validation results
865      * @param collection - the object on which constraints are to be processed - a collection
866      * @param definition - a Data Dictionary definition e.g. {@code CollectionDefinition}
867      * @param attributeValueReader - a class that encapsulate access to both dictionary metadata and object field
868      * values
869      * @param doOptionalProcessing - true if the validation should do optional validation, false otherwise
870      */
871     protected void processCollectionConstraints(DictionaryValidationResult result, Collection<?> collection,
872             Constrainable definition, AttributeValueReader attributeValueReader, boolean doOptionalProcessing,
873             String validationState, StateMapping stateMapping) {
874         processConstraints(result, collectionConstraintProcessors, collection, definition, attributeValueReader,
875                 doOptionalProcessing, validationState, stateMapping);
876     }
877 
878     /**
879      * process constraints for the provided value using the provided constraint processors
880      *
881      * @param result - used to store the validation results
882      * @param value - the object on which constraints are to be processed - a collection or the value of an attribute
883      * @param definition - a Data Dictionary definition e.g. {@code ComplexAttributeDefinition} or {@code
884      * CollectionDefinition}
885      * @param attributeValueReader - a class that encapsulate access to both dictionary metadata and object field
886      * values
887      * @param doOptionalProcessing - true if the validation should do optional validation, false otherwise
888      */
889     @SuppressWarnings("unchecked")
890     private void processConstraints(DictionaryValidationResult result,
891             List<? extends ConstraintProcessor> constraintProcessors, Object value, Constrainable definition,
892             AttributeValueReader attributeValueReader, boolean doOptionalProcessing, String validationState,
893             StateMapping stateMapping) {
894         //TODO: Implement custom validators
895 
896         if (constraintProcessors != null) {
897             Constrainable selectedDefinition = definition;
898             AttributeValueReader selectedAttributeValueReader = attributeValueReader;
899 
900             // First - take the constrainable definition and get its constraints
901 
902             Queue<Constraint> constraintQueue = new LinkedList<Constraint>();
903 
904             // Using a for loop to iterate through constraint processors because ordering is important
905             for (ConstraintProcessor<Object, Constraint> processor : constraintProcessors) {
906 
907                 // Let the calling method opt out of any optional processing
908                 if (!doOptionalProcessing && processor.isOptional()) {
909                     result.addSkipped(attributeValueReader, processor.getName());
910                     continue;
911                 }
912 
913                 Class<? extends Constraint> constraintType = processor.getConstraintType();
914 
915                 // Add all of the constraints for this constraint type for all providers to the queue
916                 for (ConstraintProvider constraintProvider : constraintProviders) {
917                     if (constraintProvider.isSupported(selectedDefinition)) {
918                         Collection<Constraint> constraintList = constraintProvider.getConstraints(selectedDefinition,
919                                 constraintType);
920                         if (constraintList != null) {
921                             constraintQueue.addAll(constraintList);
922                         }
923                     }
924                 }
925 
926                 // If there are no constraints provided for this definition, then just skip it
927                 if (constraintQueue.isEmpty()) {
928                     result.addSkipped(attributeValueReader, processor.getName());
929                     continue;
930                 }
931 
932                 Collection<Constraint> additionalConstraints = new LinkedList<Constraint>();
933 
934                 // This loop is functionally identical to a for loop, but it has the advantage of letting us keep the queue around
935                 // and populate it with any new constraints contributed by the processor
936                 while (!constraintQueue.isEmpty()) {
937 
938                     Constraint constraint = constraintQueue.poll();
939 
940                     // If this constraint is not one that this process handles, then skip and add to the queue for the next processor;
941                     // obviously this would be redundant (we're only looking at constraints that this processor can process) except that
942                     // the previous processor might have stuck a new constraint (or constraints) on the queue
943                     if (!constraintType.isInstance(constraint)) {
944                         result.addSkipped(attributeValueReader, processor.getName());
945                         additionalConstraints.add(constraint);
946                         continue;
947                     }
948 
949                     constraint = ConstraintStateUtils.getApplicableConstraint(constraint, validationState,
950                             stateMapping);
951 
952                     if (constraint != null) {
953                         ProcessorResult processorResult = processor.process(result, value, constraint,
954                                 selectedAttributeValueReader);
955 
956                         Collection<Constraint> processorResultContraints = processorResult.getConstraints();
957                         if (processorResultContraints != null && processorResultContraints.size() > 0) {
958                             constraintQueue.addAll(processorResultContraints);
959                         }
960 
961                         // Change the selected definition to whatever was returned from the processor
962                         if (processorResult.isDefinitionProvided()) {
963                             selectedDefinition = processorResult.getDefinition();
964                         }
965                         // Change the selected attribute value reader to whatever was returned from the processor
966                         if (processorResult.isAttributeValueReaderProvided()) {
967                             selectedAttributeValueReader = processorResult.getAttributeValueReader();
968                         }
969                     }
970                 }
971 
972                 // After iterating through all the constraints for this processor, add the ones that werent consumed by this processor to the queue
973                 constraintQueue.addAll(additionalConstraints);
974             }
975         }
976     }
977 
978     /**
979      * validates an attribute
980      *
981      * @param result - used to store the validation results
982      * @param attributeValueReader - a class that encapsulate access to both dictionary metadata and object field
983      * values
984      * @param checkIfRequired - check if empty values are required or not
985      * @throws AttributeValidationException
986      */
987     protected void validateAttribute(DictionaryValidationResult result, AttributeValueReader attributeValueReader,
988             boolean checkIfRequired, String validationState,
989             StateMapping stateMapping) throws AttributeValidationException {
990         Constrainable definition = attributeValueReader.getDefinition(attributeValueReader.getAttributeName());
991         validateAttribute(result, definition, attributeValueReader, checkIfRequired, validationState, stateMapping);
992     }
993 
994     /**
995      * Validates the attribute specified by definition
996      *
997      * @param definition -   the constrainable attribute definition of a specific attribute name
998      * @throws AttributeValidationException
999      * @see #validateAttribute(DictionaryValidationResult, AttributeValueReader, boolean) for the other parameters
1000      */
1001     protected void validateAttribute(DictionaryValidationResult result, Constrainable definition,
1002             AttributeValueReader attributeValueReader, boolean checkIfRequired, String validationState,
1003             StateMapping stateMapping) throws AttributeValidationException {
1004 
1005         if (definition == null) {
1006             throw new AttributeValidationException(
1007                     "Unable to validate constraints for attribute \"" + attributeValueReader.getAttributeName() +
1008                             "\" on entry \"" + attributeValueReader.getEntryName() +
1009                             "\" because no attribute definition can be found.");
1010         }
1011 
1012         Object value = attributeValueReader.getValue();
1013 
1014         processElementConstraints(result, value, definition, attributeValueReader, checkIfRequired, validationState,
1015                 stateMapping);
1016     }
1017 
1018     /**
1019      * validates an object and its attributes recursively
1020      *
1021      * @param result - used to store the validation results
1022      * @param attributeValueReader - a class that encapsulate access to both dictionary metadata and object field
1023      * values
1024      * @param doOptionalProcessing - true if the validation should do optional validation, false otherwise
1025      * @param processAttributes - if true process all attribute definitions, skip if false
1026      * @throws AttributeValidationException
1027      */
1028     protected void validateObject(DictionaryValidationResult result, AttributeValueReader attributeValueReader,
1029             boolean doOptionalProcessing, boolean processAttributes, String validationState,
1030             StateMapping stateMapping) throws AttributeValidationException {
1031 
1032         // If the entry itself is constrainable then the attribute value reader will return it here and we'll need to check if it has any constraints
1033         Constrainable objectEntry = attributeValueReader.getEntry();
1034         processElementConstraints(result, attributeValueReader.getObject(), objectEntry, attributeValueReader,
1035                 doOptionalProcessing, validationState, stateMapping);
1036 
1037         List<Constrainable> definitions = attributeValueReader.getDefinitions();
1038 
1039         // Exit if the attribute value reader has no child definitions
1040         if (null == definitions) {
1041             return;
1042         }
1043 
1044         //Process all attribute definitions (unless being skipped)
1045         if (processAttributes) {
1046             for (Constrainable definition : definitions) {
1047                 String attributeName = definition.getName();
1048                 attributeValueReader.setAttributeName(attributeName);
1049 
1050                 if (attributeValueReader.isReadable()) {
1051                     Object value = attributeValueReader.getValue(attributeName);
1052 
1053                     processElementConstraints(result, value, definition, attributeValueReader, doOptionalProcessing,
1054                             validationState, stateMapping);
1055                 }
1056             }
1057         }
1058 
1059         //Process any constraints that may be defined on complex attributes
1060         if (objectEntry instanceof DataDictionaryEntryBase) {
1061             List<ComplexAttributeDefinition> complexAttrDefinitions =
1062                     ((DataDictionaryEntryBase) objectEntry).getComplexAttributes();
1063 
1064             if (complexAttrDefinitions != null) {
1065                 for (ComplexAttributeDefinition complexAttrDefinition : complexAttrDefinitions) {
1066                     String attributeName = complexAttrDefinition.getName();
1067                     attributeValueReader.setAttributeName(attributeName);
1068 
1069                     if (attributeValueReader.isReadable()) {
1070                         Object value = attributeValueReader.getValue();
1071 
1072                         DataDictionaryEntry childEntry = complexAttrDefinition.getDataObjectEntry();
1073                         if (value != null) {
1074                             AttributeValueReader nestedAttributeValueReader = new DictionaryObjectAttributeValueReader(
1075                                     value, childEntry.getFullClassName(), childEntry, attributeValueReader.getPath());
1076                             nestedAttributeValueReader.setAttributeName(attributeValueReader.getAttributeName());
1077                             //Validate nested object, however skip attribute definition porcessing on
1078                             //nested object entry, since they have already been processed above.
1079                             validateObject(result, nestedAttributeValueReader, doOptionalProcessing, false,
1080                                     validationState, stateMapping);
1081                         }
1082 
1083                         processElementConstraints(result, value, complexAttrDefinition, attributeValueReader,
1084                                 doOptionalProcessing, validationState, stateMapping);
1085                     }
1086                 }
1087             }
1088         }
1089 
1090         //FIXME: I think we may want to use a new CollectionConstrainable interface instead to obtain from
1091         //DictionaryObjectAttributeValueReader
1092         DataObjectEntry entry = (DataObjectEntry) attributeValueReader.getEntry();
1093         if (entry != null) {
1094             for (CollectionDefinition collectionDefinition : entry.getCollections()) {
1095                 //TODO: Do we need to be able to handle simple collections (ie. String, etc)
1096 
1097                 String childEntryName = collectionDefinition.getDataObjectClass();
1098                 String attributeName = collectionDefinition.getName();
1099                 attributeValueReader.setAttributeName(attributeName);
1100 
1101                 if (attributeValueReader.isReadable()) {
1102                     Collection<?> collectionObject = attributeValueReader.getValue();
1103                     DataDictionaryEntry childEntry = childEntryName != null ?
1104                             getDataDictionaryService().getDataDictionary().getDictionaryObjectEntry(childEntryName) :
1105                             null;
1106                     if (collectionObject != null) {
1107                         int index = 0;
1108                         for (Object value : collectionObject) {
1109                             //NOTE: This path is only correct for collections that guarantee order
1110                             String objectAttributePath = attributeValueReader.getPath() + "[" + index + "]";
1111 
1112                             //FIXME: It's inefficient to be creating new attribute reader for each item in collection
1113                             AttributeValueReader nestedAttributeValueReader = new DictionaryObjectAttributeValueReader(
1114                                     value, childEntryName, childEntry, objectAttributePath);
1115                             validateObject(result, nestedAttributeValueReader, doOptionalProcessing, true,
1116                                     validationState, stateMapping);
1117                             index++;
1118                         }
1119                     }
1120 
1121                     processCollectionConstraints(result, collectionObject, collectionDefinition, attributeValueReader,
1122                             doOptionalProcessing, validationState, stateMapping);
1123                 }
1124             }
1125         }
1126     }
1127 
1128     /**
1129      * gets the {@link DataDictionaryService}
1130      *
1131      * @return Returns the dataDictionaryService
1132      */
1133     public DataDictionaryService getDataDictionaryService() {
1134         return dataDictionaryService;
1135     }
1136 
1137     /**
1138      * sets the {@link DataDictionaryService}
1139      *
1140      * @param dataDictionaryService The dataDictionaryService to set
1141      */
1142     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1143         this.dataDictionaryService = dataDictionaryService;
1144     }
1145 
1146     /**
1147      * Sets the {@link BusinessObjectService} attribute value
1148      *
1149      * @param businessObjectService - the businessObjectService to set
1150      */
1151     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1152         this.businessObjectService = businessObjectService;
1153     }
1154 
1155     /**
1156      * Sets the {@link PersistenceService} attribute value
1157      *
1158      * @param persistenceService The persistenceService to set
1159      */
1160     public void setPersistenceService(PersistenceService persistenceService) {
1161         this.persistenceService = persistenceService;
1162     }
1163 
1164     /**
1165      * sets the @{PersistenceStructureService}
1166      *
1167      * @param persistenceStructureService - the {@code PersistenceStructureService} to set
1168      */
1169     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
1170         this.persistenceStructureService = persistenceStructureService;
1171     }
1172 
1173     /**
1174      * gets the locally saved instance of @{link WorkflowAttributePropertyResolutionService}
1175      *
1176      * <p>If the instance in this class has not been initialized, retrieve it using
1177      * {@link KRADServiceLocatorInternal#getWorkflowAttributePropertyResolutionService()} and save locally</p>
1178      *
1179      * @return the locally saved instance of {@code WorkflowAttributePropertyResolutionService}
1180      */
1181     protected WorkflowAttributePropertyResolutionService getWorkflowAttributePropertyResolutionService() {
1182         if (workflowAttributePropertyResolutionService == null) {
1183             workflowAttributePropertyResolutionService =
1184                     KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
1185         }
1186         return workflowAttributePropertyResolutionService;
1187     }
1188 
1189     /**
1190      * gets the list of {@link CollectionConstraintProcessor}
1191      *
1192      * <p>Collection constraint processors are classes that determine if a feature of a collection of objects
1193      * satisfies some constraint</p>
1194      *
1195      * @return the collectionConstraintProcessors
1196      */
1197     @SuppressWarnings("unchecked")
1198     public List<CollectionConstraintProcessor> getCollectionConstraintProcessors() {
1199         return this.collectionConstraintProcessors;
1200     }
1201 
1202     /**
1203      * sets the list of {@link CollectionConstraintProcessor}
1204      *
1205      * @param collectionConstraintProcessors the collectionConstraintProcessors to set
1206      */
1207     @SuppressWarnings("unchecked")
1208     public void setCollectionConstraintProcessors(List<CollectionConstraintProcessor> collectionConstraintProcessors) {
1209         this.collectionConstraintProcessors = collectionConstraintProcessors;
1210     }
1211 
1212     /**
1213      * gets the list of {@link ConstraintProvider}s
1214      *
1215      * <p>Constraint providers are classes that map specific constraint types to a constraint resolver,
1216      * which takes a constrainable definition</p>
1217      *
1218      * @return the constraintProviders
1219      */
1220     @SuppressWarnings("unchecked")
1221     public List<ConstraintProvider> getConstraintProviders() {
1222         return this.constraintProviders;
1223     }
1224 
1225     /**
1226      * sets a list of {@link ConstraintProvider}
1227      *
1228      * @param constraintProviders the constraintProviders to set
1229      */
1230     @SuppressWarnings("unchecked")
1231     public void setConstraintProviders(List<ConstraintProvider> constraintProviders) {
1232         this.constraintProviders = constraintProviders;
1233     }
1234 
1235     /**
1236      * gets the list of element {@link ConstraintProcessor}
1237      *
1238      * <p>Element constraint processors are classes that determine if a passed value is valid
1239      * for a specific constraint at the individual object or object attribute level</p>
1240      *
1241      * @return the elementConstraintProcessors
1242      */
1243     @SuppressWarnings("unchecked")
1244     public List<ConstraintProcessor> getElementConstraintProcessors() {
1245         return this.elementConstraintProcessors;
1246     }
1247 
1248     /**
1249      * sets the list of {@link ConstraintProcessor}
1250      *
1251      * @param elementConstraintProcessors the elementConstraintProcessors to set
1252      */
1253     @SuppressWarnings("unchecked")
1254     public void setElementConstraintProcessors(List<ConstraintProcessor> elementConstraintProcessors) {
1255         this.elementConstraintProcessors = elementConstraintProcessors;
1256     }
1257 
1258     /**
1259      * gets the locally saved instance of @{link DocumentDictionaryService}
1260      *
1261      * <p>If the instance in this class has not be set, retrieve it using
1262      * {@link KRADServiceLocatorWeb#getDocumentDictionaryService()} and save locally</p>
1263      *
1264      * @return the locally saved instance of {@code DocumentDictionaryService}
1265      */
1266     public DocumentDictionaryService getDocumentDictionaryService() {
1267         if (documentDictionaryService == null) {
1268             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
1269         }
1270         return documentDictionaryService;
1271     }
1272 
1273     /**
1274      * sets the {@link DocumentDictionaryService}
1275      *
1276      * @param documentDictionaryService - the {@code DocumentDictionaryService} to set
1277      */
1278     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
1279         this.documentDictionaryService = documentDictionaryService;
1280     }
1281 }