View Javadoc

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