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