001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.service.impl;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.ArrayUtils;
020import org.apache.commons.lang.StringUtils;
021import org.kuali.rice.core.api.mo.common.active.Inactivatable;
022import org.kuali.rice.core.api.util.RiceKeyConstants;
023import org.kuali.rice.krad.bo.BusinessObject;
024import org.kuali.rice.krad.bo.PersistableBusinessObject;
025import org.kuali.rice.krad.data.DataObjectWrapper;
026import org.kuali.rice.krad.data.KradDataServiceLocator;
027import org.kuali.rice.krad.datadictionary.CollectionDefinition;
028import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition;
029import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
030import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
031import org.kuali.rice.krad.datadictionary.DataObjectEntry;
032import org.kuali.rice.krad.datadictionary.ReferenceDefinition;
033import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException;
034import org.kuali.rice.krad.datadictionary.state.StateMapping;
035import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
036import org.kuali.rice.krad.datadictionary.validation.DictionaryObjectAttributeValueReader;
037import org.kuali.rice.krad.datadictionary.validation.ErrorLevel;
038import org.kuali.rice.krad.datadictionary.validation.capability.Constrainable;
039import org.kuali.rice.krad.datadictionary.validation.constraint.Constraint;
040import org.kuali.rice.krad.datadictionary.validation.constraint.provider.ConstraintProvider;
041import org.kuali.rice.krad.datadictionary.validation.processor.CollectionConstraintProcessor;
042import org.kuali.rice.krad.datadictionary.validation.processor.ConstraintProcessor;
043import org.kuali.rice.krad.datadictionary.validation.result.ConstraintValidationResult;
044import org.kuali.rice.krad.datadictionary.validation.result.DictionaryValidationResult;
045import org.kuali.rice.krad.datadictionary.validation.result.ProcessorResult;
046import org.kuali.rice.krad.document.Document;
047import org.kuali.rice.krad.document.TransactionalDocument;
048import org.kuali.rice.krad.service.DataDictionaryService;
049import org.kuali.rice.krad.service.DictionaryValidationService;
050import org.kuali.rice.krad.service.DocumentDictionaryService;
051import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
052import org.kuali.rice.krad.service.LegacyDataAdapter;
053import org.kuali.rice.krad.uif.UifConstants;
054import org.kuali.rice.krad.uif.util.ConstraintStateUtils;
055import org.kuali.rice.krad.util.ErrorMessage;
056import org.kuali.rice.krad.util.GlobalVariables;
057import org.kuali.rice.krad.util.KRADUtils;
058import org.kuali.rice.krad.util.MessageMap;
059import org.springframework.beans.PropertyAccessorUtils;
060
061import java.beans.PropertyDescriptor;
062import java.lang.reflect.InvocationTargetException;
063import java.util.Collection;
064import java.util.IdentityHashMap;
065import java.util.Iterator;
066import java.util.LinkedList;
067import java.util.List;
068import java.util.Map;
069import java.util.Queue;
070import java.util.Set;
071
072/**
073 * Validates Documents, Business Objects, and Attributes against the data dictionary. Including min, max lengths, and
074 * validating expressions. This is the default, Kuali delivered implementation.
075 *
076 * KULRICE - 3355 Modified to prevent infinite looping (to maxDepth) scenario when a parent references a child which
077 * references a parent
078 *
079 * @author Kuali Rice Team (rice.collab@kuali.org)
080 */
081public class DictionaryValidationServiceImpl implements DictionaryValidationService {
082    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
083            DictionaryValidationServiceImpl.class);
084
085    /**
086     * Constant defines a validation method for an attribute value.
087     * <p>Value is "validate"
088     */
089    public static final String VALIDATE_METHOD = "validate";
090
091    protected DataDictionaryService dataDictionaryService;
092    //    protected BusinessObjectService businessObjectService;
093    //    protected PersistenceService persistenceService;
094    protected DocumentDictionaryService documentDictionaryService;
095    //    protected PersistenceStructureService persistenceStructureService;
096    @Deprecated
097    private LegacyDataAdapter legacyDataAdapter;
098
099    @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}