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