001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.util.RiceKeyConstants;
020import org.kuali.rice.kew.api.KewApiServiceLocator;
021import org.kuali.rice.kew.api.doctype.DocumentType;
022import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
023import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
024import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
025import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
026import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
027import org.kuali.rice.kns.document.MaintenanceDocument;
028import org.kuali.rice.kns.maintenance.Maintainable;
029import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
030import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
031import org.kuali.rice.krad.bo.PersistableBusinessObject;
032import org.kuali.rice.krad.datadictionary.DataDictionary;
033import org.kuali.rice.krad.rules.rule.BusinessRule;
034import org.kuali.rice.krad.service.DataDictionaryService;
035import org.kuali.rice.krad.util.GlobalVariables;
036import org.kuali.rice.krad.util.ObjectUtils;
037import org.kuali.rice.krad.valuefinder.ValueFinder;
038
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.Iterator;
042import java.util.List;
043
044/**
045 * This class is the service implementation for the MaintenanceDocumentDictionary structure. Defines the API for the interacting
046 * with Document-related entries in the data dictionary. This is the default implementation, that is delivered with Kuali.
047 */
048@Deprecated
049public class MaintenanceDocumentDictionaryServiceImpl implements MaintenanceDocumentDictionaryService {
050    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintenanceDocumentDictionaryServiceImpl.class);
051
052    private DataDictionaryService dataDictionaryService;
053
054    /**
055     * Gets the workflow document type for the given documentTypeName
056     * 
057     * @param documentTypeName
058     * @return
059     */
060    protected DocumentType getDocumentType(String documentTypeName) {
061        return KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(documentTypeName);
062    }
063
064    /**
065     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintenanceLabel(java.lang.String)
066     */
067    public String getMaintenanceLabel(String docTypeName) {
068        String label = null;
069
070        DocumentType docType = getDocumentType(docTypeName);
071        if (docType != null) {
072            label = docType.getLabel();
073        }
074
075        return label;
076    }
077
078    /**
079     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintenanceDescription(java.lang.String)
080     */
081    public String getMaintenanceDescription(String docTypeName) {
082        String description = null;
083
084        DocumentType docType = getDocumentType(docTypeName);
085        if (docType != null) {
086            description = docType.getDescription();
087        }
088
089        return description;
090    }
091
092    /**
093     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getMaintainableClass(java.lang.String)
094     */
095    @Deprecated
096    public Class getMaintainableClass(String docTypeName) {
097        Class maintainableClass = null;
098
099        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    /**
153     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDefaultExistenceChecks(java.lang.Class)
154     */
155    public Collection getDefaultExistenceChecks(Class businessObjectClass) {
156        return getDefaultExistenceChecks(getDocumentTypeName(businessObjectClass));
157    }
158
159    /**
160     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getDefaultExistenceChecks(java.lang.String)
161     */
162    public Collection getDefaultExistenceChecks(String docTypeName) {
163
164        Collection defaultExistenceChecks = null;
165
166        MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
167        if (entry != null) {
168            defaultExistenceChecks = entry.getDefaultExistenceChecks();
169        }
170
171        return defaultExistenceChecks;
172    }
173
174    /**
175     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getLockingKeys(java.lang.String)
176     */
177    public List getLockingKeys(String docTypeName) {
178        List lockingKeys = null;
179
180        MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
181        if (entry != null) {
182            lockingKeys = entry.getLockingKeyFieldNames();
183        }
184
185        return lockingKeys;
186    }
187
188    /**
189     * @param dataDictionaryService
190     */
191    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
192        this.dataDictionaryService = dataDictionaryService;
193    }
194
195    /**
196     * @return
197     */
198    public DataDictionary getDataDictionary() {
199        return this.dataDictionaryService.getDataDictionary();
200    }
201
202    /**
203     * @param docTypeName
204     * @return
205     */
206    public MaintenanceDocumentEntry getMaintenanceDocumentEntry(String docTypeName) {
207        if (StringUtils.isBlank(docTypeName)) {
208            throw new IllegalArgumentException("invalid (blank) docTypeName");
209        }
210
211        MaintenanceDocumentEntry entry = (MaintenanceDocumentEntry)getDataDictionary().getDocumentEntry(docTypeName);
212        return entry;
213    }
214
215    private MaintenanceDocumentEntry getMaintenanceDocumentEntry(Class businessObjectClass) {
216        if (businessObjectClass == null) {
217            throw new IllegalArgumentException("invalid (blank) dataObjectClass");
218        }
219
220        // Treat KRAD documents as non existing (KULRICE-9909)
221        org.kuali.rice.krad.datadictionary.MaintenanceDocumentEntry
222                entry = getDataDictionary().getMaintenanceDocumentEntryForBusinessObjectClass(businessObjectClass);
223        return (entry instanceof MaintenanceDocumentEntry) ? (MaintenanceDocumentEntry) entry : null;
224    }
225
226    /**
227     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getFieldDefaultValue(java.lang.Class, java.lang.String)
228     */
229    public String getFieldDefaultValue(Class boClass, String fieldName) {
230
231        // input parameter validation
232        if (boClass == null) {
233            throw new IllegalArgumentException("The boClass parameter value specified was " + "null.  A valid class representing the boClass must " + "be specified.");
234        }
235
236        // call the twin
237        return getFieldDefaultValue(getDocumentTypeName(boClass), fieldName);
238    }
239
240    /**
241     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getFieldDefaultValue(java.lang.String, java.lang.String)
242     */
243    public String getFieldDefaultValue(String docTypeName, String fieldName) {
244
245        // input parameter validation
246        if (StringUtils.isBlank(docTypeName)) {
247            throw new IllegalArgumentException("The docTypeName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the docTypeName must " + "be specified.");
248        }
249        if (StringUtils.isBlank(fieldName)) {
250            throw new IllegalArgumentException("The fieldName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the fieldName must " + "be specified.");
251        }
252
253        // walk through the sections
254        List sections = getMaintainableSections(docTypeName);
255        for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
256            MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
257
258            // walk through the fields
259            Collection fields = section.getMaintainableItems();
260            String defaultValue = getFieldDefaultValue(fields, fieldName);
261            // need to keep trying sections until a match is found
262            if (defaultValue != null) {
263                return defaultValue;
264            }
265        }
266        return null;
267    }
268
269    private String getFieldDefaultValue(Collection maintainableFields, String fieldName) {
270        for (Iterator iterator = maintainableFields.iterator(); iterator.hasNext();) {
271            MaintainableItemDefinition item = (MaintainableItemDefinition) iterator.next();
272            // only check fields...skip subcollections
273            if (item instanceof MaintainableFieldDefinition) {
274
275                MaintainableFieldDefinition field = (MaintainableFieldDefinition) item;
276
277                // if the field name matches
278                if (field.getName().endsWith(fieldName)) {
279
280                    // preferentially take the raw default value
281                    if (StringUtils.isNotBlank(field.getDefaultValue())) {
282                        return field.getDefaultValue();
283                    }
284
285                    // take the valuefinder
286                    else if (field.getDefaultValueFinderClass() != null) {
287
288                        // attempt to get an instance of the defaultValueFinderClass
289                        ValueFinder valueFinder = null;
290                        try {
291                            valueFinder = (ValueFinder) field.getDefaultValueFinderClass().newInstance();
292                        }
293                        catch (Exception e) {
294                            LOG.info("Exception obtaining valueFinder for collection field default value", e);
295                            valueFinder = null;
296                        }
297
298                        // get the value
299                        if (valueFinder != null) {
300                            return valueFinder.getValue();
301                        }
302                    }
303                    // if we found the field, but no default anything, then we're done
304                    else {
305                        return null;
306                    }
307                }
308            }
309        }
310        return null;
311    }
312
313    /**
314     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getCollectionFieldDefaultValue(java.lang.String,
315     *      java.lang.String, java.lang.String)
316     */
317    public String getCollectionFieldDefaultValue(String docTypeName, String collectionName, String fieldName) {
318        // input parameter validation
319        if (StringUtils.isBlank(docTypeName)) {
320            throw new IllegalArgumentException("The docTypeName parameter value specified was blank, whitespace, or null.  A valid string representing the docTypeName must be specified.");
321        }
322        if (StringUtils.isBlank(fieldName)) {
323            throw new IllegalArgumentException("The fieldName parameter value specified was blank, whitespace, or null.  A valid string representing the fieldName must be specified.");
324        }
325        if (StringUtils.isBlank(collectionName)) {
326            throw new IllegalArgumentException("The collectionName parameter value specified was null.  A valid string representing the collectionName must be specified.");
327        }
328
329        MaintainableCollectionDefinition coll = getMaintainableCollection(docTypeName, collectionName);
330        if (coll != null) {
331            Collection collectionFields = coll.getMaintainableFields();
332            return getFieldDefaultValue(collectionFields, fieldName);
333        }
334        return null;
335    }
336
337    /**
338     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsCopy(MaintenanceDocument)
339     */
340    public Boolean getAllowsCopy(MaintenanceDocument document) {
341        Boolean allowsCopy = Boolean.FALSE;
342        if (document != null && document.getNewMaintainableObject() != null) {
343            MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(document.getNewMaintainableObject().getBoClass());
344            if (entry != null) {
345                allowsCopy = Boolean.valueOf(entry.getAllowsCopy());
346            }
347        }
348
349        return allowsCopy;
350    }
351
352    /**
353     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsNewOrCopy(java.lang.String)
354     */
355    public Boolean getAllowsNewOrCopy(String docTypeName) {
356        Boolean allowsNewOrCopy = Boolean.FALSE;
357
358        if (docTypeName != null) {
359            MaintenanceDocumentEntry entry = getMaintenanceDocumentEntry(docTypeName);
360            if (entry != null) {
361                allowsNewOrCopy = Boolean.valueOf(entry.getAllowsNewOrCopy());
362            }
363        }
364
365        return allowsNewOrCopy;
366    }
367
368    public MaintainableItemDefinition getMaintainableItem(String docTypeName, String itemName) {
369        // input parameter validation
370        if (StringUtils.isBlank(docTypeName)) {
371            throw new IllegalArgumentException("The docTypeName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the docTypeName must " + "be specified.");
372        }
373        if (StringUtils.isBlank(itemName)) {
374            throw new IllegalArgumentException("The itemName parameter value specified was  " + "blank, whitespace, or null.  A valid string representing the itemName must " + "be specified.");
375        }
376
377        // split name for subcollections
378        String[] subItems = {};
379        subItems = StringUtils.split(itemName, ".");
380
381
382        // walk through the sections
383        List sections = getMaintainableSections(docTypeName);
384        for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
385            MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
386
387            // walk through the fields
388            Collection fields = section.getMaintainableItems();
389            for (Iterator fieldIterator = fields.iterator(); fieldIterator.hasNext();) {
390                MaintainableItemDefinition item = (MaintainableItemDefinition) fieldIterator.next();
391
392                if (item.getName().equals(itemName)) {
393                    return item;
394                }
395                // if collection check to see if it has sub collections
396                // for now this only allows 1 level (i.e. a.b) it should be expanded at some point
397                if (item instanceof MaintainableCollectionDefinition) {
398                    MaintainableCollectionDefinition col = (MaintainableCollectionDefinition) item;
399                    if ((subItems.length > 1) && (StringUtils.equals(col.getName(), subItems[0]))) {
400                        for (Iterator<MaintainableCollectionDefinition> colIterator = col.getMaintainableCollections().iterator(); colIterator.hasNext();) {
401                            MaintainableCollectionDefinition subCol = (MaintainableCollectionDefinition) colIterator.next();
402                            if (subCol.getName().equals(subItems[1])) {
403                                return subCol;
404                            }
405                        }
406                    }
407                }
408            }
409        }
410        return null;
411    }
412
413    public MaintainableFieldDefinition getMaintainableField(String docTypeName, String fieldName) {
414        MaintainableItemDefinition item = getMaintainableItem(docTypeName, fieldName);
415        if (item != null && item instanceof MaintainableFieldDefinition) {
416            return (MaintainableFieldDefinition) item;
417        }
418        return null;
419    }
420
421    public MaintainableCollectionDefinition getMaintainableCollection(String docTypeName, String collectionName) {
422        // strip brackets as they are not needed to get to collection class
423        // Like the other subcollections changes this currently only supports one sub level
424        if (StringUtils.contains(collectionName, "[")) {
425            collectionName = StringUtils.substringBefore(collectionName, "[") + StringUtils.substringAfter(collectionName, "]");
426        }
427        MaintainableItemDefinition item = getMaintainableItem(docTypeName, collectionName);
428        if (item != null && item instanceof MaintainableCollectionDefinition) {
429            return (MaintainableCollectionDefinition) item;
430        }
431        return null;
432    }
433
434    public Class getCollectionBusinessObjectClass(String docTypeName, String collectionName) {
435        MaintainableCollectionDefinition coll = getMaintainableCollection(docTypeName, collectionName);
436        if (coll != null) {
437            return coll.getBusinessObjectClass();
438        }
439        return null;
440    }
441
442    public List<MaintainableCollectionDefinition> getMaintainableCollections(String docTypeName) {
443        ArrayList<MaintainableCollectionDefinition> collections = new ArrayList<MaintainableCollectionDefinition>();
444
445        // walk through the sections
446        List sections = getMaintainableSections(docTypeName);
447        for (Iterator sectionIterator = sections.iterator(); sectionIterator.hasNext();) {
448            MaintainableSectionDefinition section = (MaintainableSectionDefinition) sectionIterator.next();
449
450            // walk through the fields
451            Collection fields = section.getMaintainableItems();
452            for (Iterator fieldIterator = fields.iterator(); fieldIterator.hasNext();) {
453                MaintainableItemDefinition item = (MaintainableItemDefinition) fieldIterator.next();
454
455                if (item instanceof MaintainableCollectionDefinition) {
456                    collections.add((MaintainableCollectionDefinition) item);
457                    // collections.addAll( getMaintainableCollections( (MaintainableCollectionDefinition)item ) );
458                }
459            }
460        }
461
462        return collections;
463    }
464
465    public List<MaintainableCollectionDefinition> getMaintainableCollections(MaintainableCollectionDefinition parentCollection) {
466        ArrayList<MaintainableCollectionDefinition> collections = new ArrayList<MaintainableCollectionDefinition>();
467
468        // walk through the sections
469        Collection<MaintainableCollectionDefinition> colls = parentCollection.getMaintainableCollections();
470        for (MaintainableCollectionDefinition coll : colls) {
471            collections.add(coll);
472            collections.addAll(getMaintainableCollections(coll));
473        }
474
475        return collections;
476    }
477
478    /**
479     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#validateMaintenanceRequiredFields(org.kuali.rice.krad.maintenance.MaintenanceDocument)
480     */
481    public void validateMaintenanceRequiredFields(MaintenanceDocument document) {
482        Maintainable newMaintainableObject = document.getNewMaintainableObject();
483        if (newMaintainableObject == null) {
484            LOG.error("New maintainable is null");
485            throw new RuntimeException("New maintainable is null");
486        }
487
488        List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections(getDocumentTypeName(newMaintainableObject.getBoClass()));
489        for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
490            for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
491                // validate fields
492                if (maintainableItemDefinition instanceof MaintainableFieldDefinition) {
493                    validateMaintainableFieldRequiredFields((MaintainableFieldDefinition) maintainableItemDefinition, newMaintainableObject.getBusinessObject(), maintainableItemDefinition.getName());
494                }
495                // validate collections
496                else if (maintainableItemDefinition instanceof MaintainableCollectionDefinition) {
497                    validateMaintainableCollectionsRequiredFields(newMaintainableObject.getBusinessObject(), (MaintainableCollectionDefinition) maintainableItemDefinition);
498                }
499            }
500        }
501    }
502
503    /**
504     * generates error message if a field is marked as required but is not filled in
505     * 
506     * @param maintainableFieldDefinition
507     * @param businessObject
508     * @param fieldName
509     */
510    private void validateMaintainableFieldRequiredFields(MaintainableFieldDefinition maintainableFieldDefinition, PersistableBusinessObject businessObject, String fieldName) {
511
512        if (StringUtils.isBlank(fieldName)) {
513            throw new IllegalArgumentException("invalid fieldName parameter.");
514        }
515        // if required check we have a value for this field
516        if (maintainableFieldDefinition.isRequired() && !maintainableFieldDefinition.isUnconditionallyReadOnly() ) {
517            try {
518                Object obj = ObjectUtils.getNestedValue(businessObject, fieldName);
519
520                if (obj == null || StringUtils.isBlank(obj.toString())) {
521                    String attributeLabel = dataDictionaryService.getAttributeLabel(businessObject.getClass(), fieldName);
522                    String shortLabel = dataDictionaryService.getAttributeShortLabel(businessObject.getClass(), fieldName);
523                    GlobalVariables.getMessageMap().putError(fieldName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel + " (" + shortLabel + ")" );
524                } else if ( fieldName.endsWith(".principalName") ) {
525                    // special handling to catch when the principalName is not really a valid user
526                    // pull the Person object and test the entity ID.  If that's null, then this
527                    // is just a shell user instance and does not represent a true user
528                    // the main principalId property on the main object would be null at this point
529                    // but it is also unconditionally read only and not tested - checking that would
530                    // require checking the relationships and be more complex than we want to get here
531                    String personProperty = ObjectUtils.getNestedAttributePrefix(fieldName); 
532                    if ( StringUtils.isNotBlank(personProperty) ) {
533                        if ( StringUtils.isBlank( (String)ObjectUtils.getNestedValue(businessObject, personProperty+".entityId") ) ) {
534                            String attributeLabel = dataDictionaryService.getAttributeLabel(businessObject.getClass(), fieldName);
535                            GlobalVariables.getMessageMap().putError(fieldName, RiceKeyConstants.ERROR_EXISTENCE, attributeLabel );
536                        }
537                    }
538                }
539            } catch( Exception ex ) {
540                LOG.error( "unable to read property during doc required field checks", ex );
541            }
542        }
543    }
544
545    
546    private MaintainableCollectionDefinition getCollectionDefinition( String docTypeName, String collectionName ) {
547        String currentCollection = collectionName;
548        String nestedCollections = "";
549        if (StringUtils.contains(collectionName, "[")) {
550                // strip off any array indexes
551            currentCollection = StringUtils.substringBefore( collectionName, "[" );
552            nestedCollections = StringUtils.substringAfter( collectionName, "." );
553        }
554        
555        // loop over all sections to find this collection
556        List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections( docTypeName );
557        for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
558            for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
559                if (maintainableItemDefinition instanceof MaintainableCollectionDefinition && maintainableItemDefinition.getName().equals( currentCollection ) ) {
560                    if ( StringUtils.isBlank( nestedCollections ) ) {
561                        return (MaintainableCollectionDefinition) maintainableItemDefinition;
562                    } 
563                    
564                    return getCollectionDefinition( (MaintainableCollectionDefinition)maintainableItemDefinition, nestedCollections );
565                }
566            }
567        }
568        
569        return null;
570    }
571
572    private MaintainableCollectionDefinition getCollectionDefinition( MaintainableCollectionDefinition collectionDef, String collectionName ) {
573        String currentCollection = collectionName;
574        String nestedCollections = "";
575        if (StringUtils.contains(collectionName, "[")) {
576                // strip off any array indexes
577            currentCollection = StringUtils.substringBefore( collectionName, "[" );
578            nestedCollections = StringUtils.substringAfter( collectionName, "." );
579        }
580        
581        // loop over all nested collections
582        for (MaintainableCollectionDefinition maintainableCollectionDefinition : collectionDef.getMaintainableCollections()) {
583            if ( maintainableCollectionDefinition.getName().equals( currentCollection ) ) {
584                if ( StringUtils.isBlank( nestedCollections ) ) {
585                    return maintainableCollectionDefinition;
586                } 
587                return getCollectionDefinition( maintainableCollectionDefinition, nestedCollections );
588            }
589        }
590        
591        return null;
592    }
593    
594    public void validateMaintainableCollectionsAddLineRequiredFields(MaintenanceDocument document, PersistableBusinessObject businessObject, String collectionName ) {
595        MaintainableCollectionDefinition def = getCollectionDefinition( getDocumentTypeName(businessObject.getClass()), collectionName );
596        if ( def != null ) {
597            validateMaintainableCollectionsAddLineRequiredFields( document, businessObject, collectionName, def, 0);
598        }
599    }
600    /**
601     * calls code to generate error messages if maintainableFields within any collections or sub-collections are marked as required
602     * 
603     * @param document
604     * @param businessObject
605     * @param collectionName
606     * @param maintainableCollectionDefinition
607     * @param depth
608     */
609    private void validateMaintainableCollectionsAddLineRequiredFields(MaintenanceDocument document, PersistableBusinessObject businessObject, String collectionName, MaintainableCollectionDefinition maintainableCollectionDefinition, int depth) {
610        if ( depth == 0 ) {
611            GlobalVariables.getMessageMap().addToErrorPath("add");
612        }
613        // validate required fields on fields withing collection definition
614        PersistableBusinessObject element = document.getNewMaintainableObject().getNewCollectionLine( collectionName );
615        GlobalVariables.getMessageMap().addToErrorPath(collectionName);
616        for (MaintainableFieldDefinition maintainableFieldDefinition : maintainableCollectionDefinition.getMaintainableFields()) {
617            final String fieldName = maintainableFieldDefinition.getName();
618            validateMaintainableFieldRequiredFields(maintainableFieldDefinition, element, fieldName);
619            
620        }
621
622        GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
623        if ( depth == 0 ) {
624            GlobalVariables.getMessageMap().removeFromErrorPath("add");
625        }
626    }
627
628    /**
629     * calls code to generate error messages if maintainableFields within any collections or sub-collections are marked as required
630     * 
631     * @param businessObject
632     * @param maintainableCollectionDefinition
633     */
634    private void validateMaintainableCollectionsRequiredFields(PersistableBusinessObject businessObject, MaintainableCollectionDefinition maintainableCollectionDefinition) {
635        final String collectionName = maintainableCollectionDefinition.getName();
636
637        // validate required fields on fields withing collection definition
638        Collection<PersistableBusinessObject> collection = (Collection) ObjectUtils.getPropertyValue(businessObject, collectionName);
639        if (collection != null && !collection.isEmpty()) {
640            for (MaintainableFieldDefinition maintainableFieldDefinition : maintainableCollectionDefinition.getMaintainableFields()) {
641                int pos = 0;
642                final String fieldName = maintainableFieldDefinition.getName();
643                for (PersistableBusinessObject element : collection) {
644                    String parentName = collectionName + "[" + (pos++) + "]";
645                    GlobalVariables.getMessageMap().addToErrorPath(parentName);
646                    validateMaintainableFieldRequiredFields(maintainableFieldDefinition, element, fieldName);
647                    GlobalVariables.getMessageMap().removeFromErrorPath(parentName);
648                }
649            }
650
651            // recursivley validate required fields on subcollections
652            GlobalVariables.getMessageMap().addToErrorPath(collectionName);
653            for (MaintainableCollectionDefinition nestedMaintainableCollectionDefinition : maintainableCollectionDefinition.getMaintainableCollections()) {
654                for (PersistableBusinessObject element : collection) {
655                    validateMaintainableCollectionsRequiredFields(element, nestedMaintainableCollectionDefinition);
656                }
657            }
658            GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
659        }
660    }
661    
662    /**
663     * default implementation checks for duplicats based on keys of objects only
664     * 
665     * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#validateMaintainableCollectionsForDuplicateEntries(org.kuali.rice.krad.maintenance.MaintenanceDocument)
666     */
667    public void validateMaintainableCollectionsForDuplicateEntries(MaintenanceDocument document) {
668        Maintainable newMaintainableObject = document.getNewMaintainableObject();
669        if (newMaintainableObject == null) {
670            LOG.error("New maintainable is null");
671            throw new RuntimeException("New maintainable is null");
672        }
673
674        List<MaintainableSectionDefinition> maintainableSectionDefinitions = getMaintainableSections(getDocumentTypeName(newMaintainableObject.getBoClass()));
675        for (MaintainableSectionDefinition maintainableSectionDefinition : maintainableSectionDefinitions) {
676            for (MaintainableItemDefinition maintainableItemDefinition : maintainableSectionDefinition.getMaintainableItems()) {
677                // validate collections
678                if (maintainableItemDefinition instanceof MaintainableCollectionDefinition) {
679                    validateMaintainableCollectionsForDuplicateEntries(newMaintainableObject.getBusinessObject(), (MaintainableCollectionDefinition) maintainableItemDefinition);
680                }
681            }
682        }
683    }
684
685    /**
686     * recursivly checks collections for duplicate entries based on key valuse
687     * 
688     * @param businessObject
689     * @param maintainableCollectionDefinition
690     */
691    private void validateMaintainableCollectionsForDuplicateEntries(PersistableBusinessObject businessObject, MaintainableCollectionDefinition maintainableCollectionDefinition) {
692        final String collectionName = maintainableCollectionDefinition.getName();
693
694        if (maintainableCollectionDefinition.dissallowDuplicateKey()) {
695            final Class maintainableBusinessObjectClass = businessObject.getClass();
696            // validate that no duplicates based on keys exist
697            Collection<PersistableBusinessObject> collection = (Collection) ObjectUtils.getPropertyValue(businessObject, collectionName);
698            if (collection != null && !collection.isEmpty()) {
699                final String propertyName = maintainableCollectionDefinition.getAttributeToHighlightOnDuplicateKey();
700                // get collection label for dd
701                final String label = dataDictionaryService.getCollectionLabel(maintainableBusinessObjectClass, collectionName);
702                final String shortLabel = dataDictionaryService.getCollectionShortLabel(maintainableBusinessObjectClass, collectionName);
703                int pos = 0;
704                for (PersistableBusinessObject element : collection) {
705                    String pathToElement = collectionName + "[" + (pos++) + "]";
706                    if (ObjectUtils.countObjectsWithIdentitcalKey(collection, element) > 1) {
707                        GlobalVariables.getMessageMap().addToErrorPath(pathToElement);
708                        GlobalVariables.getMessageMap().putError(propertyName, RiceKeyConstants.ERROR_DUPLICATE_ELEMENT, new String[] { label, shortLabel });
709                        GlobalVariables.getMessageMap().removeFromErrorPath(pathToElement);
710                    }
711                }
712
713                // recursivley check for duplicate entries on subcollections
714                GlobalVariables.getMessageMap().addToErrorPath(collectionName);
715                for (MaintainableCollectionDefinition nestedMaintainableCollectionDefinition : maintainableCollectionDefinition.getMaintainableCollections()) {
716                    for (PersistableBusinessObject element : collection) {
717                        validateMaintainableCollectionsForDuplicateEntries(element, nestedMaintainableCollectionDefinition);
718                    }
719                }
720                GlobalVariables.getMessageMap().removeFromErrorPath(collectionName);
721
722            }
723        }
724    }
725       
726        /**
727         * for issue KULRice 3072
728         * 
729         * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getgetPreserveLockingKeysOnCopy(java.lang.Class)
730         */
731        public boolean getPreserveLockingKeysOnCopy(Class businessObjectClass) {
732
733                boolean preserveLockingKeysOnCopy = false;
734
735                MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
736                
737                if (docEntry != null) {
738                        preserveLockingKeysOnCopy = docEntry.getPreserveLockingKeysOnCopy();
739                }
740                
741                return preserveLockingKeysOnCopy;
742        }
743
744        /**
745         * for isue KULRice 3070
746         * 
747         * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsRecordDeletion(java.lang.Class)
748         */
749        public Boolean getAllowsRecordDeletion(Class businessObjectClass) {
750                
751                Boolean allowsRecordDeletion = Boolean.FALSE;
752
753                MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
754                
755                if (docEntry != null) {
756                        allowsRecordDeletion = Boolean.valueOf(docEntry.getAllowsRecordDeletion());
757                }
758                
759                return allowsRecordDeletion;
760        }
761
762        /**
763         *  for issue KULRice3070, see if need delete button
764         * 
765         * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#getAllowsRecordDeletion(org.kuali.rice.krad.maintenance.MaintenanceDocument)
766         */
767        public Boolean getAllowsRecordDeletion(MaintenanceDocument document) {
768        return document != null ? this.getAllowsRecordDeletion(document.getNewMaintainableObject().getBoClass()) : Boolean.FALSE;
769        }
770
771        /**
772         * @see org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService#translateCodes(java.lang.Class)
773         */
774        public Boolean translateCodes(Class businessObjectClass) {
775                boolean translateCodes = false;
776
777                MaintenanceDocumentEntry docEntry = getMaintenanceDocumentEntry(businessObjectClass);
778
779                if (docEntry != null) {
780                        translateCodes = docEntry.isTranslateCodes();
781                }
782
783                return translateCodes;
784        }
785
786}