View Javadoc

1   /*
2    * Copyright 2005-2007 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.kns.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.util.RiceKeyConstants;
20  import org.kuali.rice.kew.api.KewApiServiceLocator;
21  import org.kuali.rice.kew.api.doctype.DocumentType;
22  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
23  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
24  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
25  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
26  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
27  import org.kuali.rice.kns.document.MaintenanceDocument;
28  import org.kuali.rice.kns.maintenance.Maintainable;
29  import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
30  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
31  import org.kuali.rice.krad.bo.PersistableBusinessObject;
32  import org.kuali.rice.krad.datadictionary.DataDictionary;
33  import org.kuali.rice.krad.rule.BusinessRule;
34  import org.kuali.rice.krad.service.DataDictionaryService;
35  import org.kuali.rice.krad.util.GlobalVariables;
36  import org.kuali.rice.krad.util.ObjectUtils;
37  import org.kuali.rice.krad.valuefinder.ValueFinder;
38  
39  import java.util.ArrayList;
40  import java.util.Collection;
41  import java.util.Iterator;
42  import java.util.List;
43  
44  /**
45   * This class is the service implementation for the MaintenanceDocumentDictionary structure. Defines the API for the interacting
46   * with Document-related entries in the data dictionary. This is the default implementation, that is delivered with Kuali.
47   */
48  @Deprecated
49  public class MaintenanceDocumentDictionaryServiceImpl implements MaintenanceDocumentDictionaryService {
50      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentDictionaryServiceImpl.class);
51  
52      private DataDictionaryService dataDictionaryService;
53  
54      /**
55       * Gets the workflow document type for the given documentTypeName
56       * 
57       * @param documentTypeName
58       * @return
59       */
60      protected DocumentType getDocumentType(String documentTypeName) {
61          return KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(documentTypeName);
62      }
63  
64      /**
65       * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintenanceLabel(java.lang.String)
66       */
67      public String getMaintenanceLabel(String docTypeName) {
68          String label = null;
69  
70          DocumentType docType = getDocumentType(docTypeName);
71          if (docType != null) {
72              label = docType.getLabel();
73          }
74  
75          return label;
76      }
77  
78      /**
79       * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintenanceDescription(java.lang.String)
80       */
81      public String getMaintenanceDescription(String docTypeName) {
82          String description = null;
83  
84          DocumentType docType = getDocumentType(docTypeName);
85          if (docType != null) {
86              description = docType.getDescription();
87          }
88  
89          return description;
90      }
91  
92      /**
93       * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintainableClass(java.lang.String)
94       */
95      @Deprecated
96      public Class getMaintainableClass(String docTypeName) {
97          Class maintainableClass = null;
98  
99          MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
100         if (entry != null) {
101             LOG.debug("suppling a generic Rule to insure basic validation");
102             maintainableClass = entry.getMaintainableClass();
103         }
104 
105         return maintainableClass;
106     }
107 
108     /**
109      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDataObjectClass(java.lang.String)
110      */
111     public Class getDataObjectClass(String docTypeName) {
112         Class dataObjectClass = null;
113 
114         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
115         if (entry != null) {
116             dataObjectClass = entry.getDataObjectClass();
117         }
118 
119         return dataObjectClass;
120     }
121 
122     /**
123      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDocumentTypeName(java.lang.Class)
124      */
125     public String getDocumentTypeName(Class businessObjectClass) {
126         String documentTypeName = null;
127 
128         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(businessObjectClass);
129         if (entry != null) {
130             documentTypeName = entry.getDocumentTypeName();
131         }
132 
133         return documentTypeName;
134     }
135 
136     /**
137      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintainableSections(java.lang.String)
138      */
139     @Deprecated
140     public List getMaintainableSections(String docTypeName) {
141         List sections = null;
142 
143         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
144         if (entry != null) {
145             sections = entry.getMaintainableSections();
146         }
147 
148         return sections;
149     }
150 
151     /**
152      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getBusinessRulesClass(MaintenanceDocument)
153      */
154     public Class<? extends BusinessRule> getBusinessRulesClass(MaintenanceDocument document) {
155         Maintainable maintainable = document.getOldMaintainableObject();
156         if (maintainable == null) {
157             throw new IllegalArgumentException("unable to determine documentType for maintenanceDocument with no oldMaintainableObject");
158         }
159 
160         Class<? extends BusinessRule> businessRulesClass = null;
161 
162         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(maintainable.getBoClass());
163         if (entry != null) {
164             businessRulesClass = entry.getBusinessRulesClass();
165         }
166 
167         if (businessRulesClass == null) {
168             return MaintenanceDocumentRuleBase.class; // default to a generic rule that will enforce Required fields
169         }
170 
171         LOG.info("return class: " + businessRulesClass.getName());
172 
173         return businessRulesClass;
174     }
175 
176     /**
177      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDefaultExistenceChecks(java.lang.Class)
178      */
179     public Collection getDefaultExistenceChecks(Class businessObjectClass) {
180         return getDefaultExistenceChecks(getDocumentTypeName(businessObjectClass));
181     }
182 
183     /**
184      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDefaultExistenceChecks(java.lang.String)
185      */
186     public Collection getDefaultExistenceChecks(String docTypeName) {
187 
188         Collection defaultExistenceChecks = null;
189 
190         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
191         if (entry != null) {
192             defaultExistenceChecks = entry.getDefaultExistenceChecks();
193         }
194 
195         return defaultExistenceChecks;
196     }
197 
198     /**
199      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getLockingKeys(java.lang.String)
200      */
201     public List getLockingKeys(String docTypeName) {
202         List lockingKeys = null;
203 
204         MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
205         if (entry != null) {
206             lockingKeys = entry.getLockingKeyFieldNames();
207         }
208 
209         return lockingKeys;
210     }
211 
212     /**
213      * @param dataDictionaryService
214      */
215     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
216         this.dataDictionaryService = dataDictionaryService;
217     }
218 
219     /**
220      * @return
221      */
222     public DataDictionary getDataDictionary() {
223         return this.dataDictionaryService.getDataDictionary();
224     }
225 
226     /**
227      * @param docTypeName
228      * @return
229      */
230     public MaintenanceDocumentEntry getMaintenanceDocumentEntry(String docTypeName) {
231         if (StringUtils.isBlank(docTypeName)) {
232             throw new IllegalArgumentException("invalid (blank) docTypeName");
233         }
234 
235         MaintenanceDocumentEntry entry = (MaintenanceDocumentEntry)getDataDictionary().getDocumentEntry(docTypeName);
236         return entry;
237     }
238 
239     private MaintenanceDocumentEntry getMaintenanceDocumentEntry(Class businessObjectClass) {
240         if (businessObjectClass == null) {
241             throw new IllegalArgumentException("invalid (blank) dataObjectClass");
242         }
243 
244         MaintenanceDocumentEntry entry = (MaintenanceDocumentEntry) getDataDictionary().getMaintenanceDocumentEntryForBusinessObjectClass(businessObjectClass);
245         return entry;
246     }
247 
248     /**
249      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getFieldDefaultValue(java.lang.Class, java.lang.String)
250      */
251     public String getFieldDefaultValue(Class boClass, String fieldName) {
252 
253         // input parameter validation
254         if (boClass == null) {
255             throw new IllegalArgumentException("The boClass parameter value specified was " + "null.  A valid class representing the boClass must " + "be specified.");
256         }
257 
258         // call the twin
259         return getFieldDefaultValue(getDocumentTypeName(boClass), fieldName);
260     }
261 
262     /**
263      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getFieldDefaultValue(java.lang.String, java.lang.String)
264      */
265     public String getFieldDefaultValue(String docTypeName, String fieldName) {
266 
267         // input parameter validation
268         if (StringUtils.isBlank(docTypeName)) {
269             throw new IllegalArgumentException("The docTypeName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the docTypeName must " + "be specified.");
270         }
271         if (StringUtils.isBlank(fieldName)) {
272             throw new IllegalArgumentException("The fieldName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the fieldName must " + "be specified.");
273         }
274 
275         // walk through the sections
276         List sections = getMaintainableSections(docTypeName);
277         for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
278             MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
279 
280             // walk through the fields
281             Collection fields = section.getMaintainableItems();
282             String defaultValue = getFieldDefaultValue(fields, fieldName);
283             // need to keep trying sections until a match is found
284             if (defaultValue != null) {
285                 return defaultValue;
286             }
287         }
288         return null;
289     }
290 
291     private String getFieldDefaultValue(Collection maintainableFields, String fieldName) {
292         for (Iterator iterator = maintainableFields.iterator(); iterator.hasNext();) {
293             MaintainableItemDefinition item = (MaintainableItemDefinition) iterator.next();
294             // only check fields...skip subcollections
295             if (item instanceof MaintainableFieldDefinition) {
296 
297                 MaintainableFieldDefinition field = (MaintainableFieldDefinition) item;
298 
299                 // if the field name matches
300                 if (field.getName().endsWith(fieldName)) {
301 
302                     // preferentially take the raw default value
303                     if (StringUtils.isNotBlank(field.getDefaultValue())) {
304                         return field.getDefaultValue();
305                     }
306 
307                     // take the valuefinder
308                     else if (field.getDefaultValueFinderClass() != null) {
309 
310                         // attempt to get an instance of the defaultValueFinderClass
311                         ValueFinder valueFinder = null;
312                         try {
313                             valueFinder = (ValueFinder) field.getDefaultValueFinderClass().newInstance();
314                         }
315                         catch (Exception e) {
316                             LOG.info("Exception obtaining valueFinder for collection field default value", e);
317                             valueFinder = null;
318                         }
319 
320                         // get the value
321                         if (valueFinder != null) {
322                             return valueFinder.getValue();
323                         }
324                     }
325                     // if we found the field, but no default anything, then we're done
326                     else {
327                         return null;
328                     }
329                 }
330             }
331         }
332         return null;
333     }
334 
335     /**
336      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getCollectionFieldDefaultValue(java.lang.String,
337      *      java.lang.String, java.lang.String)
338      */
339     public String getCollectionFieldDefaultValue(String docTypeName, String collectionName, String fieldName) {
340         // input parameter validation
341         if (StringUtils.isBlank(docTypeName)) {
342             throw new IllegalArgumentException("The docTypeName parameter value specified was blank, whitespace, or null.  A valid string representing the docTypeName must be specified.");
343         }
344         if (StringUtils.isBlank(fieldName)) {
345             throw new IllegalArgumentException("The fieldName parameter value specified was blank, whitespace, or null.  A valid string representing the fieldName must be specified.");
346         }
347         if (StringUtils.isBlank(collectionName)) {
348             throw new IllegalArgumentException("The collectionName parameter value specified was null.  A valid string representing the collectionName must be specified.");
349         }
350 
351         MaintainableCollectionDefinition coll = getMaintainableCollection(docTypeName, collectionName);
352         if (coll != null) {
353             Collection collectionFields = coll.getMaintainableFields();
354             return getFieldDefaultValue(collectionFields, fieldName);
355         }
356         return null;
357     }
358 
359     /**
360      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsCopy(MaintenanceDocument)
361      */
362     public Boolean getAllowsCopy(MaintenanceDocument document) {
363         Boolean allowsCopy = Boolean.FALSE;
364         if (document != null && document.getNewMaintainableObject() != null) {
365             MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(document.getNewMaintainableObject().getBoClass());
366             if (entry != null) {
367                 allowsCopy = Boolean.valueOf(entry.getAllowsCopy());
368             }
369         }
370 
371         return allowsCopy;
372     }
373 
374     /**
375      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsNewOrCopy(java.lang.String)
376      */
377     public Boolean getAllowsNewOrCopy(String docTypeName) {
378         Boolean allowsNewOrCopy = Boolean.FALSE;
379 
380         if (docTypeName != null) {
381             MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
382             if (entry != null) {
383                 allowsNewOrCopy = Boolean.valueOf(entry.getAllowsNewOrCopy());
384             }
385         }
386 
387         return allowsNewOrCopy;
388     }
389 
390     public MaintainableItemDefinition getMaintainableItem(String docTypeName, String itemName) {
391         // input parameter validation
392         if (StringUtils.isBlank(docTypeName)) {
393             throw new IllegalArgumentException("The docTypeName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the docTypeName must " + "be specified.");
394         }
395         if (StringUtils.isBlank(itemName)) {
396             throw new IllegalArgumentException("The itemName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the itemName must " + "be specified.");
397         }
398 
399         // split name for subcollections
400         String[] subItems = {};
401         subItems = StringUtils.split(itemName, ".");
402 
403 
404         // walk through the sections
405         List sections = getMaintainableSections(docTypeName);
406         for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
407             MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
408 
409             // walk through the fields
410             Collection fields = section.getMaintainableItems();
411             for (Iterator fieldIterator = fields.iterator(); fieldIterator.hasNext();) {
412                 MaintainableItemDefinition item = (MaintainableItemDefinition) fieldIterator.next();
413 
414                 if (item.getName().equals(itemName)) {
415                     return item;
416                 }
417                 // if collection check to see if it has sub collections
418                 // for now this only allows 1 level (i.e. a.b) it should be expanded at some point
419                 if (item instanceof MaintainableCollectionDefinition) {
420                     MaintainableCollectionDefinition col = (MaintainableCollectionDefinition) item;
421                     if ((subItems.length > 1) && (StringUtils.equals(col.getName(), subItems[0]))) {
422                         for (Iterator<MaintainableCollectionDefinition> colIterator = col.getMaintainableCollections().iterator(); colIterator.hasNext();) {
423                             MaintainableCollectionDefinition subCol = (MaintainableCollectionDefinition) colIterator.next();
424                             if (subCol.getName().equals(subItems[1])) {
425                                 return subCol;
426                             }
427                         }
428                     }
429                 }
430             }
431         }
432         return null;
433     }
434 
435     public MaintainableFieldDefinition getMaintainableField(String docTypeName, String fieldName) {
436         MaintainableItemDefinition item = getMaintainableItem(docTypeName, fieldName);
437         if (item != null && item instanceof MaintainableFieldDefinition) {
438             return (MaintainableFieldDefinition) item;
439         }
440         return null;
441     }
442 
443     public MaintainableCollectionDefinition getMaintainableCollection(String docTypeName, String collectionName) {
444         // strip brackets as they are not needed to get to collection class
445         // Like the other subcollections changes this currently only supports one sub level
446         if (StringUtils.contains(collectionName, "[")) {
447             collectionName = StringUtils.substringBefore(collectionName, "[") + StringUtils.substringAfter(collectionName, "]");
448         }
449         MaintainableItemDefinition item = getMaintainableItem(docTypeName, collectionName);
450         if (item != null && item instanceof MaintainableCollectionDefinition) {
451             return (MaintainableCollectionDefinition) item;
452         }
453         return null;
454     }
455 
456     public Class getCollectionBusinessObjectClass(String docTypeName, String collectionName) {
457         MaintainableCollectionDefinition coll = getMaintainableCollection(docTypeName, collectionName);
458         if (coll != null) {
459             return coll.getBusinessObjectClass();
460         }
461         return null;
462     }
463 
464     public List<MaintainableCollectionDefinition> getMaintainableCollections(String docTypeName) {
465         ArrayList<MaintainableCollectionDefinition> collections = new ArrayList<MaintainableCollectionDefinition>();
466 
467         // walk through the sections
468         List sections = getMaintainableSections(docTypeName);
469         for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
470             MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
471 
472             // walk through the fields
473             Collection fields = section.getMaintainableItems();
474             for (Iterator fieldIterator = fields.iterator(); fieldIterator.hasNext();) {
475                 MaintainableItemDefinition item = (MaintainableItemDefinition) fieldIterator.next();
476 
477                 if (item instanceof MaintainableCollectionDefinition) {
478                     collections.add((MaintainableCollectionDefinition) item);
479                     // collections.addAll( getMaintainableCollections( (MaintainableCollectionDefinition)item ) );
480                 }
481             }
482         }
483 
484         return collections;
485     }
486 
487     public List<MaintainableCollectionDefinition> getMaintainableCollections(MaintainableCollectionDefinition parentCollection) {
488         ArrayList<MaintainableCollectionDefinition> collections = new ArrayList<MaintainableCollectionDefinition>();
489 
490         // walk through the sections
491         Collection<MaintainableCollectionDefinition> colls = parentCollection.getMaintainableCollections();
492         for (MaintainableCollectionDefinition coll : colls) {
493             collections.add(coll);
494             collections.addAll(getMaintainableCollections(coll));
495         }
496 
497         return collections;
498     }
499 
500     /**
501      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#validateMaintenanceRequiredFields(org.kuali.rice.krad.document.MaintenanceDocument)
502      */
503     public void validateMaintenanceRequiredFields(MaintenanceDocument document) {
504         Maintainable newMaintainableObject = document.getNewMaintainableObject();
505         if (newMaintainableObject == null) {
506             LOG.error("New maintainable is null");
507             throw new RuntimeException("New maintainable is null");
508         }
509 
510         List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections(getDocumentTypeName(newMaintainableObject.getBoClass()));
511         for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
512             for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
513                 // validate fields
514                 if (maintainableItemDefinition instanceof MaintainableFieldDefinition) {
515                     validateMaintainableFieldRequiredFields((MaintainableFieldDefinition) maintainableItemDefinition, newMaintainableObject.getBusinessObject(), maintainableItemDefinition.getName());
516                 }
517                 // validate collections
518                 else if (maintainableItemDefinition instanceof MaintainableCollectionDefinition) {
519                     validateMaintainableCollectionsRequiredFields(newMaintainableObject.getBusinessObject(), (MaintainableCollectionDefinition) maintainableItemDefinition);
520                 }
521             }
522         }
523     }
524 
525     /**
526      * generates error message if a field is marked as required but is not filled in
527      * 
528      * @param maintainableFieldDefinition
529      * @param businessObject
530      * @param fieldName
531      */
532     private void validateMaintainableFieldRequiredFields(MaintainableFieldDefinition maintainableFieldDefinition, PersistableBusinessObject businessObject, String fieldName) {
533 
534         if (StringUtils.isBlank(fieldName)) {
535             throw new IllegalArgumentException("invalid fieldName parameter.");
536         }
537         // if required check we have a value for this field
538         if (maintainableFieldDefinition.isRequired() && !maintainableFieldDefinition.isUnconditionallyReadOnly() ) {
539             try {
540                 Object obj = ObjectUtils.getNestedValue(businessObject, fieldName);
541 
542                 if (obj == null || StringUtils.isBlank(obj.toString())) {
543                     String attributeLabel = dataDictionaryService.getAttributeLabel(businessObject.getClass(), fieldName);
544                     String shortLabel = dataDictionaryService.getAttributeShortLabel(businessObject.getClass(), fieldName);
545                     GlobalVariables.getMessageMap().putError(fieldName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel + " (" + shortLabel + ")" );
546                 } else if ( fieldName.endsWith(".principalName") ) {
547                     // special handling to catch when the principalName is not really a valid user
548                     // pull the Person object and test the entity ID.  If that's null, then this
549                     // is just a shell user instance and does not represent a true user
550                     // the main principalId property on the main object would be null at this point
551                     // but it is also unconditionally read only and not tested - checking that would
552                     // require checking the relationships and be more complex than we want to get here
553                     String personProperty = ObjectUtils.getNestedAttributePrefix(fieldName); 
554                     if ( StringUtils.isNotBlank(personProperty) ) {
555                         if ( StringUtils.isBlank( (String)ObjectUtils.getNestedValue(businessObject, personProperty+".entityId") ) ) {
556                             String attributeLabel = dataDictionaryService.getAttributeLabel(businessObject.getClass(), fieldName);
557                             GlobalVariables.getMessageMap().putError(fieldName, RiceKeyConstants.ERROR_EXISTENCE, attributeLabel );
558                         }
559                     }
560                 }
561             } catch( Exception ex ) {
562                 LOG.error( "unable to read property during doc required field checks", ex );
563             }
564         }
565     }
566 
567     
568     private MaintainableCollectionDefinition getCollectionDefinition( String docTypeName, String collectionName ) {
569         String currentCollection = collectionName;
570         String nestedCollections = "";
571     	if (StringUtils.contains(collectionName, "[")) {
572     		// strip off any array indexes
573             currentCollection = StringUtils.substringBefore( collectionName, "[" );
574             nestedCollections = StringUtils.substringAfter( collectionName, "." );
575     	}
576     	
577         // loop over all sections to find this collection
578         List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections( docTypeName );
579         for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
580             for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
581                 if (maintainableItemDefinition instanceof MaintainableCollectionDefinition && maintainableItemDefinition.getName().equals( currentCollection ) ) {
582                     if ( StringUtils.isBlank( nestedCollections ) ) {
583                         return (MaintainableCollectionDefinition) maintainableItemDefinition;
584                     } 
585                     
586                     return getCollectionDefinition( (MaintainableCollectionDefinition)maintainableItemDefinition, nestedCollections );
587                 }
588             }
589         }
590         
591         return null;
592     }
593 
594     private MaintainableCollectionDefinition getCollectionDefinition( MaintainableCollectionDefinition collectionDef, String collectionName ) {
595         String currentCollection = collectionName;
596         String nestedCollections = "";
597     	if (StringUtils.contains(collectionName, "[")) {
598     		// strip off any array indexes
599             currentCollection = StringUtils.substringBefore( collectionName, "[" );
600             nestedCollections = StringUtils.substringAfter( collectionName, "." );
601     	}
602         
603         // loop over all nested collections
604         for (MaintainableCollectionDefinition maintainableCollectionDefinition : collectionDef.getMaintainableCollections()) {
605             if ( maintainableCollectionDefinition.getName().equals( currentCollection ) ) {
606                 if ( StringUtils.isBlank( nestedCollections ) ) {
607                     return maintainableCollectionDefinition;
608                 } 
609                 return getCollectionDefinition( maintainableCollectionDefinition, nestedCollections );
610             }
611         }
612         
613         return null;
614     }
615     
616     public void validateMaintainableCollectionsAddLineRequiredFields(MaintenanceDocument document, PersistableBusinessObject businessObject, String collectionName ) {
617         MaintainableCollectionDefinition def = getCollectionDefinition( getDocumentTypeName(businessObject.getClass()), collectionName );
618         if ( def != null ) {
619             validateMaintainableCollectionsAddLineRequiredFields( document, businessObject, collectionName, def, 0);
620         }
621     }
622     /**
623      * calls code to generate error messages if maintainableFields within any collections or sub-collections are marked as required
624      * 
625      * @param document
626      * @param businessObject
627      * @param collectionName
628      * @param maintainableCollectionDefinition
629      * @param depth
630      */
631     private void validateMaintainableCollectionsAddLineRequiredFields(MaintenanceDocument document, PersistableBusinessObject businessObject, String collectionName, MaintainableCollectionDefinition maintainableCollectionDefinition, int depth) {
632         if ( depth == 0 ) {
633             GlobalVariables.getMessageMap().addToErrorPath("add");
634         }
635         // validate required fields on fields withing collection definition
636         PersistableBusinessObject element = document.getNewMaintainableObject().getNewCollectionLine( collectionName );
637         GlobalVariables.getMessageMap().addToErrorPath(collectionName);
638         for (MaintainableFieldDefinition maintainableFieldDefinition : maintainableCollectionDefinition.getMaintainableFields()) {
639             final String fieldName = maintainableFieldDefinition.getName();
640             validateMaintainableFieldRequiredFields(maintainableFieldDefinition, element, fieldName);
641             
642         }
643 
644         GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
645         if ( depth == 0 ) {
646             GlobalVariables.getMessageMap().removeFromErrorPath("add");
647         }
648     }
649 
650     /**
651      * calls code to generate error messages if maintainableFields within any collections or sub-collections are marked as required
652      * 
653      * @param businessObject
654      * @param maintainableCollectionDefinition
655      */
656     private void validateMaintainableCollectionsRequiredFields(PersistableBusinessObject businessObject, MaintainableCollectionDefinition maintainableCollectionDefinition) {
657         final String collectionName = maintainableCollectionDefinition.getName();
658 
659         // validate required fields on fields withing collection definition
660         Collection<PersistableBusinessObject> collection = (Collection) ObjectUtils.getPropertyValue(businessObject, collectionName);
661         if (collection != null && !collection.isEmpty()) {
662             for (MaintainableFieldDefinition maintainableFieldDefinition : maintainableCollectionDefinition.getMaintainableFields()) {
663                 int pos = 0;
664                 final String fieldName = maintainableFieldDefinition.getName();
665                 for (PersistableBusinessObject element : collection) {
666                     String parentName = collectionName + "[" + (pos++) + "]";
667                     GlobalVariables.getMessageMap().addToErrorPath(parentName);
668                     validateMaintainableFieldRequiredFields(maintainableFieldDefinition, element, fieldName);
669                     GlobalVariables.getMessageMap().removeFromErrorPath(parentName);
670                 }
671             }
672 
673             // recursivley validate required fields on subcollections
674             GlobalVariables.getMessageMap().addToErrorPath(collectionName);
675             for (MaintainableCollectionDefinition nestedMaintainableCollectionDefinition : maintainableCollectionDefinition.getMaintainableCollections()) {
676                 for (PersistableBusinessObject element : collection) {
677                     validateMaintainableCollectionsRequiredFields(element, nestedMaintainableCollectionDefinition);
678                 }
679             }
680             GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
681         }
682     }
683     
684     /**
685      * default implementation checks for duplicats based on keys of objects only
686      * 
687      * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#validateMaintainableCollectionsForDuplicateEntries(org.kuali.rice.krad.document.MaintenanceDocument)
688      */
689     public void validateMaintainableCollectionsForDuplicateEntries(MaintenanceDocument document) {
690         Maintainable newMaintainableObject = document.getNewMaintainableObject();
691         if (newMaintainableObject == null) {
692             LOG.error("New maintainable is null");
693             throw new RuntimeException("New maintainable is null");
694         }
695 
696         List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections(getDocumentTypeName(newMaintainableObject.getBoClass()));
697         for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
698             for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
699                 // validate collections
700                 if (maintainableItemDefinition instanceof MaintainableCollectionDefinition) {
701                     validateMaintainableCollectionsForDuplicateEntries(newMaintainableObject.getBusinessObject(), (MaintainableCollectionDefinition) maintainableItemDefinition);
702                 }
703             }
704         }
705     }
706 
707     /**
708      * recursivly checks collections for duplicate entries based on key valuse
709      * 
710      * @param businessObject
711      * @param maintainableCollectionDefinition
712      */
713     private void validateMaintainableCollectionsForDuplicateEntries(PersistableBusinessObject businessObject, MaintainableCollectionDefinition maintainableCollectionDefinition) {
714         final String collectionName = maintainableCollectionDefinition.getName();
715 
716         if (maintainableCollectionDefinition.dissallowDuplicateKey()) {
717             final Class maintainableBusinessObjectClass = businessObject.getClass();
718             // validate that no duplicates based on keys exist
719             Collection<PersistableBusinessObject> collection = (Collection) ObjectUtils.getPropertyValue(businessObject, collectionName);
720             if (collection != null && !collection.isEmpty()) {
721                 final String propertyName = maintainableCollectionDefinition.getAttributeToHighlightOnDuplicateKey();
722                 // get collection label for dd
723                 final String label = dataDictionaryService.getCollectionLabel(maintainableBusinessObjectClass, collectionName);
724                 final String shortLabel = dataDictionaryService.getCollectionShortLabel(maintainableBusinessObjectClass, collectionName);
725                 int pos = 0;
726                 for (PersistableBusinessObject element : collection) {
727                     String pathToElement = collectionName + "[" + (pos++) + "]";
728                     if (ObjectUtils.countObjectsWithIdentitcalKey(collection, element) > 1) {
729                         GlobalVariables.getMessageMap().addToErrorPath(pathToElement);
730                         GlobalVariables.getMessageMap().putError(propertyName, RiceKeyConstants.ERROR_DUPLICATE_ELEMENT, new String[] { label, shortLabel });
731                         GlobalVariables.getMessageMap().removeFromErrorPath(pathToElement);
732                     }
733                 }
734 
735                 // recursivley check for duplicate entries on subcollections
736                 GlobalVariables.getMessageMap().addToErrorPath(collectionName);
737                 for (MaintainableCollectionDefinition nestedMaintainableCollectionDefinition : maintainableCollectionDefinition.getMaintainableCollections()) {
738                     for (PersistableBusinessObject element : collection) {
739                         validateMaintainableCollectionsForDuplicateEntries(element, nestedMaintainableCollectionDefinition);
740                     }
741                 }
742                 GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
743 
744             }
745         }
746     }
747        
748 	/**
749 	 * for issue KULRice 3072
750 	 * 
751 	 * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getgetPreserveLockingKeysOnCopy(java.lang.Class)
752 	 */
753 	public boolean getPreserveLockingKeysOnCopy(Class businessObjectClass) {
754 
755 		boolean preserveLockingKeysOnCopy = false;
756 
757 		MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
758 		
759 		if (docEntry != null) {
760 			preserveLockingKeysOnCopy = docEntry.getPreserveLockingKeysOnCopy();
761 		}
762 		
763 		return preserveLockingKeysOnCopy;
764 	}
765 
766 	/**
767 	 * for isue KULRice 3070
768 	 * 
769 	 * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsRecordDeletion(java.lang.Class)
770 	 */
771 	public Boolean getAllowsRecordDeletion(Class businessObjectClass) {
772 		
773 		Boolean allowsRecordDeletion = Boolean.FALSE;
774 
775 		MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
776 		
777 		if (docEntry != null) {
778 			allowsRecordDeletion = Boolean.valueOf(docEntry.getAllowsRecordDeletion());
779 		}
780 		
781 		return allowsRecordDeletion;
782 	}
783 
784 	/**
785 	 *  for issue KULRice3070, see if need delete button
786 	 * 
787 	 * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsRecordDeletion(org.kuali.rice.krad.document.MaintenanceDocument)
788 	 */
789 	public Boolean getAllowsRecordDeletion(MaintenanceDocument document) {
790         return document != null ? this.getAllowsRecordDeletion(document.getNewMaintainableObject().getBoClass()) : Boolean.FALSE;
791 	}
792 
793 	/**
794 	 * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#translateCodes(java.lang.Class)
795 	 */
796 	public Boolean translateCodes(Class businessObjectClass) {
797 		boolean translateCodes = false;
798 
799 		MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
800 
801 		if (docEntry != null) {
802 			translateCodes = docEntry.isTranslateCodes();
803 		}
804 
805 		return translateCodes;
806 	}
807 
808 }