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