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