View Javadoc

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