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