001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.web.struts.action;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.collections.CollectionUtils;
020import org.apache.commons.lang.StringUtils;
021import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
022import org.apache.struts.action.ActionForm;
023import org.apache.struts.action.ActionForward;
024import org.apache.struts.action.ActionMapping;
025import org.apache.struts.upload.FormFile;
026import org.kuali.rice.core.api.CoreApiServiceLocator;
027import org.kuali.rice.core.api.encryption.EncryptionService;
028import org.kuali.rice.core.api.util.ClassLoaderUtils;
029import org.kuali.rice.core.api.util.RiceConstants;
030import org.kuali.rice.core.api.util.RiceKeyConstants;
031import org.kuali.rice.core.web.format.Formatter;
032import org.kuali.rice.kew.api.KewApiConstants;
033import org.kuali.rice.kim.api.identity.Person;
034import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
035import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
036import org.kuali.rice.kns.document.MaintenanceDocument;
037import org.kuali.rice.kns.document.MaintenanceDocumentBase;
038import org.kuali.rice.kns.document.authorization.MaintenanceDocumentAuthorizer;
039import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
040import org.kuali.rice.kns.lookup.LookupResultsService;
041import org.kuali.rice.kns.maintenance.Maintainable;
042import org.kuali.rice.kns.rule.event.KualiAddLineEvent;
043import org.kuali.rice.kns.service.KNSServiceLocator;
044import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
045import org.kuali.rice.kns.util.KNSGlobalVariables;
046import org.kuali.rice.kns.util.MaintenanceUtils;
047import org.kuali.rice.kns.util.WebUtils;
048import org.kuali.rice.kns.web.struts.form.InquiryForm;
049import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
050import org.kuali.rice.kns.web.struts.form.KualiForm;
051import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
052import org.kuali.rice.kns.web.ui.Field;
053import org.kuali.rice.kns.web.ui.Row;
054import org.kuali.rice.kns.web.ui.Section;
055import org.kuali.rice.krad.bo.DocumentAttachment;
056import org.kuali.rice.krad.bo.MultiDocumentAttachment;
057import org.kuali.rice.krad.bo.PersistableAttachment;
058import org.kuali.rice.krad.bo.PersistableAttachmentList;
059import org.kuali.rice.krad.bo.PersistableBusinessObject;
060import org.kuali.rice.krad.exception.DocumentTypeAuthorizationException;
061import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
062import org.kuali.rice.krad.service.LookupService;
063import org.kuali.rice.krad.util.GlobalVariables;
064import org.kuali.rice.krad.util.KRADConstants;
065import org.kuali.rice.krad.util.KRADPropertyConstants;
066import org.kuali.rice.krad.util.ObjectUtils;
067
068import javax.servlet.http.HttpServletRequest;
069import javax.servlet.http.HttpServletResponse;
070import java.lang.reflect.InvocationTargetException;
071import java.lang.reflect.Method;
072import java.security.GeneralSecurityException;
073import java.util.ArrayList;
074import java.util.Collection;
075import java.util.Enumeration;
076import java.util.HashMap;
077import java.util.Iterator;
078import java.util.List;
079import java.util.Map;
080
081/**
082 * This class handles actions for maintenance documents. These include creating new edit, and copying of maintenance records.
083 */
084public 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}