001    /**
002     * Copyright 2005-2014 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.web.struts.action;
017    
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.collections.CollectionUtils;
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
022    import org.apache.struts.action.ActionForm;
023    import org.apache.struts.action.ActionForward;
024    import org.apache.struts.action.ActionMapping;
025    import org.apache.struts.upload.FormFile;
026    import org.kuali.rice.core.api.CoreApiServiceLocator;
027    import org.kuali.rice.core.api.encryption.EncryptionService;
028    import org.kuali.rice.core.api.util.ClassLoaderUtils;
029    import org.kuali.rice.core.api.util.RiceConstants;
030    import org.kuali.rice.core.api.util.RiceKeyConstants;
031    import org.kuali.rice.core.web.format.Formatter;
032    import org.kuali.rice.kew.api.KewApiConstants;
033    import org.kuali.rice.kim.api.identity.Person;
034    import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
035    import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
036    import org.kuali.rice.kns.document.MaintenanceDocument;
037    import org.kuali.rice.kns.document.MaintenanceDocumentBase;
038    import org.kuali.rice.kns.document.authorization.MaintenanceDocumentAuthorizer;
039    import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
040    import org.kuali.rice.kns.lookup.LookupResultsService;
041    import org.kuali.rice.kns.maintenance.Maintainable;
042    import org.kuali.rice.kns.rule.event.KualiAddLineEvent;
043    import org.kuali.rice.kns.service.KNSServiceLocator;
044    import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
045    import org.kuali.rice.kns.util.KNSGlobalVariables;
046    import org.kuali.rice.kns.util.MaintenanceUtils;
047    import org.kuali.rice.kns.util.WebUtils;
048    import org.kuali.rice.kns.web.struts.form.InquiryForm;
049    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
050    import org.kuali.rice.kns.web.struts.form.KualiForm;
051    import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
052    import org.kuali.rice.kns.web.ui.Field;
053    import org.kuali.rice.kns.web.ui.Row;
054    import org.kuali.rice.kns.web.ui.Section;
055    import org.kuali.rice.krad.bo.DocumentAttachment;
056    import org.kuali.rice.krad.bo.MultiDocumentAttachment;
057    import org.kuali.rice.krad.bo.PersistableAttachment;
058    import org.kuali.rice.krad.bo.PersistableAttachmentList;
059    import org.kuali.rice.krad.bo.PersistableBusinessObject;
060    import org.kuali.rice.krad.exception.DocumentTypeAuthorizationException;
061    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
062    import org.kuali.rice.krad.service.LookupService;
063    import org.kuali.rice.krad.util.GlobalVariables;
064    import org.kuali.rice.krad.util.KRADConstants;
065    import org.kuali.rice.krad.util.KRADPropertyConstants;
066    import org.kuali.rice.krad.util.ObjectUtils;
067    
068    import javax.servlet.http.HttpServletRequest;
069    import javax.servlet.http.HttpServletResponse;
070    import java.lang.reflect.InvocationTargetException;
071    import java.lang.reflect.Method;
072    import java.security.GeneralSecurityException;
073    import java.util.ArrayList;
074    import java.util.Collection;
075    import java.util.Enumeration;
076    import java.util.HashMap;
077    import java.util.Iterator;
078    import java.util.List;
079    import java.util.Map;
080    
081    /**
082     * This class handles actions for maintenance documents. These include creating new edit, and copying of maintenance records.
083     */
084    public class KualiMaintenanceDocumentAction extends KualiDocumentActionBase {
085        protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiMaintenanceDocumentAction.class);
086    
087        protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService = null;
088        protected EncryptionService encryptionService;
089        protected LookupService lookupService;
090        protected LookupResultsService lookupResultsService;
091    
092            public KualiMaintenanceDocumentAction() {
093                    super();
094                    maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
095                    encryptionService = CoreApiServiceLocator.getEncryptionService();
096            }
097    
098            @Override
099            public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {       
100                    request.setAttribute(KRADConstants.PARAM_MAINTENANCE_VIEW_MODE, KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_MAINTENANCE);
101                    return super.execute(mapping, form, request, response);
102            }
103    
104            /**
105             * Calls setup Maintenance for new action.
106             */
107            public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
108                    request.setAttribute(KRADConstants.MAINTENANCE_ACTN, KRADConstants.MAINTENANCE_NEW_ACTION);
109                    return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_NEW_ACTION);
110            }
111    
112            /**
113             * Calls setupMaintenance for copy action.
114             */
115            public ActionForward copy(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
116                    // check for copy document number
117                    if (request.getParameter("document." + KRADPropertyConstants.DOCUMENT_NUMBER) == null) { // object copy
118                            return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_COPY_ACTION);
119                    }
120                    else { // document copy
121                            throw new UnsupportedOperationException("System does not support copying of maintenance documents.");
122                    }
123            }
124    
125            /**
126             * Calls setupMaintenance for edit action.
127             */
128            public ActionForward edit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
129            
130                    return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_EDIT_ACTION);
131            }
132    
133            /**
134             * KUALRice 3070 Calls setupMaintenance for delete action.
135             */
136            public ActionForward delete(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
137                    if (isFormRepresentingLockObject((KualiDocumentFormBase)form)) {
138                             return super.delete(mapping, form, request, response);
139                    }
140                    KNSGlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_DELETE);
141                    return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_DELETE_ACTION);
142            }
143            
144            /**
145             * Calls setupMaintenance for new object that have existing objects attributes.
146             */
147            public ActionForward newWithExisting(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
148                    return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION);
149            }
150    
151            /**
152             * Gets a new document for a maintenance record. The maintainable is specified with the documentTypeName or business object
153             * class request parameter and request parameters are parsed for key values for retrieving the business object. Forward to the
154             * maintenance jsp which renders the page based on the maintainable's field specifications. Retrieves an existing business
155             * object for edit and copy. Checks locking on edit.
156             */
157        protected ActionForward setupMaintenance(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String maintenanceAction) throws Exception {
158                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) form;
159                    MaintenanceDocument document = null;
160    
161                    // create a new document object, if required (on NEW object, or other reasons)
162                    if (maintenanceForm.getDocument() == null) {
163                            if (StringUtils.isEmpty(maintenanceForm.getBusinessObjectClassName()) && StringUtils.isEmpty(maintenanceForm.getDocTypeName())) {
164                                    throw new IllegalArgumentException("Document type name or bo class not given!");
165                            }
166    
167                            String documentTypeName = maintenanceForm.getDocTypeName();
168                            // get document type if not passed
169                            if (StringUtils.isEmpty(documentTypeName)) {
170                                    documentTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(Class.forName(maintenanceForm.getBusinessObjectClassName()));
171                                    maintenanceForm.setDocTypeName(documentTypeName);
172                            }
173    
174                            if (StringUtils.isEmpty(documentTypeName)) {
175                                    throw new RuntimeException("documentTypeName is empty; does this Business Object have a maintenance document definition? " + maintenanceForm.getBusinessObjectClassName());
176                            }
177    
178                            // check doc type allows new or copy if that action was requested
179                            if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) || KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
180                                    Class boClass = maintenanceDocumentDictionaryService.getDataObjectClass(documentTypeName);
181                                    boolean allowsNewOrCopy = getBusinessObjectAuthorizationService().canCreate(boClass, GlobalVariables.getUserSession().getPerson(), documentTypeName);
182                                    if (!allowsNewOrCopy) {
183                                            LOG.error("Document type " + documentTypeName + " does not allow new or copy actions.");
184                                            throw new DocumentTypeAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalId(), "newOrCopy", documentTypeName);
185                                    }
186                            }
187    
188                            // get new document from service
189                            document = (MaintenanceDocument) getDocumentService().getNewDocument(maintenanceForm.getDocTypeName());
190                            // Check for an auto-incrementing PK and set it if needed
191                            //            if (document.getNewMaintainableObject().getBoClass().isAnnotationPresent(Sequence.class)) {
192                            //                      Sequence sequence = (Sequence) document.getNewMaintainableObject().getBoClass().getAnnotation(Sequence.class);
193                            //                      Long pk = OrmUtils.getNextAutoIncValue(sequence);
194                            //                      OrmUtils.populateAutoIncValue(document.getOldMaintainableObject().getBusinessObject(), pk);
195                            //                      OrmUtils.populateAutoIncValue(document.getNewMaintainableObject().getBusinessObject(), pk);
196                            //                      document.getOldMaintainableObject().getBusinessObject().setAutoIncrementSet(true);
197                            //                      document.getNewMaintainableObject().getBusinessObject().setAutoIncrementSet(true);
198                            //            }
199                            maintenanceForm.setDocument(document);
200                    }
201                    else {
202                            document = (MaintenanceDocument) maintenanceForm.getDocument();
203                    }
204    
205                    // retrieve business object from request parameters
206                    if (!(KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction))
207                    && !(KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction))) {
208                            Map requestParameters = buildKeyMapFromRequest(document.getNewMaintainableObject(), request);
209                PersistableBusinessObject oldBusinessObject = null;
210                try {
211                    oldBusinessObject = (PersistableBusinessObject) getLookupService().findObjectBySearch(Class.forName(maintenanceForm.getBusinessObjectClassName()), requestParameters);
212                } catch ( ClassNotPersistenceCapableException ex ) {
213                    if ( !document.getOldMaintainableObject().isExternalBusinessObject() ) {
214                            throw new RuntimeException( "BO Class: " + maintenanceForm.getBusinessObjectClassName() + " is not persistable and is not externalizable - configuration error" );
215                    }
216                    // otherwise, let fall through
217                }
218                            if (oldBusinessObject == null && !document.getOldMaintainableObject().isExternalBusinessObject()) {
219                    throw new RuntimeException("Cannot retrieve old record for maintenance document, incorrect parameters passed on maint url: " + requestParameters );
220                            } 
221    
222                            if(document.getOldMaintainableObject().isExternalBusinessObject()){
223                    if ( oldBusinessObject == null ) {
224                            try {
225                                    oldBusinessObject = (PersistableBusinessObject)document.getOldMaintainableObject().getBoClass().newInstance();
226                            } catch ( Exception ex ) {
227                                    throw new RuntimeException( "External BO maintainable was null and unable to instantiate for old maintainable object.", ex );
228                            }
229                    }
230                                    populateBOWithCopyKeyValues(request, oldBusinessObject, document.getOldMaintainableObject());
231                                    document.getOldMaintainableObject().prepareBusinessObject(oldBusinessObject);
232                    oldBusinessObject = document.getOldMaintainableObject().getBusinessObject();
233                            }
234                 //KULRICE-6985 Commented out because of StringIndexOutOfBoundsException for some classnames and since we are not using JPA at the moment.
235                            // Temp solution for loading extension objects - need to find a better way
236    //                      final String TMP_NM = oldBusinessObject.getClass().getName();
237    //                      final int START_INDEX = TMP_NM.indexOf('.', TMP_NM.indexOf('.') + 1) + 1;
238    //                      if ( ( OrmUtils.isJpaEnabled() || OrmUtils.isJpaEnabled(TMP_NM.substring(START_INDEX, TMP_NM.indexOf('.', TMP_NM.indexOf('.', START_INDEX) + 1))) ) &&
239    //                                      OrmUtils.isJpaAnnotated(oldBusinessObject.getClass()) && oldBusinessObject.getExtension() != null && OrmUtils.isJpaAnnotated(oldBusinessObject.getExtension().getClass())) {
240    //                              if (oldBusinessObject.getExtension() != null) {
241    //                                      PersistableBusinessObjectExtension boe = oldBusinessObject.getExtension();
242    //                                      EntityDescriptor entity = MetadataManager.getEntityDescriptor(oldBusinessObject.getExtension().getClass());
243    //                                      Criteria extensionCriteria = new Criteria(boe.getClass().getName());
244    //                                      for (FieldDescriptor fieldDescriptor : entity.getPrimaryKeys()) {
245    //                                              try {
246    //                                                      Field field = oldBusinessObject.getClass().getDeclaredField(fieldDescriptor.getName());
247    //                                                      field.setAccessible(true);
248    //                                                      extensionCriteria.eq(fieldDescriptor.getName(), field.get(oldBusinessObject));
249    //                                              } catch (Exception e) {
250    //                                                      LOG.error(e.getMessage(),e);
251    //                                              }
252    //                                      }
253    //                                      try {
254    //                                              boe = (PersistableBusinessObjectExtension) new QueryByCriteria(getEntityManagerFactory().createEntityManager(), extensionCriteria).toQuery().getSingleResult();
255    //                                      } catch (PersistenceException e) {}
256    //                                      oldBusinessObject.setExtension(boe);
257    //                              }
258    //                      }
259    
260                            PersistableBusinessObject newBusinessObject = (PersistableBusinessObject) ObjectUtils.deepCopy(oldBusinessObject);
261    
262                            // set business object instance for editing
263                            Class<? extends PersistableBusinessObject> businessObjectClass = ClassLoaderUtils.getClass(maintenanceForm.getBusinessObjectClassName(), PersistableBusinessObject.class); 
264                            document.getOldMaintainableObject().setBusinessObject(oldBusinessObject);
265                            document.getOldMaintainableObject().setBoClass(businessObjectClass);
266                            document.getNewMaintainableObject().setBusinessObject(newBusinessObject);
267                            document.getNewMaintainableObject().setBoClass(businessObjectClass);
268    
269    
270                            // on a COPY, clear any fields that this user isnt authorized for, and also
271                            // clear the primary key fields and the version number and objectId
272                            if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
273                                    if (!document.isFieldsClearedOnCopy()) {
274                                            //for issue KULRice 3072
275                                            Class boClass = maintenanceDocumentDictionaryService.getDataObjectClass(
276                                maintenanceForm.getDocTypeName());
277                        if (!maintenanceDocumentDictionaryService.getPreserveLockingKeysOnCopy(boClass)) {
278                            clearPrimaryKeyFields(document);
279                        }
280    
281                                            clearUnauthorizedNewFields(document);
282    
283                                            Maintainable maintainable = document.getNewMaintainableObject();
284    
285                                            maintainable.processAfterCopy( document, request.getParameterMap() );
286    
287                                            // mark so that this clearing doesnt happen again
288                                            document.setFieldsClearedOnCopy(true);
289    
290                                            // mark so that blank required fields will be populated with default values
291                                            maintainable.setGenerateBlankRequiredValues(maintenanceForm.getDocTypeName());
292                                    }
293                            }
294                            else if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction)) {
295                                    boolean allowsEdit = getBusinessObjectAuthorizationService().canMaintain(oldBusinessObject, GlobalVariables.getUserSession().getPerson(), document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
296                                    if (!allowsEdit) {
297                                            LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() + " does not allow edit actions.");
298                                            throw  new DocumentTypeAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalId(), "edit", document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
299                                    }
300                                    document.getNewMaintainableObject().processAfterEdit( document, request.getParameterMap() );
301                            }
302                            //3070
303                            else if (KRADConstants.MAINTENANCE_DELETE_ACTION.equals(maintenanceAction)) {
304                                    boolean allowsDelete = getBusinessObjectAuthorizationService().canMaintain(oldBusinessObject, GlobalVariables.getUserSession().getPerson(), document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
305                                    if (!allowsDelete) {
306                                            LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() + " does not allow delete actions.");
307                                            throw  new DocumentTypeAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalId(), "delete", document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
308                                    }       
309                                    //document.getNewMaintainableObject().processAfterEdit( document, request.getParameterMap() );
310                            }
311                            // Check for an auto-incrementing PK and set it if needed
312                            //            if (document.getNewMaintainableObject().getBoClass().isAnnotationPresent(Sequence.class)) {
313                            //                      Sequence sequence = (Sequence) document.getNewMaintainableObject().getBoClass().getAnnotation(Sequence.class);
314                            //                      Long pk = OrmUtils.getNextAutoIncValue(sequence);
315                            //                      OrmUtils.populateAutoIncValue(document.getNewMaintainableObject().getBusinessObject(), pk);
316                            //                      document.getNewMaintainableObject().getBusinessObject().setAutoIncrementSet(true);
317                            //            }
318                    }
319                    // if new with existing we need to populate we need to populate with passed in parameters
320                    if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) {
321                            // TODO: this code should be abstracted out into a helper
322                            // also is it a problem that we're not calling setGenerateDefaultValues? it blanked out the below values when I did
323                            // maybe we need a new generateDefaultValues that doesn't overwrite?
324                            PersistableBusinessObject newBO = document.getNewMaintainableObject().getBusinessObject();
325                            Map<String, String> parameters = buildKeyMapFromRequest(document.getNewMaintainableObject(), request);
326                            copyParametersToBO(parameters, newBO);
327                            newBO.refresh();
328                            document.getNewMaintainableObject().setupNewFromExisting( document, request.getParameterMap() );
329                    }
330    
331                    // for new maintainble need to pick up default values
332                    if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction)) {
333                            document.getNewMaintainableObject().setGenerateDefaultValues(maintenanceForm.getDocTypeName());
334                            document.getNewMaintainableObject().processAfterNew( document, request.getParameterMap() );
335    
336                            // If a maintenance lock exists, warn the user.
337                            MaintenanceUtils.checkForLockingDocument(document.getNewMaintainableObject(), false);
338                    }
339    
340                    // set maintenance action state
341                    document.getNewMaintainableObject().setMaintenanceAction(maintenanceAction);
342                    maintenanceForm.setMaintenanceAction(maintenanceAction);
343    
344                    // attach any extra JS from the data dictionary
345            MaintenanceDocumentEntry entry =  maintenanceDocumentDictionaryService.getMaintenanceDocumentEntry(document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
346                    if (LOG.isDebugEnabled()) {
347                            LOG.debug("maintenanceForm.getAdditionalScriptFiles(): " + maintenanceForm.getAdditionalScriptFiles());
348                    }
349                    if (maintenanceForm.getAdditionalScriptFiles().isEmpty()) {
350                            maintenanceForm.getAdditionalScriptFiles().addAll(entry.getWebScriptFiles());
351                    }
352    
353                    // Retrieve notes topic display flag from data dictionary and add to document
354                    document.setDisplayTopicFieldInNotes(entry.getDisplayTopicFieldInNotes());
355    
356                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
357            }
358    
359        protected void populateBOWithCopyKeyValues(HttpServletRequest request, PersistableBusinessObject oldBusinessObject, Maintainable oldMaintainableObject) throws Exception{
360                    List keyFieldNamesToCopy = new ArrayList();
361                    Map<String, String> parametersToCopy;
362                    if (!StringUtils.isBlank(request.getParameter(KRADConstants.COPY_KEYS))) {
363                            String[] copyKeys = request.getParameter(KRADConstants.COPY_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
364                            for (String copyKey: copyKeys) {
365                                    keyFieldNamesToCopy.add(copyKey);
366                            }
367                    }
368                    parametersToCopy = getRequestParameters(keyFieldNamesToCopy, oldMaintainableObject, request);
369                    if(parametersToCopy!=null && parametersToCopy.size()>0){
370                            copyParametersToBO(parametersToCopy, oldBusinessObject);
371                    }
372            }
373    
374        protected void copyParametersToBO(Map<String, String> parameters, PersistableBusinessObject newBO) throws Exception{
375                    for (String parmName : parameters.keySet()) {
376                            String propertyValue = parameters.get(parmName);
377    
378                            if (StringUtils.isNotBlank(propertyValue)) {
379                                    String propertyName = parmName;
380                                    // set value of property in bo
381                                    if (PropertyUtils.isWriteable(newBO, propertyName)) {
382                                            Class type = ObjectUtils.easyGetPropertyType(newBO, propertyName);
383                                            if (type != null && Formatter.getFormatter(type) != null) {
384                                                    Formatter formatter = Formatter.getFormatter(type);
385                                                    Object obj = formatter.convertFromPresentationFormat(propertyValue);
386                                                    ObjectUtils.setObjectProperty(newBO, propertyName, obj.getClass(), obj);
387                                            }
388                                            else {
389                                                    ObjectUtils.setObjectProperty(newBO, propertyName, String.class, propertyValue);
390                                            }
391                                    }
392                            }
393                    }
394            }
395    
396            /**
397             * Downloads the attachment to the user's browser
398             *
399             * @param mapping
400             * @param form
401             * @param request
402             * @param response
403             * @return ActionForward
404             * @throws Exception
405             */
406            public ActionForward downloadAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
407                    KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
408                    MaintenanceDocumentBase document = (MaintenanceDocumentBase) documentForm.getDocument();
409    
410            int line = getSelectedLine(request);
411            if (line < 0) {
412                DocumentAttachment documentAttachment = document.getAttachment();
413                if (documentAttachment != null
414                        && documentAttachment.getAttachmentContent() != null) {
415    
416                    streamToResponse(documentAttachment.getAttachmentContent(), documentAttachment.getFileName(), documentAttachment.getContentType(), response);
417                    return null;
418                }
419                PersistableAttachment attachment = (PersistableAttachment) document.getNewMaintainableObject().getBusinessObject();
420                String attachmentPropNm = document.getAttachmentPropertyName();
421                FormFile attachmentFromBusinessObject = null;
422                byte[] attachmentContent;
423                String fileName = attachment.getFileName();
424                String contentType = attachment.getContentType();
425                if (StringUtils.isNotBlank(attachmentPropNm)) {
426                    String attachmentPropNmSetter = "get" + attachmentPropNm.substring(0, 1).toUpperCase() + attachmentPropNm.substring(1, attachmentPropNm.length());
427                    attachmentFromBusinessObject = (FormFile)(attachment.getClass().getDeclaredMethod(attachmentPropNmSetter).invoke(attachment));
428                }
429                if (attachmentFromBusinessObject != null
430                        && attachmentFromBusinessObject.getInputStream() != null) {
431                    attachmentContent = attachmentFromBusinessObject.getFileData();
432                    fileName = attachmentFromBusinessObject.getFileName();
433                    contentType = attachmentFromBusinessObject.getContentType();
434                } else {
435                    attachmentContent = attachment.getAttachmentContent();
436                }
437                if (StringUtils.isNotBlank(fileName)
438                        && contentType != null
439                        && attachmentContent != null) {
440                    streamToResponse(attachmentContent, fileName, contentType, response);
441                }
442            } else {
443    
444                // attachment is part of a collection
445                PersistableAttachmentList<PersistableAttachment> attachmentsBo = (PersistableAttachmentList<PersistableAttachment>) document.getNewMaintainableObject().getBusinessObject();
446                if (CollectionUtils.isEmpty(attachmentsBo.getAttachments())) {
447                    document.populateAttachmentListForBO();
448                }
449    
450                List<? extends PersistableAttachment> attachments = attachmentsBo.getAttachments();
451                if (CollectionUtils.isNotEmpty(attachments)
452                        && attachments.size() > line) {
453                    PersistableAttachment attachment = attachmentsBo.getAttachments().get(line);
454    
455                    //it is possible that document hasn't been saved (attachment just added) and the attachment content is still in the FormFile
456                    //need to grab it if that is the case.
457                    byte[] attachmentContent; // = attachment.getAttachmentContent();
458                    String fileName = attachment.getFileName();
459                    String contentType = attachment.getContentType();
460                    String attachmentPropNm = document.getAttachmentListPropertyName();
461                    FormFile attachmentFromBusinessObject = null;
462                    if (StringUtils.isNotBlank(attachmentPropNm)) {
463                        String attachmentPropNmSetter = "get" + attachmentPropNm.substring(0, 1).toUpperCase() + attachmentPropNm.substring(1, attachmentPropNm.length());
464                        attachmentFromBusinessObject = (FormFile)(attachment.getClass().getDeclaredMethod(attachmentPropNmSetter).invoke(attachment));
465                    }
466                    //Use form file data if it exists
467                    //if (attachmentContent == null) {
468    
469                    if (attachmentFromBusinessObject != null
470                        && attachmentFromBusinessObject.getInputStream() != null) {
471                        attachmentContent = attachmentFromBusinessObject.getFileData();
472                        fileName = attachmentFromBusinessObject.getFileName();
473                        contentType = attachmentFromBusinessObject.getContentType();
474                    } else {
475                        attachmentContent = attachment.getAttachmentContent();
476                    }
477    
478                    if (attachmentContent != null) {
479                        streamToResponse(attachmentContent, fileName, contentType, response);
480                    } else {
481                        // last ditch effort to find the correct attachment
482                        //check to see if attachment is populated on document first, so no copying done unless necessary
483                        List<MultiDocumentAttachment> multiDocumentAttachs = document.getAttachments();
484                        if (CollectionUtils.isNotEmpty(multiDocumentAttachs)) {
485                            for (MultiDocumentAttachment multiAttach : multiDocumentAttachs) {
486                                if (multiAttach.getFileName().equals(fileName)
487                                        && multiAttach.getContentType().equals(contentType)) {
488                                    streamToResponse(multiAttach.getAttachmentContent(), multiAttach.getFileName(), multiAttach.getContentType(), response);
489                                    break;
490                                }
491                            }
492                        }
493                    }
494                }
495            }
496                    return null;
497            }
498    
499    
500            /**
501             * 
502             * This method used to replace the attachment
503             * @param mapping
504             * @param form
505             * @param request
506             * @param response
507             * @return
508             * @throws Exception
509             */
510            public ActionForward replaceAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request,
511                            HttpServletResponse response) throws Exception {
512                    KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
513                    MaintenanceDocumentBase document = (MaintenanceDocumentBase) documentForm.getDocument();
514    
515            int lineNum = getSelectedLine(request);
516    
517            if (lineNum < 0) {
518    
519                document.refreshReferenceObject("attachment");
520                documentForm.setAttachmentFile(null);
521                document.setFileAttachment(null);
522                getBusinessObjectService().delete(document.getAttachment());
523                document.setAttachment(null);
524    
525                PersistableAttachment attachment = (PersistableAttachment) document.getNewMaintainableObject().getBusinessObject();
526    
527                attachment.setAttachmentContent(null);
528                attachment.setContentType(null);
529                attachment.setFileName(null);
530                //pBo.setAttachmentFile(null);
531    
532                String attachmentPropNm = document.getAttachmentPropertyName();
533                String attachmentPropNmSetter = "set" + attachmentPropNm.substring(0, 1).toUpperCase() + attachmentPropNm.substring(1, attachmentPropNm.length());
534                Class propNameSetterSig = null;
535    
536                try {
537                    Method[] methods = attachment.getClass().getMethods();
538                    for (Method method : methods) {
539                        if (method.getName().equals(attachmentPropNmSetter)) {
540                            propNameSetterSig = method.getParameterTypes()[0];
541                            attachment.getClass().getDeclaredMethod(attachmentPropNmSetter, propNameSetterSig).invoke(attachment, (Object) null);
542                            break;
543                        }
544                    }
545                } catch (Exception e) {
546                    LOG.error("Not able to get the attachment " + e.getMessage());
547                    throw new RuntimeException(
548                            "Not able to get the attachment  " + e.getMessage());
549                }
550            } else {
551                document.refreshReferenceObject("attachments");
552                getBusinessObjectService().delete(document.getAttachment());
553    
554                PersistableAttachmentList<PersistableAttachment> attachmentListBo = (PersistableAttachmentList<PersistableAttachment>) document.getNewMaintainableObject().getBusinessObject();
555    
556                PersistableAttachment attachment = (PersistableAttachment)attachmentListBo.getAttachments().get(lineNum);
557                attachment.setAttachmentContent(null);
558                attachment.setContentType(null);
559                attachment.setFileName(null);
560    
561                String attachmentPropNm = document.getAttachmentListPropertyName();
562                String attachmentPropNmSetter = "set" + attachmentPropNm.substring(0, 1).toUpperCase() + attachmentPropNm.substring(1, attachmentPropNm.length());
563                Class propNameSetterSig = null;
564    
565                try {
566                    Method[] methods = attachment.getClass().getMethods();
567                    for (Method method : methods) {
568                        if (method.getName().equals(attachmentPropNmSetter)) {
569                            propNameSetterSig = method.getParameterTypes()[0];
570                            attachment.getClass().getDeclaredMethod(attachmentPropNmSetter, propNameSetterSig).invoke(attachment, (Object) null);
571                            break;
572                        }
573                    }
574                } catch (Exception e) {
575                    LOG.error("Not able to get the attachment " + e.getMessage());
576                    throw new RuntimeException(
577                            "Not able to get the attachment  " + e.getMessage());
578                }
579            }
580    
581                return mapping.findForward(RiceConstants.MAPPING_BASIC);
582            }
583    
584            /**
585             * route the document using the document service
586             * 
587             * @param mapping
588             * @param form
589             * @param request
590             * @param response
591             * @return ActionForward
592             * @throws Exception
593             */
594            @Override
595            public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
596                    KualiDocumentFormBase documentForm = (KualiDocumentFormBase) form;
597                    MaintenanceDocumentBase document = (MaintenanceDocumentBase) documentForm.getDocument();
598    
599                    ActionForward forward = super.route(mapping, form, request, response);
600                    PersistableBusinessObject businessObject = document.getNewMaintainableObject().getBusinessObject();
601                    if(businessObject instanceof PersistableAttachment) {
602                            document.populateAttachmentForBO();
603                            String fileName = ((PersistableAttachment) businessObject).getFileName();
604                            if(StringUtils.isEmpty(fileName)) {
605                                    PersistableAttachment existingBO = (PersistableAttachment) getBusinessObjectService().retrieve(document.getNewMaintainableObject().getBusinessObject());
606                                    if (existingBO == null) {
607                                            if (document.getAttachment() != null) {
608                                                    fileName = document.getAttachment().getFileName();
609                                            } else {
610                                                    fileName = "";
611                                            }
612                                    } else {
613                                            fileName = (existingBO != null ? existingBO.getFileName() : "");
614                                    }
615                                    request.setAttribute("fileName", fileName);
616                            }
617                    }
618                    return forward;
619            }
620    
621            /**
622             * Handles creating and loading of documents.
623             */
624            @Override
625            public ActionForward docHandler(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
626                    ActionForward af = super.docHandler(mapping, form, request, response);
627            if (af.getName().equals(KRADConstants.KRAD_INITIATED_DOCUMENT_VIEW_NAME))
628            {
629                return af;
630            }
631                    KualiMaintenanceForm kualiMaintenanceForm = (KualiMaintenanceForm) form;
632    
633                    if (KewApiConstants.ACTIONLIST_COMMAND.equals(kualiMaintenanceForm.getCommand()) || KewApiConstants.DOCSEARCH_COMMAND.equals(kualiMaintenanceForm.getCommand()) || KewApiConstants.SUPERUSER_COMMAND.equals(kualiMaintenanceForm.getCommand()) || KewApiConstants.HELPDESK_ACTIONLIST_COMMAND.equals(kualiMaintenanceForm.getCommand()) && kualiMaintenanceForm.getDocId() != null) {
634                            if (kualiMaintenanceForm.getDocument() instanceof MaintenanceDocument) {
635                                    kualiMaintenanceForm.setReadOnly(true);
636                                    kualiMaintenanceForm.setMaintenanceAction(((MaintenanceDocument) kualiMaintenanceForm.getDocument()).getNewMaintainableObject().getMaintenanceAction());
637    
638                                    //Retrieving the FileName from BO table
639                                    Maintainable tmpMaintainable = ((MaintenanceDocument) kualiMaintenanceForm.getDocument()).getNewMaintainableObject();
640                                    if(tmpMaintainable.getBusinessObject() instanceof PersistableAttachment) {
641                                            PersistableAttachment bo = (PersistableAttachment) getBusinessObjectService().retrieve(tmpMaintainable.getBusinessObject());
642                        if (bo != null) {
643                            request.setAttribute("fileName", bo.getFileName());
644                        }
645                                    }
646                            }
647                            else {
648                                    LOG.error("Illegal State: document is not a maintenance document");
649                                    throw new IllegalArgumentException("Document is not a maintenance document");
650                            }
651                    }
652                    else if (KewApiConstants.INITIATE_COMMAND.equals(kualiMaintenanceForm.getCommand())) {
653                            kualiMaintenanceForm.setReadOnly(false);
654                            return setupMaintenance(mapping, form, request, response, KRADConstants.MAINTENANCE_NEW_ACTION);
655                    }
656                    else {
657                            LOG.error("We should never have gotten to here");
658                            throw new IllegalArgumentException("docHandler called with invalid parameters");
659                    }
660                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
661            }
662    
663            /**
664             * Called on return from a lookup.
665             */
666            @Override
667            public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
668                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) form;
669    
670                    WebUtils.reuseErrorMapFromPreviousRequest(maintenanceForm);
671                    maintenanceForm.setDerivedValuesOnForm(request);
672    
673                    refreshAdHocRoutingWorkgroupLookups(request, maintenanceForm);
674                    MaintenanceDocument document = (MaintenanceDocument) maintenanceForm.getDocument();
675    
676                    // call refresh on new maintainable
677                    Map<String, String> requestParams = new HashMap<String, String>();
678                    for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
679                            String requestKey = (String) i.nextElement();
680                            String requestValue = request.getParameter(requestKey);
681                            requestParams.put(requestKey, requestValue);
682                    }
683    
684                    // Add multiple values from Lookup
685                    Collection<PersistableBusinessObject> rawValues = null;
686                    if (StringUtils.equals(KRADConstants.MULTIPLE_VALUE, maintenanceForm.getRefreshCaller())) {
687                            String lookupResultsSequenceNumber = maintenanceForm.getLookupResultsSequenceNumber();
688                            if (StringUtils.isNotBlank(lookupResultsSequenceNumber)) {
689                                    // actually returning from a multiple value lookup
690                                    String lookupResultsBOClassName = maintenanceForm.getLookupResultsBOClassName();
691                                    Class lookupResultsBOClass = Class.forName(lookupResultsBOClassName);
692    
693                                    rawValues = getLookupResultsService().retrieveSelectedResultBOs(lookupResultsSequenceNumber, lookupResultsBOClass, GlobalVariables.getUserSession().getPerson().getPrincipalId());
694                            }
695                    }
696    
697                    if (rawValues != null) { // KULCOA-1073 - caused by this block running unnecessarily?
698                            // we need to run the business rules on all the newly added items to the collection
699                            // KULCOA-1000, KULCOA-1004 removed business rule validation on multiple value return
700                            // (this was running before the objects were added anyway)
701                            // getKualiRuleService().applyRules(new SaveDocumentEvent(document));
702                            String collectionName = maintenanceForm.getLookedUpCollectionName();
703                            //TODO: Cathy remember to delete this block of comments after I've tested.            
704                            //            PersistableBusinessObject bo = document.getNewMaintainableObject().getBusinessObject();
705                            //            Collection maintCollection = this.extractCollection(bo, collectionName);
706                            //            String docTypeName = ((MaintenanceDocument) maintenanceForm.getDocument()).getDocumentHeader().getWorkflowDocument().getDocumentType();
707                            //            Class collectionClass = extractCollectionClass(docTypeName, collectionName);
708                            //
709                            //            List<MaintainableSectionDefinition> sections = maintenanceDocumentDictionaryService.getMaintainableSections(docTypeName);
710                            //            Map<String, String> template = MaintenanceUtils.generateMultipleValueLookupBOTemplate(sections, collectionName);
711                            //            for (PersistableBusinessObject nextBo : rawValues) {
712                            //                PersistableBusinessObject templatedBo = (PersistableBusinessObject) ObjectUtils.createHybridBusinessObject(collectionClass, nextBo, template);
713                            //                templatedBo.setNewCollectionRecord(true);
714                            //                maintCollection.add(templatedBo);
715                            //            }
716                            document.getNewMaintainableObject().addMultipleValueLookupResults(document, collectionName, rawValues, false, document.getNewMaintainableObject().getBusinessObject());
717                            if (LOG.isInfoEnabled()) {
718                                    LOG.info("********************doing editing 3 in refersh()***********************.");
719                            }
720                            boolean isEdit = KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceForm.getMaintenanceAction());
721                            boolean isCopy = KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceForm.getMaintenanceAction());
722    
723                            if (isEdit || isCopy) {
724                                    document.getOldMaintainableObject().addMultipleValueLookupResults(document, collectionName, rawValues, true, document.getOldMaintainableObject().getBusinessObject());
725                                    document.getOldMaintainableObject().refresh(maintenanceForm.getRefreshCaller(), requestParams, document);
726                            }
727                    }
728    
729                    document.getNewMaintainableObject().refresh(maintenanceForm.getRefreshCaller(), requestParams, document);
730    
731                    //pass out customAction from methodToCall parameter. Call processAfterPost
732                    String fullParameter = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
733                    if(StringUtils.contains(fullParameter, KRADConstants.CUSTOM_ACTION)){
734                            String customAction = StringUtils.substringBetween(fullParameter, KRADConstants.METHOD_TO_CALL_PARM1_LEFT_DEL, KRADConstants.METHOD_TO_CALL_PARM1_RIGHT_DEL);
735                            String[] actionValue = new String[1];
736                            actionValue[0]= StringUtils.substringAfter(customAction, ".");
737                            Map<String,String[]> paramMap = new HashMap<String,String[]>(request.getParameterMap());
738                            paramMap.put(KRADConstants.CUSTOM_ACTION, actionValue);
739                            doProcessingAfterPost( (KualiMaintenanceForm) form, paramMap );
740                    }
741    
742                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
743            }
744    
745            /**
746             * Gets keys for the maintainable business object from the persistence metadata explorer. Checks for existence of key property
747             * names as request parameters, if found adds them to the returned hash map.
748             */
749        protected Map buildKeyMapFromRequest(Maintainable maintainable, HttpServletRequest request) {
750                    List keyFieldNames = null;
751                    // are override keys listed in the request? If so, then those need to be our keys,
752                    // not the primary keye fields for the BO
753                    if (!StringUtils.isBlank(request.getParameter(KRADConstants.OVERRIDE_KEYS))) {
754                            String[] overrideKeys = request.getParameter(KRADConstants.OVERRIDE_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
755                            keyFieldNames = new ArrayList();
756                            for (String overrideKey : overrideKeys) {
757                                    keyFieldNames.add(overrideKey);
758                            }
759                    }
760                    else {
761                            keyFieldNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(maintainable.getBusinessObject().getClass());
762                    }
763                    return getRequestParameters(keyFieldNames, maintainable, request);
764            }
765    
766        protected Map<String, String> getRequestParameters(List keyFieldNames, Maintainable maintainable, HttpServletRequest request){
767    
768                    Map<String, String> requestParameters = new HashMap<String, String>();
769    
770    
771                    for (Iterator iter = keyFieldNames.iterator(); iter.hasNext();) {
772                            String keyPropertyName = (String) iter.next();
773    
774                            if (request.getParameter(keyPropertyName) != null) {
775                                    String keyValue = request.getParameter(keyPropertyName);
776    
777                                    // Check if this element was encrypted, if it was decrypt it
778                    if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(maintainable.getBoClass(), keyPropertyName)) {
779                                            try {
780                            keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX);
781                            if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
782                                                        keyValue = encryptionService.decrypt(keyValue);
783                            }
784                                            }
785                                            catch (GeneralSecurityException e) {
786                                                    throw new RuntimeException(e);
787                                            }
788                                    }
789    
790    
791                                    requestParameters.put(keyPropertyName, keyValue);
792                            }
793                    }
794    
795                    return requestParameters;
796    
797            }
798    
799            /**
800             * Convert a Request into a Map<String,String>. Technically, Request parameters do not neatly translate into a Map of Strings,
801             * because a given parameter may legally appear more than once (so a Map of String[] would be more accurate.) This method should
802             * be safe for business objects, but may not be reliable for more general uses.
803             */
804            String extractCollectionName(HttpServletRequest request, String methodToCall) {
805                    // collection name and underlying object type from request parameter
806                    String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
807                    String collectionName = null;
808                    if (StringUtils.isNotBlank(parameterName)) {
809                            collectionName = StringUtils.substringBetween(parameterName, methodToCall + ".", ".(");
810                    }
811                    return collectionName;
812            }
813    
814            Collection extractCollection(PersistableBusinessObject bo, String collectionName) {
815                    // retrieve the collection from the business object
816                    Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(bo, collectionName);
817                    return maintCollection;
818            }
819    
820            Class extractCollectionClass(String docTypeName, String collectionName) {
821                    return maintenanceDocumentDictionaryService.getCollectionBusinessObjectClass(docTypeName, collectionName);
822            }
823    
824            /**
825             * Adds a line to a collection being maintained in a many section.
826             */
827            public ActionForward addLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
828                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) form;
829                    MaintenanceDocument document = (MaintenanceDocument) maintenanceForm.getDocument();
830                    Maintainable oldMaintainable = document.getOldMaintainableObject();
831                    Maintainable newMaintainable = document.getNewMaintainableObject();
832    
833                    String collectionName = extractCollectionName(request, KRADConstants.ADD_LINE_METHOD);
834                    if (collectionName == null) {
835                            LOG.error("Unable to get find collection name and class in request.");
836                            throw new RuntimeException("Unable to get find collection name and class in request.");
837                    }
838    
839                    // if dealing with sub collection it will have a "["
840                    if ((StringUtils.lastIndexOf(collectionName, "]") + 1) == collectionName.length()) {
841                            collectionName = StringUtils.substringBeforeLast(collectionName, "[");
842                    }
843    
844                    PersistableBusinessObject bo = newMaintainable.getBusinessObject();
845                    Collection maintCollection = extractCollection(bo, collectionName);
846                    Class collectionClass = extractCollectionClass(((MaintenanceDocument) maintenanceForm.getDocument()).getDocumentHeader().getWorkflowDocument().getDocumentTypeName(), collectionName);
847    
848                    // TODO: sort of collection, new instance should be first
849    
850                    // get the BO from the new collection line holder
851                    PersistableBusinessObject addBO = newMaintainable.getNewCollectionLine(collectionName);
852                    if (LOG.isDebugEnabled()) {
853                            LOG.debug("obtained addBO from newCollectionLine: " + addBO);
854                    }
855    
856                    // link up the user fields, if any
857                    getBusinessObjectService().linkUserFields(addBO);
858    
859                    //KULRICE-4264 - a hook to change the state of the business object, which is the "new line" of a collection, before it is validated
860                    newMaintainable.processBeforeAddLine(collectionName, collectionClass, addBO);
861                    
862                    // apply rules to the addBO
863                    boolean rulePassed = false;
864                    if (LOG.isDebugEnabled()) {
865                            LOG.debug("about to call AddLineEvent applyRules: document=" + document + "\ncollectionName=" + collectionName + "\nBO=" + addBO);
866                    }
867                    rulePassed = getKualiRuleService().applyRules(new KualiAddLineEvent(document, collectionName, addBO));
868    
869                    // if the rule evaluation passed, let's add it
870                    if (rulePassed) {
871                            if (LOG.isInfoEnabled()) {
872                                    LOG.info("********************doing editing 4 in addline()***********************.");
873                            }
874                            // if edit or copy action, just add empty instance to old maintainable
875                            boolean isEdit = KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceForm.getMaintenanceAction());
876                            boolean isCopy = KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceForm.getMaintenanceAction());
877    
878    
879                            if (isEdit || isCopy) {
880                                    PersistableBusinessObject oldBo = oldMaintainable.getBusinessObject();
881                                    Collection oldMaintCollection = (Collection) ObjectUtils.getPropertyValue(oldBo, collectionName);
882    
883                                    if (oldMaintCollection == null) {
884                                            oldMaintCollection = new ArrayList();
885                                    }
886                                    if (PersistableBusinessObject.class.isAssignableFrom(collectionClass)) {
887                                            PersistableBusinessObject placeholder = (PersistableBusinessObject) collectionClass.newInstance();
888                                            // KULRNE-4538: must set it as a new collection record, because the maintainable will set the BO that gets added
889                                            // to the new maintainable as a new collection record
890    
891                                            // if not set, then the subcollections of the newly added object will appear as read only
892                                            // see FieldUtils.getContainerRows on how the delete button is rendered
893                                            placeholder.setNewCollectionRecord(true);
894                                            ((List) oldMaintCollection).add(placeholder);
895                                    }
896                                    else {
897                                            LOG.warn("Should be a instance of PersistableBusinessObject");
898                                            ((List) oldMaintCollection).add(collectionClass.newInstance());
899                                    }
900                                    // update collection in maintenance business object
901                                    ObjectUtils.setObjectProperty(oldBo, collectionName, List.class, oldMaintCollection);
902                            }
903    
904                            newMaintainable.addNewLineToCollection(collectionName);
905                            int subCollectionIndex = 0;
906                            for (Object aSubCollection : maintCollection) {
907                                    subCollectionIndex += getSubCollectionIndex(aSubCollection, maintenanceForm.getDocTypeName());
908                            }
909                            //TODO: Should we keep this logic and continue using currentTabIndex as the key in the tabStates HashMap ?
910                            //            
911                            //            String parameter = (String) request.getAttribute(Constants.METHOD_TO_CALL_ATTRIBUTE);
912                            //            String indexStr = StringUtils.substringBetween(parameter, Constants.METHOD_TO_CALL_PARM13_LEFT_DEL, Constants.METHOD_TO_CALL_PARM13_RIGHT_DEL);
913                            //            // + 1 is for the fact that the first element of a collection is on the next tab
914                            //            int index = Integer.parseInt(indexStr) + subCollectionIndex + 1;
915                            //            Map<String, String> tabStates = maintenanceForm.getTabStates();
916                            //            Map<String, String> copyOfTabStates = new HashMap<String, String>();
917                            //
918                            //            int incrementor = 0;
919                            //            for (String tabState : tabStates.keySet()) {
920                            //              String originalValue = maintenanceForm.getTabState(Integer.toString(incrementor));
921                            //                copyOfTabStates.put(Integer.toString(incrementor), originalValue);
922                            //                incrementor++;
923                            //            }
924                            //
925                            //            int i = index;
926                            //              if (tabStates.containsKey(Integer.toString(i-1))) {
927                            //                      tabStates.remove(Integer.toString(i-1));
928                            //              }
929                            //            while (i < copyOfTabStates.size() + 1) {
930                            //                String originalValue = copyOfTabStates.get(Integer.toString(i-1));
931                            //                if (tabStates.containsKey(Integer.toString(i))) {
932                            //                    tabStates.remove(Integer.toString(i));
933                            //                }
934                            //                tabStates.put(Integer.toString(i), originalValue);
935                            //                i++;
936                            //            }
937    
938    
939                            // End of whether we should continue to keep this logic and use currentTabIndex as the key            
940                    }
941                    doProcessingAfterPost( (KualiMaintenanceForm) form, request );
942    
943                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
944            }
945    
946        protected int getSubCollectionIndex(Object object, String documentTypeName) {
947                    int index = 1;
948                    MaintainableCollectionDefinition theCollectionDefinition = null;
949                    for (MaintainableCollectionDefinition maintainableCollectionDefinition : maintenanceDocumentDictionaryService.getMaintainableCollections(documentTypeName)) {
950                            if (maintainableCollectionDefinition.getBusinessObjectClass().equals(object.getClass())) {
951                                    // we've found the collection we were looking for, so let's find all of its subcollections
952                                    theCollectionDefinition = maintainableCollectionDefinition;
953                                    break;
954                            }
955                    }
956                    if (theCollectionDefinition != null) {
957                            for (MaintainableCollectionDefinition subCollDef : theCollectionDefinition.getMaintainableCollections()) {
958                                    String name = subCollDef.getName();
959                                    String capitalFirst = name.substring(0, 1).toUpperCase();
960                                    String methodName = "get" + capitalFirst + name.substring(1);
961                                    List subCollectionList = new ArrayList();
962                                    try {
963                                            subCollectionList = (List) object.getClass().getMethod(methodName).invoke(object);
964                                    }
965                                    catch (InvocationTargetException ite) {
966                                            // this shouldn't happen
967                                    }
968                                    catch (IllegalAccessException iae) {
969                                            // this shouldn't happen
970                                    }
971                                    catch (NoSuchMethodException nme) {
972                                            // this shouldn't happen
973                                    }
974                                    index += subCollectionList.size();
975                            }
976                    }
977                    return index;
978            }
979    
980            /**
981             * Deletes a collection line that is pending by this document. The collection name and the index to delete is embedded into the
982             * delete button name. These parameters are extracted, the collection pulled out of the parent business object, and finally the
983             * collection record at the specified index is removed for the new maintainable, and the old if we are dealing with an edit.
984             */
985            public ActionForward deleteLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
986                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) form;
987                    MaintenanceDocument document = (MaintenanceDocument) maintenanceForm.getDocument();
988                    Maintainable oldMaintainable = document.getOldMaintainableObject();
989                    Maintainable newMaintainable = document.getNewMaintainableObject();
990    
991                    String collectionName = extractCollectionName(request, KRADConstants.DELETE_LINE_METHOD);
992                    if (collectionName == null) {
993                            LOG.error("Unable to get find collection name in request.");
994                            throw new RuntimeException("Unable to get find collection class in request.");
995                    }
996    
997                    PersistableBusinessObject bo = newMaintainable.getBusinessObject();
998                    Collection maintCollection = extractCollection(bo, collectionName);
999                    if (collectionName == null) {
1000                            LOG.error("Collection is null in parent business object.");
1001                            throw new RuntimeException("Collection is null in parent business object.");
1002                    }
1003    
1004                    int deleteRecordIndex = getLineToDelete(request);
1005                    if (deleteRecordIndex < 0 || deleteRecordIndex > maintCollection.size() - 1) {
1006                            if (collectionName == null) {
1007                                    LOG.error("Invalid index for deletion of collection record: " + deleteRecordIndex);
1008                                    throw new RuntimeException("Invalid index for deletion of collection record: " + deleteRecordIndex);
1009                            }
1010                    }
1011    
1012                    ((List) maintCollection).remove(deleteRecordIndex);
1013    
1014                    // if it's either an edit or a copy, need to remove the collection from the old maintainable as well
1015                    if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceForm.getMaintenanceAction()) ||
1016                                    KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceForm.getMaintenanceAction())) {
1017                            bo = oldMaintainable.getBusinessObject();
1018                            maintCollection = extractCollection(bo, collectionName);
1019    
1020                            if (collectionName == null) {
1021                                    LOG.error("Collection is null in parent business object.");
1022                                    throw new RuntimeException("Collection is null in parent business object.");
1023                            }
1024    
1025                            ((List) maintCollection).remove(deleteRecordIndex);
1026                    }
1027    
1028                    // remove the tab state information of the tab that the deleted element originally occupied, so that it will keep tab states
1029                    // consistent
1030                    //        String parameter = (String) request.getAttribute(Constants.METHOD_TO_CALL_ATTRIBUTE);
1031                    //        String indexStr = StringUtils.substringBetween(parameter, Constants.METHOD_TO_CALL_PARM13_LEFT_DEL, Constants.METHOD_TO_CALL_PARM13_RIGHT_DEL);
1032                    //        int index = Integer.parseInt(indexStr);
1033                    //        maintenanceForm.removeTabState(index);
1034    
1035    
1036                    //      TODO: Should we keep this logic and continue using currentTabIndex as the key in the tabStates HashMap ?        
1037                    //        
1038                    //        String parameter = (String) request.getAttribute(Constants.METHOD_TO_CALL_ATTRIBUTE);
1039                    //        String indexStr = StringUtils.substringBetween(parameter, Constants.METHOD_TO_CALL_PARM13_LEFT_DEL, Constants.METHOD_TO_CALL_PARM13_RIGHT_DEL);
1040                    //        // + 1 is for the fact that the first element of a collection is on the next tab
1041                    //        int index = Integer.parseInt(indexStr) +  1;
1042                    //        Map<String, String> tabStates = maintenanceForm.getTabStates();
1043                    //        Map<String, String> copyOfTabStates = new HashMap<String, String>();
1044                    //
1045                    //        int incrementor = 0;
1046                    //        for (String tabState : tabStates.keySet()) {
1047                    //              String originalValue = maintenanceForm.getTabState(Integer.toString(incrementor));
1048                    //            copyOfTabStates.put(Integer.toString(incrementor), originalValue);
1049                    //            incrementor++;
1050                    //        }
1051                    //
1052                    //        int i = index;
1053                    //
1054                    //        while (i < copyOfTabStates.size() ) {
1055                    //            String originalValue = copyOfTabStates.get(Integer.toString(i));
1056                    //            if (tabStates.containsKey(Integer.toString(i-1))) {
1057                    //                tabStates.remove(Integer.toString(i-1));
1058                    //            }
1059                    //            tabStates.put(Integer.toString(i-1), originalValue);
1060                    //            i++;
1061                    //        }
1062                    //
1063                    //        
1064                    //End of whether we should continue to keep this logic and use currentTabIndex as the key            
1065    
1066                    doProcessingAfterPost( (KualiMaintenanceForm) form, request );
1067    
1068                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
1069            }
1070    
1071            /**
1072             * Turns on (or off) the inactive record display for a maintenance collection.
1073             */
1074            public ActionForward toggleInactiveRecordDisplay(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1075                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) form;
1076                    MaintenanceDocument document = (MaintenanceDocument) maintenanceForm.getDocument();
1077                    Maintainable oldMaintainable = document.getOldMaintainableObject();
1078                    Maintainable newMaintainable = document.getNewMaintainableObject();
1079    
1080                    String collectionName = extractCollectionName(request, KRADConstants.TOGGLE_INACTIVE_METHOD);
1081                    if (collectionName == null) {
1082                            LOG.error("Unable to get find collection name in request.");
1083                            throw new RuntimeException("Unable to get find collection class in request.");
1084                    }  
1085    
1086                    String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
1087                    boolean showInactive = Boolean.parseBoolean(StringUtils.substringBetween(parameterName, KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, "."));
1088    
1089                    oldMaintainable.setShowInactiveRecords(collectionName, showInactive);
1090                    newMaintainable.setShowInactiveRecords(collectionName, showInactive);
1091    
1092                    return mapping.findForward(RiceConstants.MAPPING_BASIC);
1093            }
1094    
1095            /**
1096             * This method clears the value of the primary key fields on a Business Object.
1097             * 
1098             * @param document - document to clear the pk fields on
1099             */
1100        protected void clearPrimaryKeyFields(MaintenanceDocument document) {
1101                    // get business object being maintained and its keys
1102                    PersistableBusinessObject bo = document.getNewMaintainableObject().getBusinessObject();
1103                    List<String> keyFieldNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(bo.getClass());
1104    
1105                    for (String keyFieldName : keyFieldNames) {
1106                            try {
1107                                    ObjectUtils.setObjectProperty(bo, keyFieldName, null);
1108                            }
1109                            catch (Exception e) {
1110                                    LOG.error("Unable to clear primary key field: " + e.getMessage());
1111                                    throw new RuntimeException("Unable to clear primary key field: " + e.getMessage());
1112                            }
1113                    }
1114            bo.setObjectId(null);
1115            bo.setVersionNumber(new Long(1));
1116            }
1117    
1118            /**
1119             * This method is used as part of the Copy functionality, to clear any field values that the user making the copy does not have
1120             * permissions to modify. This will prevent authorization errors on a copy.
1121             * 
1122             * @param document - document to be adjusted
1123             */
1124        protected void clearUnauthorizedNewFields(MaintenanceDocument document) {
1125                    // get a reference to the current user
1126                    Person user = GlobalVariables.getUserSession().getPerson();
1127    
1128                    // get the correct documentAuthorizer for this document
1129                    MaintenanceDocumentAuthorizer documentAuthorizer = (MaintenanceDocumentAuthorizer) getDocumentHelperService().getDocumentAuthorizer(document);
1130    
1131                    // get a new instance of MaintenanceDocumentAuthorizations for this context
1132                    MaintenanceDocumentRestrictions maintenanceDocumentRestrictions = getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(document, user);
1133    
1134                    // get a reference to the newBo
1135                    PersistableBusinessObject newBo = document.getNewMaintainableObject().getBusinessObject();
1136    
1137                    document.getNewMaintainableObject().clearBusinessObjectOfRestrictedValues(maintenanceDocumentRestrictions);
1138            }
1139    
1140            /**
1141             * This method does all special processing on a document that should happen on each HTTP post (ie, save, route, approve, etc).
1142             * 
1143             * @param form
1144             */
1145            @SuppressWarnings("unchecked")
1146            protected void doProcessingAfterPost( KualiForm form, HttpServletRequest request ) {
1147                    MaintenanceDocument document = (MaintenanceDocument) ((KualiMaintenanceForm)form).getDocument();
1148                    Maintainable maintainable = document.getNewMaintainableObject();
1149                    PersistableBusinessObject bo = maintainable.getBusinessObject();
1150    
1151                    getBusinessObjectService().linkUserFields(bo);
1152    
1153                    maintainable.processAfterPost(document, request.getParameterMap() );
1154            }
1155    
1156            protected void doProcessingAfterPost( KualiForm form, Map<String,String[]> parameters ) {
1157                    MaintenanceDocument document = (MaintenanceDocument) ((KualiMaintenanceForm)form).getDocument();
1158                    Maintainable maintainable = document.getNewMaintainableObject();
1159                    PersistableBusinessObject bo = maintainable.getBusinessObject();
1160    
1161                    getBusinessObjectService().linkUserFields(bo);
1162    
1163                    maintainable.processAfterPost(document, parameters );
1164            }
1165    
1166            protected void populateAuthorizationFields(KualiDocumentFormBase formBase){
1167                    super.populateAuthorizationFields(formBase);
1168    
1169                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) formBase;
1170                    MaintenanceDocument maintenanceDocument = (MaintenanceDocument) maintenanceForm.getDocument();
1171                    MaintenanceDocumentAuthorizer maintenanceDocumentAuthorizer = (MaintenanceDocumentAuthorizer) getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument);
1172                    Person user = GlobalVariables.getUserSession().getPerson();
1173                    maintenanceForm.setReadOnly(!formBase.getDocumentActions().containsKey(KRADConstants.KUALI_ACTION_CAN_EDIT));
1174                    MaintenanceDocumentRestrictions maintenanceDocumentAuthorizations = getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(maintenanceDocument, user);
1175                    maintenanceForm.setAuthorizations(maintenanceDocumentAuthorizations);
1176            }
1177    
1178            public LookupService getLookupService() {
1179                    if ( lookupService == null ) {
1180                            lookupService = KRADServiceLocatorWeb.getLookupService();
1181                    }
1182                    return this.lookupService;
1183            }
1184    
1185            public LookupResultsService getLookupResultsService() {
1186                    if ( lookupResultsService == null ) {
1187                            lookupResultsService = KNSServiceLocator.getLookupResultsService();
1188                    }
1189                    return this.lookupResultsService;
1190            }
1191    
1192    }