View Javadoc

1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.web.struts.action;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.collections.CollectionUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
22  import org.apache.struts.action.ActionForm;
23  import org.apache.struts.action.ActionForward;
24  import org.apache.struts.action.ActionMapping;
25  import org.apache.struts.upload.FormFile;
26  import org.kuali.rice.core.api.CoreApiServiceLocator;
27  import org.kuali.rice.core.api.encryption.EncryptionService;
28  import org.kuali.rice.core.api.util.ClassLoaderUtils;
29  import org.kuali.rice.core.api.util.RiceConstants;
30  import org.kuali.rice.core.api.util.RiceKeyConstants;
31  import org.kuali.rice.core.web.format.Formatter;
32  import org.kuali.rice.kew.api.KewApiConstants;
33  import org.kuali.rice.kim.api.identity.Person;
34  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
35  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
36  import org.kuali.rice.kns.document.MaintenanceDocument;
37  import org.kuali.rice.kns.document.MaintenanceDocumentBase;
38  import org.kuali.rice.kns.document.authorization.MaintenanceDocumentAuthorizer;
39  import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
40  import org.kuali.rice.kns.lookup.LookupResultsService;
41  import org.kuali.rice.kns.maintenance.Maintainable;
42  import org.kuali.rice.kns.rule.event.KualiAddLineEvent;
43  import org.kuali.rice.kns.service.KNSServiceLocator;
44  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
45  import org.kuali.rice.kns.util.KNSGlobalVariables;
46  import org.kuali.rice.kns.util.MaintenanceUtils;
47  import org.kuali.rice.kns.util.WebUtils;
48  import org.kuali.rice.kns.web.struts.form.InquiryForm;
49  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
50  import org.kuali.rice.kns.web.struts.form.KualiForm;
51  import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
52  import org.kuali.rice.kns.web.ui.Field;
53  import org.kuali.rice.kns.web.ui.Row;
54  import org.kuali.rice.kns.web.ui.Section;
55  import org.kuali.rice.krad.bo.DocumentAttachment;
56  import org.kuali.rice.krad.bo.MultiDocumentAttachment;
57  import org.kuali.rice.krad.bo.PersistableAttachment;
58  import org.kuali.rice.krad.bo.PersistableAttachmentList;
59  import org.kuali.rice.krad.bo.PersistableBusinessObject;
60  import org.kuali.rice.krad.exception.DocumentTypeAuthorizationException;
61  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
62  import org.kuali.rice.krad.service.LookupService;
63  import org.kuali.rice.krad.util.GlobalVariables;
64  import org.kuali.rice.krad.util.KRADConstants;
65  import org.kuali.rice.krad.util.KRADPropertyConstants;
66  import org.kuali.rice.krad.util.ObjectUtils;
67  
68  import javax.servlet.http.HttpServletRequest;
69  import javax.servlet.http.HttpServletResponse;
70  import java.lang.reflect.InvocationTargetException;
71  import java.lang.reflect.Method;
72  import java.security.GeneralSecurityException;
73  import java.util.ArrayList;
74  import java.util.Collection;
75  import java.util.Enumeration;
76  import java.util.HashMap;
77  import java.util.Iterator;
78  import java.util.List;
79  import java.util.Map;
80  
81  /**
82   * This class handles actions for maintenance documents. These include creating new edit, and copying of maintenance records.
83   */
84  public class KualiMaintenanceDocumentAction extends KualiDocumentActionBase {
85      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiMaintenanceDocumentAction.class);
86  
87      protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService = null;
88      protected EncryptionService encryptionService;
89      protected LookupService lookupService;
90      protected LookupResultsService lookupResultsService;
91  
92  	public KualiMaintenanceDocumentAction() {
93  		super();
94  		maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
95  		encryptionService = CoreApiServiceLocator.getEncryptionService();
96  	}
97  
98  	@Override
99  	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 }