001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.kns.maintenance;
017    
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.kuali.rice.core.api.CoreApiServiceLocator;
021    import org.kuali.rice.core.api.encryption.EncryptionService;
022    import org.kuali.rice.core.web.format.FormatException;
023    import org.kuali.rice.kim.api.identity.PersonService;
024    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
025    import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
026    import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
027    import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
028    import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
029    import org.kuali.rice.kns.document.MaintenanceDocument;
030    import org.kuali.rice.kns.document.authorization.FieldRestriction;
031    import org.kuali.rice.kns.document.authorization.MaintenanceDocumentPresentationController;
032    import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
033    import org.kuali.rice.kns.lookup.LookupUtils;
034    import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
035    import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
036    import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
037    import org.kuali.rice.kns.service.DocumentHelperService;
038    import org.kuali.rice.kns.service.KNSServiceLocator;
039    import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
040    import org.kuali.rice.kns.util.FieldUtils;
041    import org.kuali.rice.kns.util.InactiveRecordsHidingUtils;
042    import org.kuali.rice.kns.util.MaintenanceUtils;
043    import org.kuali.rice.kns.web.ui.Section;
044    import org.kuali.rice.kns.web.ui.SectionBridge;
045    import org.kuali.rice.krad.bo.BusinessObject;
046    import org.kuali.rice.krad.bo.DataObjectRelationship;
047    import org.kuali.rice.krad.bo.PersistableBusinessObject;
048    import org.kuali.rice.krad.datadictionary.AttributeSecurity;
049    import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
050    import org.kuali.rice.krad.maintenance.MaintainableImpl;
051    import org.kuali.rice.krad.service.DataDictionaryService;
052    import org.kuali.rice.krad.service.KRADServiceLocator;
053    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
054    import org.kuali.rice.krad.service.ModuleService;
055    import org.kuali.rice.krad.service.PersistenceStructureService;
056    import org.kuali.rice.krad.util.GlobalVariables;
057    import org.kuali.rice.krad.util.KRADConstants;
058    import org.kuali.rice.krad.util.KRADPropertyConstants;
059    import org.kuali.rice.krad.util.MessageMap;
060    import org.kuali.rice.krad.util.ObjectUtils;
061    import org.kuali.rice.krad.valuefinder.ValueFinder;
062    
063    import java.beans.PropertyDescriptor;
064    import java.lang.reflect.InvocationTargetException;
065    import java.security.GeneralSecurityException;
066    import java.util.ArrayList;
067    import java.util.Collection;
068    import java.util.HashMap;
069    import java.util.HashSet;
070    import java.util.Iterator;
071    import java.util.List;
072    import java.util.Map;
073    import java.util.Set;
074    
075    /**
076     * Base Maintainable class to hold things common to all maintainables.
077     */
078    @Deprecated
079    public class KualiMaintainableImpl extends MaintainableImpl implements Maintainable {
080            private static final long serialVersionUID = 4814145799502207182L;
081    
082            private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiMaintainableImpl.class);
083    
084            protected PersistableBusinessObject businessObject;
085    
086            protected Map<String, PersistableBusinessObject> newCollectionLines = new HashMap<String, PersistableBusinessObject>();
087            protected Map<String, Boolean> inactiveRecordDisplay = new HashMap<String, Boolean>();
088    
089        // TODO: rename once 'newCollectionLines' is removed
090        protected Set<String> newCollectionLineNames = new HashSet<String>();
091    
092            protected transient BusinessObjectDictionaryService businessObjectDictionaryService;
093            protected transient PersonService personService;
094            protected transient BusinessObjectMetaDataService businessObjectMetaDataService;
095            protected transient BusinessObjectAuthorizationService businessObjectAuthorizationService;
096            protected transient DocumentHelperService documentHelperService;
097        protected transient MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
098    
099            /**
100             * Default empty constructor
101             */
102            public KualiMaintainableImpl() {
103            super();
104            }
105    
106            /**
107             * Constructor which initializes the business object to be maintained.
108             *
109             * @param businessObject
110             */
111            public KualiMaintainableImpl(PersistableBusinessObject businessObject) {
112                    super();
113                    this.businessObject = businessObject;
114                    super.setDataObject(businessObject);
115            }
116    
117            /**
118             * @see Maintainable#populateBusinessObject(java.util.Map,
119             *      org.kuali.rice.krad.maintenance.MaintenanceDocument, String)
120             */
121            @SuppressWarnings("unchecked")
122            public Map populateBusinessObject(Map<String, String> fieldValues, MaintenanceDocument maintenanceDocument,
123                            String methodToCall) {
124                    fieldValues = decryptEncryptedData(fieldValues, maintenanceDocument, methodToCall);
125                    Map newFieldValues = null;
126                    newFieldValues = getPersonService().resolvePrincipalNamesToPrincipalIds(getBusinessObject(), fieldValues);
127    
128                    Map cachedValues = FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), newFieldValues);
129                    performForceUpperCase(newFieldValues);
130    
131                    return cachedValues;
132            }
133    
134            /**
135             * Special hidden parameters are set on the maintenance jsp starting with a
136             * prefix that tells us which fields have been encrypted. This field finds
137             * the those parameters in the map, whose value gives us the property name
138             * that has an encrypted value. We then need to decrypt the value in the Map
139             * before the business object is populated.
140             * 
141             * @param fieldValues
142             *            - possibly with encrypted values
143             * @return Map fieldValues - with no encrypted values
144             */
145            protected Map<String, String> decryptEncryptedData(Map<String, String> fieldValues,
146                            MaintenanceDocument maintenanceDocument, String methodToCall) {
147                    try {
148                            MaintenanceDocumentRestrictions auths = KNSServiceLocator.getBusinessObjectAuthorizationService()
149                                            .getMaintenanceDocumentRestrictions(maintenanceDocument,
150                                                            GlobalVariables.getUserSession().getPerson());
151                            for (Iterator<String> iter = fieldValues.keySet().iterator(); iter.hasNext();) {
152                                    String fieldName = iter.next();
153                                    String fieldValue = (String) fieldValues.get(fieldName);
154    
155                                    if (fieldValue != null && !"".equals(fieldValue)
156                                                    && fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
157                                            if (shouldFieldBeEncrypted(maintenanceDocument, fieldName, auths, methodToCall)) {
158                                                    String encryptedValue = fieldValue;
159    
160                                                    // take off the postfix
161                                                    encryptedValue = StringUtils.stripEnd(encryptedValue, EncryptionService.ENCRYPTION_POST_PREFIX);
162                            if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
163                                String decryptedValue = getEncryptionService().decrypt(encryptedValue);
164    
165                                                        fieldValues.put(fieldName, decryptedValue);
166                            }
167                                            }
168                                            else
169                                                    throw new RuntimeException("The field value for field name " + fieldName
170                                                                    + " should not be encrypted.");
171                                    }
172                                    else if (fieldValue != null && !"".equals(fieldValue)
173                                        && auths.hasRestriction(fieldName)
174                                        && shouldFieldBeEncrypted(maintenanceDocument, fieldName, auths, methodToCall))
175                                                throw new RuntimeException("The field value for field name " + fieldName + " should be encrypted.");
176                            }
177                    }
178                    catch (GeneralSecurityException e) {
179                            throw new RuntimeException("Unable to decrypt secure data: " + e.getMessage());
180                    }
181    
182                    return fieldValues;
183            }
184    
185            /**
186             * Determines whether the field in a request should be encrypted. This base
187             * implementation does not work for properties of collection elements.
188             * 
189             * This base implementation will only return true if the maintenance
190             * document is being refreshed after a lookup (i.e. methodToCall is
191             * "refresh") and the data dictionary-based attribute security definition
192             * has any restriction defined, whether the user would be authorized to view
193             * the field. This assumes that only fields returned from a lookup should be
194             * encrypted in a request. If the user otherwise has no permissions to
195             * view/edit the field, then a request parameter will not be sent back to
196             * the server for population.
197             * 
198             * @param maintenanceDocument
199             * @param fieldName
200             * @param auths
201             * @param methodToCall
202             * @return
203             */
204            protected boolean shouldFieldBeEncrypted(MaintenanceDocument maintenanceDocument, String fieldName,
205                            MaintenanceDocumentRestrictions auths, String methodToCall) {
206                    if ("refresh".equals(methodToCall) && fieldName != null) {
207                            fieldName = fieldName.replaceAll("\\[[0-9]*+\\]", "");
208                            fieldName = fieldName.replaceAll("^add\\.", "");
209                            Map<String, AttributeSecurity> fieldNameToAttributeSecurityMap = MaintenanceUtils
210                                            .retrievePropertyPathToAttributeSecurityMappings(getDocumentTypeName());
211                            AttributeSecurity attributeSecurity = fieldNameToAttributeSecurityMap.get(fieldName);
212                            return attributeSecurity != null && attributeSecurity.hasRestrictionThatRemovesValueFromUI();
213                    }
214                    else {
215                            return false;
216                    }
217            }
218    
219            /**
220             * Calls method to get all the core sections for the business object defined
221             * in the data dictionary. Then determines if the bo has custom attributes,
222             * if so builds a custom attribute section and adds to the section list.
223             * 
224             * @return List of org.kuali.ui.Section objects
225             */
226            public List getSections(MaintenanceDocument document, Maintainable oldMaintainable) {
227                    List<Section> sections = new ArrayList<Section>();
228                    sections.addAll(getCoreSections(document, oldMaintainable));
229    
230                    return sections;
231            }
232    
233            /**
234             * Gets list of maintenance sections built from the data dictionary. If the
235             * section contains maintenance fields, construct Row/Field UI objects and
236             * place under Section UI. If section contains a maintenance collection,
237             * call method to build a Section UI which contains rows of Container
238             * Fields.
239             * 
240             * @return List of org.kuali.ui.Section objects
241             */
242            public List<Section> getCoreSections(MaintenanceDocument document, Maintainable oldMaintainable) {
243                    List<Section> sections = new ArrayList<Section>();
244                    MaintenanceDocumentRestrictions maintenanceRestrictions = KNSServiceLocator
245                                    .getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(document,
246                                                    GlobalVariables.getUserSession().getPerson());
247    
248                    MaintenanceDocumentPresentationController maintenanceDocumentPresentationController = (MaintenanceDocumentPresentationController) getDocumentHelperService()
249                                    .getDocumentPresentationController(document);
250                    Set<String> conditionallyRequiredFields = maintenanceDocumentPresentationController
251                                    .getConditionallyRequiredPropertyNames(document);
252    
253                    List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
254                                    .getMaintainableSections(getDocumentTypeName());
255                    try {
256                            // iterate through section definitions and create Section UI object
257                            for (Iterator iter = sectionDefinitions.iterator(); iter.hasNext();) {
258                                    MaintainableSectionDefinition maintSectionDef = (MaintainableSectionDefinition) iter.next();
259    
260                                    List<String> displayedFieldNames = new ArrayList<String>();
261                                    if (!maintenanceRestrictions.isHiddenSectionId(maintSectionDef.getId())) {
262    
263                                            for (Iterator iter2 = maintSectionDef.getMaintainableItems().iterator(); iter2.hasNext();) {
264                                                    MaintainableItemDefinition item = (MaintainableItemDefinition) iter2.next();
265                                                    if (item instanceof MaintainableFieldDefinition) {
266                                                            displayedFieldNames.add(((MaintainableFieldDefinition) item).getName());
267                                                    }
268                                            }
269    
270                                            Section section = SectionBridge
271                                .toSection(maintSectionDef, getBusinessObject(), this, oldMaintainable,
272                                        getMaintenanceAction(), displayedFieldNames, conditionallyRequiredFields);
273                                            if (maintenanceRestrictions.isReadOnlySectionId(maintSectionDef.getId())) {
274                                                    section.setReadOnly(true);
275                                            }
276    
277                                            // add to section list
278                                            sections.add(section);
279                                    }
280    
281                            }
282    
283                    }
284                    catch (InstantiationException e) {
285                            LOG.error("Unable to create instance of object class" + e.getMessage());
286                            throw new RuntimeException("Unable to create instance of object class" + e.getMessage());
287                    }
288                    catch (IllegalAccessException e) {
289                            LOG.error("Unable to create instance of object class" + e.getMessage());
290                            throw new RuntimeException("Unable to create instance of object class" + e.getMessage());
291                    }
292    
293                    return sections;
294            }
295    
296    
297        /**
298             * 
299             * @see Maintainable#saveBusinessObject()
300             */
301            public void saveBusinessObject() {
302                    getBusinessObjectService().linkAndSave(businessObject);
303            }
304    
305        /**
306         * delegate this call to KNS' {@link org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject()} in order
307         * to support KNS maintainables.
308         */
309        @Override
310        public void saveDataObject() {
311            saveBusinessObject();
312        }
313    
314        /**
315             * Retrieves title for maintenance document from data dictionary
316             */
317            public String getMaintainableTitle() {
318                    return getMaintenanceDocumentDictionaryService().getMaintenanceLabel(getDocumentTypeName());
319            }
320    
321        @Override
322        public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
323        }
324    
325            public boolean isBoNotesEnabled() {
326            return getDataObjectMetaDataService().areNotesSupported(getDataObjectClass());
327            }
328    
329        /**
330         * Overriding to call old (KNS) name of the method
331         */
332        @Override
333        public boolean isNotesEnabled() {
334            return isBoNotesEnabled();
335        }
336    
337        /**
338             * @see Maintainable#refresh(java.lang.String,
339             *      java.util.Map) Impls will be needed if custom action is needed on
340             *      refresh.
341             */
342            public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
343                    String referencesToRefresh = (String) fieldValues.get(KRADConstants.REFERENCES_TO_REFRESH);
344                    refreshReferences(referencesToRefresh);
345            }
346    
347            protected void refreshReferences(String referencesToRefresh) {
348                    PersistenceStructureService persistenceStructureService = getPersistenceStructureService();
349                    if (StringUtils.isNotBlank(referencesToRefresh)) {
350                            String[] references = StringUtils.split(referencesToRefresh, KRADConstants.REFERENCES_TO_REFRESH_SEPARATOR);
351                            for (String reference : references) {
352                                    if (StringUtils.isNotBlank(reference)) {
353                                            if (reference.startsWith(KRADConstants.ADD_PREFIX + ".")) {
354                                                    // add one for the period
355                                                    reference = reference.substring(KRADConstants.ADD_PREFIX.length() + 1);
356    
357                                                    String boToRefreshName = StringUtils.substringBeforeLast(reference, ".");
358                                                    String propertyToRefresh = StringUtils.substringAfterLast(reference, ".");
359                                                    if (StringUtils.isNotBlank(propertyToRefresh)) {
360                                                            PersistableBusinessObject addlineBO = getNewCollectionLine(boToRefreshName);
361                                                            Class addlineBOClass = addlineBO.getClass();
362                                                            if (LOG.isDebugEnabled()) {
363                                                                    LOG.debug("Refresh this \"new\"/add object for the collections:  " + reference);
364                                                            }
365                                                            if (persistenceStructureService.hasReference(addlineBOClass, propertyToRefresh)
366                                                                            || persistenceStructureService.hasCollection(addlineBOClass, propertyToRefresh)) {
367                                                                    addlineBO.refreshReferenceObject(propertyToRefresh);
368                                                            }
369                                                            else {
370                                                                    if (getDataDictionaryService().hasRelationship(addlineBOClass.getName(),
371                                                                                    propertyToRefresh)) {
372                                                                            // a DD mapping, try to go straight to the
373                                                                            // object and refresh it there
374                                                                            Object possibleBO = ObjectUtils.getPropertyValue(addlineBO, propertyToRefresh);
375                                                                            if (possibleBO != null && possibleBO instanceof PersistableBusinessObject) {
376                                                                                    ((PersistableBusinessObject) possibleBO).refresh();
377                                                                            }
378                                                                    }
379                                                            }
380                                                    }
381                                                    else {
382                                                            LOG.error("Error: unable to refresh this \"new\"/add object for the collections:  "
383                                                                            + reference);
384                                                    }
385                                            }
386                                            else if (ObjectUtils.isNestedAttribute(reference)) {
387                                                    Object nestedObject = ObjectUtils.getNestedValue(getBusinessObject(),
388                                                                    ObjectUtils.getNestedAttributePrefix(reference));
389                            if (nestedObject == null) {
390                                LOG.warn("Unable to refresh ReferenceToRefresh (" + reference + ")  was found to be null");
391                            }
392                            else {
393                                if (nestedObject instanceof Collection) {
394                                    // do nothing, probably because it's not really a
395                                    // collection reference but a relationship defined
396                                    // in the DD for a collections lookup
397                                    // this part will need to be rewritten when the DD
398                                    // supports true collection references
399                                }
400                                else if (nestedObject instanceof PersistableBusinessObject) {
401                                    String propertyToRefresh = ObjectUtils.getNestedAttributePrimitive(reference);
402                                    if (persistenceStructureService.hasReference(nestedObject.getClass(), propertyToRefresh)
403                                            || persistenceStructureService.hasCollection(nestedObject.getClass(),
404                                                    propertyToRefresh)) {
405                                        if (LOG.isDebugEnabled()) {
406                                            LOG.debug("Refeshing " + ObjectUtils.getNestedAttributePrefix(reference) + " "
407                                                    + ObjectUtils.getNestedAttributePrimitive(reference));
408                                        }
409                                        ((PersistableBusinessObject) nestedObject).refreshReferenceObject(propertyToRefresh);
410                                    }
411                                    else {
412                                        // a DD mapping, try to go straight to the
413                                        // object and refresh it there
414                                        Object possibleBO = ObjectUtils.getPropertyValue(nestedObject, propertyToRefresh);
415                                        if (possibleBO != null && possibleBO instanceof PersistableBusinessObject) {
416                                            if (getDataDictionaryService().hasRelationship(possibleBO.getClass().getName(),
417                                                    propertyToRefresh)) {
418                                                ((PersistableBusinessObject) possibleBO).refresh();
419                                            }
420                                        }
421                                    }
422                                }
423                                else {
424                                    LOG.warn("Expected that a referenceToRefresh ("
425                                            + reference
426                                            + ")  would be a PersistableBusinessObject or Collection, but instead, it was of class "
427                                            + nestedObject.getClass().getName());
428                                }
429                            }
430                                            }
431                                            else {
432                                                    if (LOG.isDebugEnabled()) {
433                                                            LOG.debug("Refreshing " + reference);
434                                                    }
435                                                    if (persistenceStructureService.hasReference(getDataObjectClass(), reference)
436                                                                    || persistenceStructureService.hasCollection(getDataObjectClass(), reference)) {
437                                                            getBusinessObject().refreshReferenceObject(reference);
438                                                    }
439                                                    else {
440                                                            if (getDataDictionaryService().hasRelationship(getBusinessObject().getClass().getName(),
441                                                                            reference)) {
442                                                                    // a DD mapping, try to go straight to the
443                                                                    // object and refresh it there
444                                                                    Object possibleRelationship = ObjectUtils.getPropertyValue(getBusinessObject(),
445                                                                                    reference);
446                                                                    if (possibleRelationship != null) {
447                                                                            if (possibleRelationship instanceof PersistableBusinessObject) {
448                                                                                    ((PersistableBusinessObject) possibleRelationship).refresh();
449                                                                            }
450                                                                            else if (possibleRelationship instanceof Collection) {
451                                                                                    // do nothing, probably because it's not
452                                                                                    // really a collection reference but a
453                                                                                    // relationship defined in the DD for a
454                                                                                    // collections lookup
455                                                                                    // this part will need to be rewritten
456                                                                                    // when the DD supports true collection
457                                                                                    // references
458                                                                            }
459                                                                            else {
460                                                                                    LOG.warn("Expected that a referenceToRefresh ("
461                                                                                                    + reference
462                                                                                                    + ")  would be a PersistableBusinessObject or Collection, but instead, it was of class "
463                                                                                                    + possibleRelationship.getClass().getName());
464                                                                            }
465                                                                    }
466                                                            }
467                                                    }
468                                            }
469                                    }
470                            }
471                    }
472            }
473    
474            public void addMultipleValueLookupResults(MaintenanceDocument document, String collectionName,
475                            Collection<PersistableBusinessObject> rawValues, boolean needsBlank, PersistableBusinessObject bo) {
476                    Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(bo, collectionName);
477                    String docTypeName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
478    
479                    List<String> duplicateIdentifierFieldsFromDataDictionary = getDuplicateIdentifierFieldsFromDataDictionary(
480                                    docTypeName, collectionName);
481    
482                    List<String> existingIdentifierList = getMultiValueIdentifierList(maintCollection,
483                                    duplicateIdentifierFieldsFromDataDictionary);
484    
485                    Class collectionClass = getMaintenanceDocumentDictionaryService().getCollectionBusinessObjectClass(docTypeName,
486                                    collectionName);
487    
488                    List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
489                                    .getMaintainableSections(docTypeName);
490                    Map<String, String> template = MaintenanceUtils.generateMultipleValueLookupBOTemplate(sections, collectionName);
491                    try {
492                            for (PersistableBusinessObject nextBo : rawValues) {
493                                    PersistableBusinessObject templatedBo;
494                                    if (needsBlank) {
495                                            templatedBo = (PersistableBusinessObject) collectionClass.newInstance();
496                                    }
497                                    else {
498                                            // templatedBo = (PersistableBusinessObject)
499                                            // ObjectUtils.createHybridBusinessObject(collectionClass,
500                                            // nextBo, template);
501                                            try {
502                                                    ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService()
503                                                                    .getResponsibleModuleService(collectionClass);
504                                                    if (moduleService != null && moduleService.isExternalizable(collectionClass))
505                                                            templatedBo = (PersistableBusinessObject) moduleService
506                                                                            .createNewObjectFromExternalizableClass(collectionClass);
507                                                    else
508                                                            templatedBo = (PersistableBusinessObject) collectionClass.newInstance();
509                                            }
510                                            catch (Exception e) {
511                                                    throw new RuntimeException("Cannot instantiate " + collectionClass.getName(), e);
512                                            }
513                                            // first set the default values specified in the DD
514                                            setNewCollectionLineDefaultValues(collectionName, templatedBo);
515                                            // then set the values from the multiple value lookup result
516                                            ObjectUtils.createHybridBusinessObject(templatedBo, nextBo, template);
517    
518                                            prepareBusinessObjectForAdditionFromMultipleValueLookup(collectionName, templatedBo);
519                                    }
520                                    templatedBo.setNewCollectionRecord(true);
521    
522                                    if (!hasBusinessObjectExisted(templatedBo, existingIdentifierList,
523                                                    duplicateIdentifierFieldsFromDataDictionary)) {
524                                            maintCollection.add(templatedBo);
525    
526                                    }
527                            }
528                    }
529                    catch (Exception e) {
530                            LOG.error("Unable to add multiple value lookup results " + e.getMessage());
531                            throw new RuntimeException("Unable to add multiple value lookup results " + e.getMessage());
532                    }
533            }
534    
535            /**
536             * This method is to retrieve a List of fields which are specified in the
537             * maintenance document data dictionary as the
538             * duplicateIdentificationFields. This List is used to determine whether the
539             * new entry being added to the collection is a duplicate entry and if so,
540             * we should not add the new entry to the existing collection
541             * 
542             * @param docTypeName
543             * @param collectionName
544             */
545            public List<String> getDuplicateIdentifierFieldsFromDataDictionary(String docTypeName, String collectionName) {
546                    List<String> duplicateIdentifierFieldNames = new ArrayList<String>();
547                    MaintainableCollectionDefinition collDef = getMaintenanceDocumentDictionaryService().getMaintainableCollection(
548                                    docTypeName, collectionName);
549                    Collection<MaintainableFieldDefinition> fieldDef = collDef.getDuplicateIdentificationFields();
550                    for (MaintainableFieldDefinition eachFieldDef : fieldDef) {
551                            duplicateIdentifierFieldNames.add(eachFieldDef.getName());
552                    }
553                    return duplicateIdentifierFieldNames;
554            }
555    
556            public List<String> getMultiValueIdentifierList(Collection maintCollection, List<String> duplicateIdentifierFields) {
557                    List<String> identifierList = new ArrayList<String>();
558                    for (PersistableBusinessObject bo : (Collection<PersistableBusinessObject>) maintCollection) {
559                            String uniqueIdentifier = new String();
560                            for (String identifierField : duplicateIdentifierFields) {
561                                    uniqueIdentifier = uniqueIdentifier + identifierField + "-"
562                                                    + ObjectUtils.getPropertyValue(bo, identifierField);
563                            }
564                            if (StringUtils.isNotEmpty(uniqueIdentifier)) {
565                                    identifierList.add(uniqueIdentifier);
566                            }
567                    }
568                    return identifierList;
569            }
570    
571            public boolean hasBusinessObjectExisted(BusinessObject bo, List<String> existingIdentifierList,
572                            List<String> duplicateIdentifierFields) {
573                    String uniqueIdentifier = new String();
574                    for (String identifierField : duplicateIdentifierFields) {
575                            uniqueIdentifier = uniqueIdentifier + identifierField + "-"
576                                            + ObjectUtils.getPropertyValue(bo, identifierField);
577                    }
578                    if (existingIdentifierList.contains(uniqueIdentifier)) {
579                            return true;
580                    }
581                    else {
582                            return false;
583                    }
584            }
585    
586            public void prepareBusinessObjectForAdditionFromMultipleValueLookup(String collectionName, BusinessObject bo) {
587                    // default implementation does nothing
588            }
589    
590            /**
591             * Set the new collection records back to true so they can be deleted (copy
592             * should act like new)
593             * 
594             * @see KualiMaintainableImpl#processAfterCopy()
595             */
596            public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
597                    try {
598                            ObjectUtils.setObjectPropertyDeep(businessObject, KRADPropertyConstants.NEW_COLLECTION_RECORD,
599                                            boolean.class, true, 2);
600                    } catch (Exception e) {
601                            LOG.error("unable to set newCollectionRecord property: " + e.getMessage(), e);
602                            throw new RuntimeException("unable to set newCollectionRecord property: " + e.getMessage(), e);
603                    }
604            }
605    
606        @Override
607        public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
608    
609        }
610    
611        @Override
612        public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
613    
614        }
615    
616        @Override
617        public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
618    
619        }
620    
621        @Override
622        public void setDataObject(Object object) {
623            super.setDataObject(object);
624            
625            if(object instanceof PersistableBusinessObject) {
626                this.businessObject = (PersistableBusinessObject)object;
627            }
628        }
629    
630        @Override
631        public String getDocumentTitle(MaintenanceDocument document) {
632            return super.getDocumentTitle((org.kuali.rice.krad.maintenance.MaintenanceDocument) document);
633        }
634    
635        /**
636         * @return Returns the instance of the business object being maintained.
637         */
638        public PersistableBusinessObject getBusinessObject() {
639            return businessObject;
640        }
641    
642        /**
643         * @param businessObject
644         *            Sets the instance of a business object that will be
645         *            maintained.
646         */
647        public void setBusinessObject(PersistableBusinessObject businessObject) {
648            this.businessObject = businessObject;
649            setDataObject(businessObject);
650        }
651    
652            /**
653             * @return Returns the boClass.
654             */
655            public Class getBoClass() {
656                    return super.getDataObjectClass();
657            }
658    
659            /**
660             * @param boClass
661             *            The boClass to set.
662             */
663            public void setBoClass(Class boClass) {
664                    setDataObjectClass(boClass);
665            }
666    
667            /**
668             * 
669             * @see Maintainable#setGenerateDefaultValues()
670             */
671            public void setGenerateDefaultValues(String docTypeName) {
672                    List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
673                                    .getMaintainableSections(docTypeName);
674                    Map defaultValues = new HashMap();
675    
676                    try {
677                            // iterate through section definitions
678                            for (Iterator iter = sectionDefinitions.iterator(); iter.hasNext();) {
679    
680                                    MaintainableSectionDefinition maintSectionDef = (MaintainableSectionDefinition) iter.next();
681                                    Collection maintItems = maintSectionDef.getMaintainableItems();
682                                    for (Iterator iterator = maintItems.iterator(); iterator.hasNext();) {
683                                            MaintainableItemDefinition item = (MaintainableItemDefinition) iterator.next();
684    
685                                            if (item instanceof MaintainableFieldDefinition) {
686                                                    MaintainableFieldDefinition maintainableFieldDefinition = (MaintainableFieldDefinition) item;
687    
688                                                    String defaultValue = maintainableFieldDefinition.getDefaultValue();
689                                                    if (defaultValue != null) {
690                                                            if (defaultValue.equals("true")) {
691                                                                    defaultValue = "Yes";
692                                                            }
693                                                            else if (defaultValue.equals("false")) {
694                                                                    defaultValue = "No";
695                                                            }
696                                                    }
697    
698                                                    Class defaultValueFinderClass = maintainableFieldDefinition.getDefaultValueFinderClass();
699                                                    if (defaultValueFinderClass != null) {
700                                                            defaultValue = ((ValueFinder) defaultValueFinderClass.newInstance()).getValue();
701    
702                                                    }
703                                                    if (defaultValue != null) {
704                                                            defaultValues.put(item.getName(), defaultValue);
705                                                    }
706                                            }
707                                    }
708                            }
709                            Map cachedValues = FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), defaultValues);
710                    }
711                    catch (Exception e) {
712                            LOG.error("Unable to set default value " + e.getMessage(), e);
713                            throw new RuntimeException("Unable to set default value" + e.getMessage(), e);
714                    }
715    
716            }
717    
718            /**
719             * 
720             * @see Maintainable#setGenerateBlankRequiredValues()
721             */
722            public void setGenerateBlankRequiredValues(String docTypeName) {
723                    try {
724                            List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
725                                            .getMaintainableSections(docTypeName);
726                            Map<String, String> defaultValues = new HashMap<String, String>();
727    
728                            for (MaintainableSectionDefinition maintSectionDef : sectionDefinitions) {
729                                    for (MaintainableItemDefinition item : maintSectionDef.getMaintainableItems()) {
730                                            if (item instanceof MaintainableFieldDefinition) {
731                                                    MaintainableFieldDefinition maintainableFieldDefinition = (MaintainableFieldDefinition) item;
732                                                    if (maintainableFieldDefinition.isRequired()
733                                                                    && maintainableFieldDefinition.isUnconditionallyReadOnly()) {
734                                                            Object currPropVal = ObjectUtils.getPropertyValue(this.getBusinessObject(), item.getName());
735                                                            if (currPropVal == null
736                                                                            || (currPropVal instanceof String && StringUtils.isBlank((String) currPropVal))) {
737                                                                    Class<? extends ValueFinder> defaultValueFinderClass = maintainableFieldDefinition
738                                                                                    .getDefaultValueFinderClass();
739                                                                    if (defaultValueFinderClass != null) {
740                                                                            String defaultValue = defaultValueFinderClass.newInstance().getValue();
741                                                                            if (defaultValue != null) {
742                                                                                    defaultValues.put(item.getName(), defaultValue);
743                                                                            }
744                                                                    }
745                                                            }
746                                                    }
747                                            }
748                                    }
749                            }
750                            FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), defaultValues);
751                    }
752                    catch (Exception e) {
753                            LOG.error("Unable to set blank required value " + e.getMessage(), e);
754                            throw new RuntimeException("Unable to set blank required value" + e.getMessage(), e);
755                    }
756            }
757    
758            @Deprecated
759            public void processAfterAddLine(String colName, Class colClass) {
760            }
761    
762            /**
763             * @see Maintainable#processBeforeAddLine(java.lang.String,
764             *      java.lang.Class, org.kuali.rice.krad.bo.BusinessObject)
765             */
766            public void processBeforeAddLine(String colName, Class colClass, BusinessObject addBO) {
767            }
768    
769            /**
770             * @see Maintainable#getShowInactiveRecords(java.lang.String)
771             */
772            public boolean getShowInactiveRecords(String collectionName) {
773                    return InactiveRecordsHidingUtils.getShowInactiveRecords(inactiveRecordDisplay, collectionName);
774            }
775    
776            /**
777             * @see Maintainable#setShowInactiveRecords(java.lang.String,
778             *      boolean)
779             */
780            public void setShowInactiveRecords(String collectionName, boolean showInactive) {
781                    InactiveRecordsHidingUtils.setShowInactiveRecords(inactiveRecordDisplay, collectionName, showInactive);
782            }
783    
784            /**
785             * @return the inactiveRecordDisplay
786             */
787            public Map<String, Boolean> getInactiveRecordDisplay() {
788                    return inactiveRecordDisplay;
789            }
790    
791            public void addNewLineToCollection(String collectionName) {
792    
793                    if (LOG.isDebugEnabled()) {
794                            LOG.debug("addNewLineToCollection( " + collectionName + " )");
795                    }
796                    // get the new line from the map
797                    PersistableBusinessObject addLine = newCollectionLines.get(collectionName);
798                    if (addLine != null) {
799                            // mark the isNewCollectionRecord so the option to delete this line
800                            // will be presented
801                            addLine.setNewCollectionRecord(true);
802    
803                            // if we add back add button on sub collection of an "add line" we
804                            // may need extra logic here
805    
806                            // get the collection from the business object
807                            Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(getBusinessObject(), collectionName);
808                            // add the line to the collection
809                            maintCollection.add(addLine);
810                            // refresh parent object since attributes could of changed prior to
811                            // user clicking add
812    
813                            String referencesToRefresh = LookupUtils
814                                            .convertReferencesToSelectCollectionToString(getAllRefreshableReferences(getBusinessObject()
815                                                            .getClass()));
816                            if (LOG.isInfoEnabled()) {
817                                    LOG.info("References to refresh for adding line to collection " + collectionName + ": "
818                                                    + referencesToRefresh);
819                            }
820                            refreshReferences(referencesToRefresh);
821                    }
822    
823                    initNewCollectionLine(collectionName);
824    
825            }
826    
827            public PersistableBusinessObject getNewCollectionLine(String collectionName) {
828                    if (LOG.isDebugEnabled()) {
829                            // LOG.debug( this + ") getNewCollectionLine( " + collectionName +
830                            // ")", new Exception( "tracing exception") );
831                            LOG.debug("newCollectionLines: " + newCollectionLines);
832                    }
833                    PersistableBusinessObject addLine = newCollectionLines.get(collectionName);
834                    if (addLine == null) {
835                            addLine = initNewCollectionLine(collectionName);
836                    }
837                    return addLine;
838            }
839    
840            public PersistableBusinessObject initNewCollectionLine(String collectionName) {
841                    if (LOG.isDebugEnabled()) {
842                            LOG.debug("initNewCollectionLine( " + collectionName + " )");
843                    }
844                    // try to get the object from the map
845                    // BusinessObject addLine = newCollectionLines.get( collectionName );
846                    // if ( addLine == null ) {
847                    // if not there, instantiate a new one
848                    PersistableBusinessObject addLine;
849                    try {
850                            addLine = (PersistableBusinessObject) getMaintenanceDocumentDictionaryService()
851                                            .getCollectionBusinessObjectClass(getDocumentTypeName(), collectionName).newInstance();
852                    }
853                    catch (Exception ex) {
854                            LOG.error("unable to instantiate new collection line", ex);
855                            throw new RuntimeException("unable to instantiate new collection line", ex);
856                    }
857                    // and add it to the map
858                    newCollectionLines.put(collectionName, addLine);
859                    // }
860                    // set its values to the defaults
861                    setNewCollectionLineDefaultValues(collectionName, addLine);
862                    return addLine;
863            }
864    
865            /**
866             * 
867             * @see Maintainable#populateNewCollectionLines(java.util.Map)
868             */
869            public Map<String, String> populateNewCollectionLines(Map<String, String> fieldValues,
870                            MaintenanceDocument maintenanceDocument, String methodToCall) {
871                    if (LOG.isDebugEnabled()) {
872                            LOG.debug("populateNewCollectionLines: " + fieldValues);
873                    }
874                    fieldValues = decryptEncryptedData(fieldValues, maintenanceDocument, methodToCall);
875    
876                    Map<String, String> cachedValues = new HashMap<String, String>();
877    
878                    // loop over all collections with an enabled add line
879                    List<MaintainableCollectionDefinition> collections = getMaintenanceDocumentDictionaryService()
880                                    .getMaintainableCollections(getDocumentTypeName());
881    
882                    for (MaintainableCollectionDefinition coll : collections) {
883                            // get the collection name
884                            String collName = coll.getName();
885                            if (LOG.isDebugEnabled()) {
886                                    LOG.debug("checking for collection: " + collName);
887                            }
888                            // build a map for that collection
889                            Map<String, String> collectionValues = new HashMap<String, String>();
890                            Map<String, String> subCollectionValues = new HashMap<String, String>();
891                            // loop over the collection, extracting entries with a matching
892                            // prefix
893                            for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
894                                    String key = entry.getKey();
895                                    if (key.startsWith(collName)) {
896                                            String subStrKey = key.substring(collName.length() + 1);
897                                            // check for subcoll w/ '[', set collName to propername and
898                                            // put in correct name for collection values (i.e. strip
899                                            // '*[x].')
900                                            if (key.contains("[")) {
901    
902                                                    // collName = StringUtils.substringBeforeLast(key,"[");
903    
904                                                    // need the whole thing if subcollection
905                                                    subCollectionValues.put(key, entry.getValue());
906                                            }
907                                            else {
908                                                    collectionValues.put(subStrKey, entry.getValue());
909                                            }
910                                    }
911                            }
912                            // send those values to the business object
913                            if (LOG.isDebugEnabled()) {
914                                    LOG.debug("values for collection: " + collectionValues);
915                            }
916                            cachedValues.putAll(FieldUtils.populateBusinessObjectFromMap(getNewCollectionLine(collName),
917                                            collectionValues, KRADConstants.MAINTENANCE_ADD_PREFIX + collName + "."));
918                            performFieldForceUpperCase(getNewCollectionLine(collName), collectionValues);
919    
920                            cachedValues.putAll(populateNewSubCollectionLines(coll, subCollectionValues));
921                    }
922    
923                    // cachedValues.putAll( FieldUtils.populateBusinessObjectFromMap( ))
924                    return cachedValues;
925            }
926    
927            /*
928             * Yes, I think this could be merged with the above code - I'm leaving it
929             * separate until I figure out of there are any issues which would reqire
930             * that it be separated.
931             */
932            protected Map populateNewSubCollectionLines(MaintainableCollectionDefinition parentCollection, Map fieldValues) {
933                    if (LOG.isDebugEnabled()) {
934                            LOG.debug("populateNewSubCollectionLines: " + fieldValues);
935                    }
936                    Map cachedValues = new HashMap();
937    
938                    for (MaintainableCollectionDefinition coll : parentCollection.getMaintainableCollections()) {
939                            // get the collection name
940                            String collName = coll.getName();
941    
942                            if (LOG.isDebugEnabled()) {
943                                    LOG.debug("checking for sub collection: " + collName);
944                            }
945                            Map<String, String> parents = new HashMap<String, String>();
946                            // get parents from list
947                            for (Object entry : fieldValues.entrySet()) {
948                                    String key = (String) ((Map.Entry) entry).getKey();
949                                    if (key.contains(collName)) {
950                                            parents.put(StringUtils.substringBefore(key, "."), "");
951                                    }
952                            }
953    
954                            for (String parent : parents.keySet()) {
955                                    // build a map for that collection
956                                    Map<String, Object> collectionValues = new HashMap<String, Object>();
957                                    // loop over the collection, extracting entries with a matching
958                                    // prefix
959                                    for (Object entry : fieldValues.entrySet()) {
960                                            String key = (String) ((Map.Entry) entry).getKey();
961                                            if (key.contains(parent)) {
962                                                    String substr = StringUtils.substringAfterLast(key, ".");
963                                                    collectionValues.put(substr, ((Map.Entry) entry).getValue());
964                                            }
965                                    }
966                                    // send those values to the business object
967                                    if (LOG.isDebugEnabled()) {
968                                            LOG.debug("values for sub collection: " + collectionValues);
969                                    }
970                                    GlobalVariables.getMessageMap().addToErrorPath(
971                                                    KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName);
972                                    cachedValues.putAll(FieldUtils.populateBusinessObjectFromMap(getNewCollectionLine(parent + "."
973                                                    + collName), collectionValues, KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName
974                                                    + "."));
975                                    performFieldForceUpperCase(getNewCollectionLine(parent + "." + collName), collectionValues);
976                                    GlobalVariables.getMessageMap().removeFromErrorPath(
977                                                    KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName);
978                            }
979    
980                            cachedValues.putAll(populateNewSubCollectionLines(coll, fieldValues));
981                    }
982    
983                    return cachedValues;
984            }
985    
986            public Collection<String> getAffectedReferencesFromLookup(BusinessObject baseBO, String attributeName,
987                            String collectionPrefix) {
988                    PersistenceStructureService pss = getPersistenceStructureService();
989                    String nestedBOPrefix = "";
990                    if (ObjectUtils.isNestedAttribute(attributeName)) {
991                            // if we're performing a lookup on a nested attribute, we need to
992                            // use the nested BO all the way down the chain
993                            nestedBOPrefix = ObjectUtils.getNestedAttributePrefix(attributeName);
994    
995                            // renormalize the base BO so that the attribute name is not nested
996                            // anymore
997                            Class reference = ObjectUtils.getPropertyType(baseBO, nestedBOPrefix, pss);
998                            if (!(PersistableBusinessObject.class.isAssignableFrom(reference))) {
999                                    return new ArrayList<String>();
1000                            }
1001    
1002                            try {
1003                                    baseBO = (PersistableBusinessObject) reference.newInstance();
1004                            }
1005                            catch (InstantiationException e) {
1006                                    LOG.error(e);
1007                            }
1008                            catch (IllegalAccessException e) {
1009                                    LOG.error(e);
1010                            }
1011                            attributeName = ObjectUtils.getNestedAttributePrimitive(attributeName);
1012                    }
1013    
1014                    if (baseBO == null) {
1015                            return new ArrayList<String>();
1016                    }
1017    
1018                    Map<String, Class> referenceNameToClassFromPSS = LookupUtils.getPrimitiveReference(baseBO, attributeName);
1019                    if (referenceNameToClassFromPSS.size() > 1) {
1020                            LOG.error("LookupUtils.getPrimitiveReference return results should only have at most one element");
1021                    }
1022    
1023                    BusinessObjectMetaDataService businessObjectMetaDataService = getBusinessObjectMetaDataService();
1024                    DataObjectRelationship relationship = businessObjectMetaDataService.getBusinessObjectRelationship(baseBO,
1025                                    attributeName);
1026                    if (relationship == null) {
1027                            return new ArrayList<String>();
1028                    }
1029    
1030                    Map<String, String> fkToPkMappings = relationship.getParentToChildReferences();
1031    
1032                    Collection<String> affectedReferences = generateAllAffectedReferences(baseBO.getClass(), fkToPkMappings,
1033                                    nestedBOPrefix, collectionPrefix);
1034                    if (LOG.isDebugEnabled()) {
1035                            LOG.debug("References affected by a lookup on BO attribute \"" + collectionPrefix + nestedBOPrefix + "."
1036                                            + attributeName + ": " + affectedReferences);
1037                    }
1038    
1039                    return affectedReferences;
1040            }
1041    
1042            protected boolean isRelationshipRefreshable(Class boClass, String relationshipName) {
1043                    if (getPersistenceStructureService().isPersistable(boClass)) {
1044                            if (getPersistenceStructureService().hasCollection(boClass, relationshipName)) {
1045                                    return !getPersistenceStructureService().isCollectionUpdatable(boClass, relationshipName);
1046                            }
1047                            else if (getPersistenceStructureService().hasReference(boClass, relationshipName)) {
1048                                    return !getPersistenceStructureService().isReferenceUpdatable(boClass, relationshipName);
1049                            }
1050                            // else, assume that the relationship is defined in the DD
1051                    }
1052    
1053                    return true;
1054            }
1055    
1056            protected Collection<String> generateAllAffectedReferences(Class boClass, Map<String, String> fkToPkMappings,
1057                            String nestedBOPrefix, String collectionPrefix) {
1058                    Set<String> allAffectedReferences = new HashSet<String>();
1059                    DataDictionaryService dataDictionaryService = getDataDictionaryService();
1060                    PersistenceStructureService pss = getPersistenceStructureService();
1061    
1062                    collectionPrefix = StringUtils.isBlank(collectionPrefix) ? "" : collectionPrefix;
1063    
1064                    // retrieve the attributes that are affected by a lookup on
1065                    // attributeName.
1066                    Collection<String> attributeReferenceFKAttributes = fkToPkMappings.keySet();
1067    
1068                    // a lookup on an attribute may cause other attributes to be updated
1069                    // (e.g. account code lookup would also affect chart code)
1070                    // build a list of all affected FK values via mapKeyFields above, and
1071                    // for each FK, see if there are any non-updatable references with that
1072                    // FK
1073    
1074                    // deal with regular simple references (<reference-descriptor>s in OJB)
1075                    for (String fkAttribute : attributeReferenceFKAttributes) {
1076                            for (String affectedReference : pss.getReferencesForForeignKey(boClass, fkAttribute).keySet()) {
1077                                    if (isRelationshipRefreshable(boClass, affectedReference)) {
1078                                            if (StringUtils.isBlank(nestedBOPrefix)) {
1079                                                    allAffectedReferences.add(collectionPrefix + affectedReference);
1080                                            }
1081                                            else {
1082                                                    allAffectedReferences.add(collectionPrefix + nestedBOPrefix + "." + affectedReference);
1083                                            }
1084                                    }
1085                            }
1086                    }
1087    
1088                    // now with collection references (<collection-descriptor>s in OJB)
1089                    for (String collectionName : pss.listCollectionObjectTypes(boClass).keySet()) {
1090                            if (isRelationshipRefreshable(boClass, collectionName)) {
1091                                    Map<String, String> keyMappingsForCollection = pss.getInverseForeignKeysForCollection(boClass,
1092                                                    collectionName);
1093                                    for (String collectionForeignKey : keyMappingsForCollection.keySet()) {
1094                                            if (attributeReferenceFKAttributes.contains(collectionForeignKey)) {
1095                                                    if (StringUtils.isBlank(nestedBOPrefix)) {
1096                                                            allAffectedReferences.add(collectionPrefix + collectionName);
1097                                                    }
1098                                                    else {
1099                                                            allAffectedReferences.add(collectionPrefix + nestedBOPrefix + "." + collectionName);
1100                                                    }
1101                                            }
1102                                    }
1103                            }
1104                    }
1105    
1106                    // now use the DD to compute more affected references
1107                    List<String> ddDefinedRelationships = dataDictionaryService.getRelationshipNames(boClass.getName());
1108                    for (String ddRelationship : ddDefinedRelationships) {
1109                            // note that this map is PK (key/target) => FK (value/source)
1110                            Map<String, String> referencePKtoFKmappings = dataDictionaryService.getRelationshipAttributeMap(
1111                                            boClass.getName(), ddRelationship);
1112                            for (String sourceAttribute : referencePKtoFKmappings.values()) {
1113                                    // the sourceAttribute is the FK pointing to the target
1114                                    // attribute (PK)
1115                                    if (attributeReferenceFKAttributes.contains(sourceAttribute)) {
1116                                            for (String affectedReference : dataDictionaryService.getRelationshipEntriesForSourceAttribute(
1117                                                            boClass.getName(), sourceAttribute)) {
1118                                                    if (isRelationshipRefreshable(boClass, ddRelationship)) {
1119                                                            if (StringUtils.isBlank(nestedBOPrefix)) {
1120                                                                    allAffectedReferences.add(affectedReference);
1121                                                            }
1122                                                            else {
1123                                                                    allAffectedReferences.add(nestedBOPrefix + "." + affectedReference);
1124                                                            }
1125                                                    }
1126                                            }
1127                                    }
1128                            }
1129                    }
1130                    return allAffectedReferences;
1131            }
1132    
1133            protected Collection<String> getAllRefreshableReferences(Class boClass) {
1134                    HashSet<String> references = new HashSet<String>();
1135                    for (String referenceName : getPersistenceStructureService().listReferenceObjectFields(boClass).keySet()) {
1136                            if (isRelationshipRefreshable(boClass, referenceName)) {
1137                                    references.add(referenceName);
1138                            }
1139                    }
1140                    for (String collectionName : getPersistenceStructureService().listCollectionObjectTypes(boClass).keySet()) {
1141                            if (isRelationshipRefreshable(boClass, collectionName)) {
1142                                    references.add(collectionName);
1143                            }
1144                    }
1145                    for (String relationshipName : getDataDictionaryService().getRelationshipNames(boClass.getName())) {
1146                            if (isRelationshipRefreshable(boClass, relationshipName)) {
1147                                    references.add(relationshipName);
1148                            }
1149                    }
1150                    return references;
1151            }
1152    
1153            protected void setNewCollectionLineDefaultValues(String collectionName, PersistableBusinessObject addLine) {
1154                    PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(addLine);
1155                    for (int i = 0; i < descriptors.length; ++i) {
1156                            PropertyDescriptor propertyDescriptor = descriptors[i];
1157    
1158                            String fieldName = propertyDescriptor.getName();
1159                            Class propertyType = propertyDescriptor.getPropertyType();
1160                            String value = getMaintenanceDocumentDictionaryService().getCollectionFieldDefaultValue(getDocumentTypeName(),
1161                                            collectionName, fieldName);
1162    
1163                            if (value != null) {
1164                                    try {
1165                                            ObjectUtils.setObjectProperty(addLine, fieldName, propertyType, value);
1166                                    }
1167                                    catch (Exception ex) {
1168                                            LOG.error("Unable to set default property of collection object: " + "\nobject: " + addLine
1169                                                            + "\nfieldName=" + fieldName + "\npropertyType=" + propertyType + "\nvalue=" + value, ex);
1170                                    }
1171                            }
1172    
1173                    }
1174            }
1175    
1176            /**
1177             * @see Maintainable#clearBusinessObjectOfRestrictedValues(org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions)
1178             */
1179            public void clearBusinessObjectOfRestrictedValues(MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1180                    List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
1181                                    .getMaintainableSections(getDocumentTypeName());
1182                    for (MaintainableSectionDefinition sectionDefinition : sections) {
1183                            for (MaintainableItemDefinition itemDefinition : sectionDefinition.getMaintainableItems()) {
1184                                    if (itemDefinition instanceof MaintainableFieldDefinition) {
1185                                            clearFieldRestrictedValues("", businessObject, (MaintainableFieldDefinition) itemDefinition,
1186                                                            maintenanceDocumentRestrictions);
1187                                    }
1188                                    else if (itemDefinition instanceof MaintainableCollectionDefinition) {
1189                                            clearCollectionRestrictedValues("", businessObject,
1190                                                            (MaintainableCollectionDefinition) itemDefinition, maintenanceDocumentRestrictions);
1191                                    }
1192                            }
1193                    }
1194            }
1195    
1196            protected void clearCollectionRestrictedValues(String fieldNamePrefix, BusinessObject businessObject,
1197                            MaintainableCollectionDefinition collectionDefinition,
1198                            MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1199                    String collectionName = fieldNamePrefix + collectionDefinition.getName();
1200                    Collection<BusinessObject> collection = (Collection<BusinessObject>) ObjectUtils.getPropertyValue(
1201                                    businessObject, collectionDefinition.getName());
1202    
1203                    if (collection != null) {
1204                            int i = 0;
1205                            // even though it's technically a Collection, we're going to index
1206                            // it like a list
1207                            for (BusinessObject collectionItem : collection) {
1208                                    String collectionItemNamePrefix = collectionName + "[" + i + "].";
1209                                    for (MaintainableCollectionDefinition subCollectionDefinition : collectionDefinition
1210                                                    .getMaintainableCollections()) {
1211                                            clearCollectionRestrictedValues(collectionItemNamePrefix, collectionItem, subCollectionDefinition,
1212                                                            maintenanceDocumentRestrictions);
1213                                    }
1214                                    for (MaintainableFieldDefinition fieldDefinition : collectionDefinition.getMaintainableFields()) {
1215                                            clearFieldRestrictedValues(collectionItemNamePrefix, collectionItem, fieldDefinition,
1216                                                            maintenanceDocumentRestrictions);
1217                                    }
1218                                    i++;
1219                            }
1220                    }
1221            }
1222    
1223            protected void clearFieldRestrictedValues(String fieldNamePrefix, BusinessObject businessObject,
1224                            MaintainableFieldDefinition fieldDefinition, MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1225                    String fieldName = fieldNamePrefix + fieldDefinition.getName();
1226    
1227                    FieldRestriction fieldRestriction = maintenanceDocumentRestrictions.getFieldRestriction(fieldName);
1228                    if (fieldRestriction.isRestricted()) {
1229                            String defaultValue = null;
1230                            if (StringUtils.isNotBlank(fieldDefinition.getDefaultValue())) {
1231                                    defaultValue = fieldDefinition.getDefaultValue();
1232                            }
1233                            else if (fieldDefinition.getDefaultValueFinderClass() != null) {
1234                                    try {
1235                                            defaultValue = ((ValueFinder) fieldDefinition.getDefaultValueFinderClass().newInstance())
1236                                                            .getValue();
1237                                    }
1238                                    catch (Exception e) {
1239                                            defaultValue = null;
1240                                            LOG.error("Error trying to instantiate ValueFinder or to determine ValueFinder for doc type: "
1241                                                            + getDocumentTypeName() + " field name " + fieldDefinition.getName() + " with field prefix: "
1242                                                            + fieldNamePrefix, e);
1243                                    }
1244                            }
1245                            try {
1246                                    ObjectUtils.setObjectProperty(businessObject, fieldDefinition.getName(), defaultValue);
1247                            }
1248                            catch (Exception e) {
1249                                    // throw an exception, because we don't want users to be able to
1250                                    // see the restricted value
1251                                    LOG.error("Unable to clear maintenance document values for field name: " + fieldName
1252                                                    + " default value: " + defaultValue, e);
1253                                    throw new RuntimeException("Unable to clear maintenance document values for field name: " + fieldName,
1254                                                    e);
1255                            }
1256                    }
1257            }
1258    
1259            protected void performForceUpperCase(Map fieldValues) {
1260                    List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
1261                                    .getMaintainableSections(getDocumentTypeName());
1262                    for (MaintainableSectionDefinition sectionDefinition : sections) {
1263                            for (MaintainableItemDefinition itemDefinition : sectionDefinition.getMaintainableItems()) {
1264                                    if (itemDefinition instanceof MaintainableFieldDefinition) {
1265                                            performFieldForceUpperCase("", businessObject, (MaintainableFieldDefinition) itemDefinition,
1266                                                            fieldValues);
1267                                    }
1268                                    else if (itemDefinition instanceof MaintainableCollectionDefinition) {
1269                                            performCollectionForceUpperCase("", businessObject,
1270                                                            (MaintainableCollectionDefinition) itemDefinition, fieldValues);
1271    
1272                                    }
1273                            }
1274                    }
1275            }
1276    
1277            protected void performFieldForceUpperCase(String fieldNamePrefix, BusinessObject bo,
1278                            MaintainableFieldDefinition fieldDefinition, Map fieldValues) {
1279                    MessageMap errorMap = GlobalVariables.getMessageMap();
1280                    String fieldName = fieldDefinition.getName();
1281                    String mapKey = fieldNamePrefix + fieldName;
1282                    if (fieldValues != null && fieldValues.get(mapKey) != null) {
1283                            if (PropertyUtils.isWriteable(bo, fieldName) && ObjectUtils.getNestedValue(bo, fieldName) != null) {
1284    
1285                                    try {
1286                                            Class type = ObjectUtils.easyGetPropertyType(bo, fieldName);
1287                                            // convert to upperCase based on data dictionary
1288                                            Class businessObjectClass = bo.getClass();
1289                                            boolean upperCase = false;
1290                                            try {
1291                                                    upperCase = getDataDictionaryService().getAttributeForceUppercase(businessObjectClass,
1292                                                                    fieldName);
1293                                            }
1294                                            catch (UnknownBusinessClassAttributeException t) {
1295                                                    boolean catchme = true;
1296                                                    // throw t;
1297                                            }
1298    
1299                                            Object fieldValue = ObjectUtils.getNestedValue(bo, fieldName);
1300    
1301                                            if (upperCase && fieldValue instanceof String) {
1302                                                    fieldValue = ((String) fieldValue).toUpperCase();
1303                                            }
1304                                            ObjectUtils.setObjectProperty(bo, fieldName, type, fieldValue);
1305                                    }
1306                                    catch (FormatException e) {
1307                                            errorMap.putError(fieldName, e.getErrorKey(), e.getErrorArgs());
1308                                    }
1309                                    catch (IllegalAccessException e) {
1310                                            LOG.error("unable to populate business object" + e.getMessage());
1311                                            throw new RuntimeException(e.getMessage(), e);
1312                                    }
1313                                    catch (InvocationTargetException e) {
1314                                            LOG.error("unable to populate business object" + e.getMessage());
1315                                            throw new RuntimeException(e.getMessage(), e);
1316                                    }
1317                                    catch (NoSuchMethodException e) {
1318                                            LOG.error("unable to populate business object" + e.getMessage());
1319                                            throw new RuntimeException(e.getMessage(), e);
1320                                    }
1321                            }
1322                    }
1323            }
1324    
1325            protected void performCollectionForceUpperCase(String fieldNamePrefix, BusinessObject bo,
1326                            MaintainableCollectionDefinition collectionDefinition, Map fieldValues) {
1327                    String collectionName = fieldNamePrefix + collectionDefinition.getName();
1328                    Collection<BusinessObject> collection = (Collection<BusinessObject>) ObjectUtils.getPropertyValue(bo,
1329                                    collectionDefinition.getName());
1330                    if (collection != null) {
1331                            int i = 0;
1332                            // even though it's technically a Collection, we're going to index
1333                            // it like a list
1334                            for (BusinessObject collectionItem : collection) {
1335                                    String collectionItemNamePrefix = collectionName + "[" + i + "].";
1336                                    // String collectionItemNamePrefix = "";
1337                                    for (MaintainableFieldDefinition fieldDefinition : collectionDefinition.getMaintainableFields()) {
1338                                            performFieldForceUpperCase(collectionItemNamePrefix, collectionItem, fieldDefinition, fieldValues);
1339                                    }
1340                                    for (MaintainableCollectionDefinition subCollectionDefinition : collectionDefinition
1341                                                    .getMaintainableCollections()) {
1342                                            performCollectionForceUpperCase(collectionItemNamePrefix, collectionItem, subCollectionDefinition,
1343                                                            fieldValues);
1344                                    }
1345                                    i++;
1346                            }
1347                    }
1348            }
1349    
1350            protected void performFieldForceUpperCase(BusinessObject bo, Map fieldValues) {
1351                    MessageMap errorMap = GlobalVariables.getMessageMap();
1352    
1353                    try {
1354                            for (Iterator iter = fieldValues.keySet().iterator(); iter.hasNext();) {
1355                                    String propertyName = (String) iter.next();
1356    
1357                                    if (PropertyUtils.isWriteable(bo, propertyName) && fieldValues.get(propertyName) != null) {
1358                                            // if the field propertyName is a valid property on the bo
1359                                            // class
1360                                            Class type = ObjectUtils.easyGetPropertyType(bo, propertyName);
1361                                            try {
1362                                                    // Keep the convert to upperCase logic here. It will be
1363                                                    // used in populateNewCollectionLines,
1364                                                    // populateNewSubCollectionLines
1365                                                    // convert to upperCase based on data dictionary
1366                                                    Class businessObjectClass = bo.getClass();
1367                                                    boolean upperCase = false;
1368                                                    try {
1369                                                            upperCase = getDataDictionaryService().getAttributeForceUppercase(businessObjectClass,
1370                                                                            propertyName);
1371                                                    }
1372                                                    catch (UnknownBusinessClassAttributeException t) {
1373                                                            boolean catchme = true;
1374                                                            // throw t;
1375                                                    }
1376    
1377                                                    Object fieldValue = fieldValues.get(propertyName);
1378    
1379                                                    if (upperCase && fieldValue instanceof String) {
1380                                                            fieldValue = ((String) fieldValue).toUpperCase();
1381                                                    }
1382                                                    ObjectUtils.setObjectProperty(bo, propertyName, type, fieldValue);
1383                                            }
1384                                            catch (FormatException e) {
1385                                                    errorMap.putError(propertyName, e.getErrorKey(), e.getErrorArgs());
1386                                            }
1387                                    }
1388                            }
1389                    }
1390                    catch (IllegalAccessException e) {
1391                            LOG.error("unable to populate business object" + e.getMessage());
1392                            throw new RuntimeException(e.getMessage(), e);
1393                    }
1394                    catch (InvocationTargetException e) {
1395                            LOG.error("unable to populate business object" + e.getMessage());
1396                            throw new RuntimeException(e.getMessage(), e);
1397                    }
1398                    catch (NoSuchMethodException e) {
1399                            LOG.error("unable to populate business object" + e.getMessage());
1400                            throw new RuntimeException(e.getMessage(), e);
1401                    }
1402    
1403            }
1404    
1405            /**
1406             * By default a maintainable is not external
1407             * 
1408             * @see Maintainable#isExternalBusinessObject()
1409             */
1410            public boolean isExternalBusinessObject() {
1411                    return false;
1412            }
1413    
1414            /**
1415             * @see Maintainable#getExternalBusinessObject()
1416             */
1417            public void prepareBusinessObject(BusinessObject businessObject) {
1418                    // Do nothing by default
1419            }
1420    
1421            // 3070
1422            public void deleteBusinessObject() {
1423                    if (businessObject == null)
1424                            return;
1425    
1426                    KRADServiceLocator.getBusinessObjectService().delete(businessObject);
1427                    businessObject = null;
1428            }
1429    
1430            public boolean isOldBusinessObjectInDocument() {
1431                    return super.isOldDataObjectInDocument();
1432            }
1433    
1434            protected BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
1435                    if (businessObjectDictionaryService == null) {
1436                            businessObjectDictionaryService = KNSServiceLocator.getBusinessObjectDictionaryService();
1437                    }
1438                    return businessObjectDictionaryService;
1439            }
1440    
1441            protected PersonService getPersonService() {
1442                    if (personService == null) {
1443                            personService = KimApiServiceLocator.getPersonService();
1444                    }
1445                    return personService;
1446            }
1447    
1448            protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
1449                    if (businessObjectMetaDataService == null) {
1450                            businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
1451                    }
1452                    return businessObjectMetaDataService;
1453            }
1454    
1455            protected BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
1456                    if (businessObjectAuthorizationService == null) {
1457                            businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
1458                    }
1459                    return businessObjectAuthorizationService;
1460            }
1461    
1462            protected DocumentHelperService getDocumentHelperService() {
1463                    if (documentHelperService == null) {
1464                            documentHelperService = KNSServiceLocator.getDocumentHelperService();
1465                    }
1466                    return documentHelperService;
1467            }
1468    
1469            public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) {
1470                    this.businessObjectDictionaryService = businessObjectDictionaryService;
1471            }
1472    
1473            public void setPersonService(PersonService personService) {
1474                    this.personService = personService;
1475            }
1476    
1477            public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) {
1478                    this.businessObjectMetaDataService = businessObjectMetaDataService;
1479            }
1480    
1481            public void setBusinessObjectAuthorizationService(
1482                            BusinessObjectAuthorizationService businessObjectAuthorizationService) {
1483                    this.businessObjectAuthorizationService = businessObjectAuthorizationService;
1484            }
1485    
1486            public void setDocumentHelperService(DocumentHelperService documentHelperService) {
1487                    this.documentHelperService = documentHelperService;
1488            }
1489    
1490        public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
1491            if (maintenanceDocumentDictionaryService == null) {
1492                this.maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
1493            }
1494            return maintenanceDocumentDictionaryService;
1495        }
1496    
1497        public void setMaintenanceDocumentDictionaryService(
1498                MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
1499            this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
1500        }
1501    }