View Javadoc
1   /**
2    * Copyright 2005-2016 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.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.apache.log4j.MDC;
22  import org.apache.struts.Globals;
23  import org.apache.struts.action.*;
24  import org.apache.struts.config.FormBeanConfig;
25  import org.apache.struts.config.ForwardConfig;
26  import org.apache.struts.util.RequestUtils;
27  import org.kuali.rice.core.api.CoreApiServiceLocator;
28  import org.kuali.rice.core.api.config.property.ConfigurationService;
29  import org.kuali.rice.core.api.util.RiceConstants;
30  import org.kuali.rice.core.api.util.RiceKeyConstants;
31  import org.kuali.rice.kns.exception.FileUploadLimitExceededException;
32  import org.kuali.rice.kns.service.KNSServiceLocator;
33  import org.kuali.rice.kns.service.SessionDocumentService;
34  import org.kuali.rice.kns.util.*;
35  import org.kuali.rice.kns.web.EditablePropertiesHistoryHolder;
36  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
37  import org.kuali.rice.kns.web.struts.form.KualiForm;
38  import org.kuali.rice.kns.web.struts.form.pojo.PojoForm;
39  import org.kuali.rice.krad.UserSession;
40  import org.kuali.rice.krad.document.Document;
41  import org.kuali.rice.krad.exception.ValidationException;
42  import org.kuali.rice.krad.util.*;
43  import org.springframework.dao.OptimisticLockingFailureException;
44  import org.springframework.transaction.PlatformTransactionManager;
45  import org.springframework.transaction.TransactionStatus;
46  import org.springframework.transaction.support.TransactionCallback;
47  import org.springframework.transaction.support.TransactionTemplate;
48  import org.springmodules.orm.ojb.OjbOperationException;
49  
50  import javax.servlet.ServletException;
51  import javax.servlet.http.HttpServletRequest;
52  import javax.servlet.http.HttpServletResponse;
53  import javax.servlet.http.HttpSession;
54  import java.io.IOException;
55  
56  /**
57   * This class handles setup of user session and restoring of action form.
58   * 
59   * @deprecated KNS Struts deprecated, use KRAD and the Spring MVC framework.
60   */
61  @Deprecated
62  public class KualiRequestProcessor extends RequestProcessor {
63  	
64  	private static final String MDC_DOC_ID = "docId";
65  	private static final String PREVIOUS_REQUEST_EDITABLE_PROPERTIES_GUID_PARAMETER_NAME = "actionEditablePropertiesGuid";
66  
67  	private static Logger LOG = Logger.getLogger(KualiRequestProcessor.class);
68  
69  	private SessionDocumentService sessionDocumentService;
70  	private PlatformTransactionManager transactionManager;
71  
72      @Override
73      public void process(final HttpServletRequest request,
74              final HttpServletResponse response) throws IOException, ServletException {
75          // indicates that we are running in legacy KNS context
76          LegacyUtils.beginLegacyContext();
77          try {
78              if (LOG.isInfoEnabled()) {
79                  LOG.info(new StringBuffer("Started processing request: '").append(request.getRequestURI()).append(
80                          "' w/ query string: '").append(request.getQueryString()).append("'"));
81              }
82  
83              try {
84                  strutsProcess(request, response);
85              } catch (FileUploadLimitExceededException e) {
86                  ActionForward actionForward = processException(request, response, e, e.getActionForm(),
87                          e.getActionMapping());
88                  processForwardConfig(request, response, actionForward);
89              } finally {
90                  KNSGlobalVariables.setKualiForm(null);
91              }
92  
93              try {
94                  ActionForm form = WebUtils.getKualiForm(request);
95  
96                  if (form != null && form instanceof KualiDocumentFormBase) {
97                      String docId = ((KualiDocumentFormBase) form).getDocId();
98                      if (docId != null) {
99                          MDC.put(MDC_DOC_ID, docId);
100                     }
101                 }
102 
103                 String refreshCaller = request.getParameter(KRADConstants.REFRESH_CALLER);
104                 if (form != null && KualiDocumentFormBase.class.isAssignableFrom(form.getClass()) && !KRADConstants
105                         .QUESTION_REFRESH.equalsIgnoreCase(refreshCaller)) {
106                     KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
107                     Document document = docForm.getDocument();
108                     String docFormKey = docForm.getFormKey();
109 
110                     UserSession userSession = (UserSession) request.getSession().getAttribute(
111                             KRADConstants.USER_SESSION_KEY);
112 
113                     if (WebUtils.isDocumentSession(document, docForm)) {
114                         getSessionDocumentService().setDocumentForm(docForm, userSession, request.getRemoteAddr());
115                     }
116 
117                     Boolean exitingDocument = (Boolean) request.getAttribute(KRADConstants.EXITING_DOCUMENT);
118 
119                     if (exitingDocument != null && exitingDocument.booleanValue()) {
120                         // remove KualiDocumentFormBase object from session and
121                         // table.
122                         getSessionDocumentService().purgeDocumentForm(docForm.getDocument().getDocumentNumber(),
123                                 docFormKey, userSession, request.getRemoteAddr());
124                     }
125                 }
126 
127                 if (LOG.isInfoEnabled()) {
128                     LOG.info(new StringBuffer("Finished processing request: '").append(request.getRequestURI()).append(
129                             "' w/ query string: '").append(request.getQueryString()).append("'"));
130                 }
131 
132             } finally {
133                 // MDC docId key is set above, and also during super.process() in the call to processActionForm
134                 MDC.remove(MDC_DOC_ID);
135             }
136         } finally {
137             LegacyUtils.endLegacyContext();
138         }
139     }
140 
141     @Override
142 	protected boolean processPreprocess(HttpServletRequest request, HttpServletResponse response) {
143         final UserSession session = KRADUtils.getUserSessionFromRequest(request);
144         
145         if (session == null) {
146         	throw new IllegalStateException("the user session has not been established");
147         }
148     	GlobalVariables.setUserSession(session);
149     	KNSGlobalVariables.clear();
150 		return true;
151 	}
152 	
153 	/**
154      * <p>ProcessDefinition an <code>HttpServletRequest</code> and create the
155      * corresponding <code>HttpServletResponse</code> or dispatch
156      * to another resource.</p>
157      *
158      * @param request The servlet request we are processing
159      * @param response The servlet response we are creating
160      *
161      * @exception IOException if an input/output error occurs
162      * @exception ServletException if a processing exception occurs
163      */
164     public void strutsProcess(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
165 
166         // Wrap multipart requests with a special wrapper
167         request = processMultipart(request);
168 
169         // Identify the path component we will use to select a mapping
170         String path = processPath(request, response);
171         if (path == null) {
172             return;
173         }
174         
175         if (log.isDebugEnabled()) {
176             log.debug("Processing a '" + request.getMethod() +
177                       "' for path '" + path + "'");
178         }
179 
180         // Select a Locale for the current user if requested
181         processLocale(request, response);
182 
183         // Set the content type and no-caching headers if requested
184         processContent(request, response);
185         processNoCache(request, response);
186 
187         // General purpose preprocessing hook
188         if (!processPreprocess(request, response)) {
189             return;
190         }
191         
192         this.processCachedMessages(request, response);
193 
194         // Identify the mapping for this request
195         ActionMapping mapping = processMapping(request, response, path);
196         if (mapping == null) {
197             return;
198         }
199 
200         // Check for any role required to perform this action
201         if (!processRoles(request, response, mapping)) {
202             return;
203         }
204 
205         processFormActionAndForward(request, response, mapping);
206 
207     }
208 
209     public void processFormActionAndForward(final HttpServletRequest request, final HttpServletResponse response, final ActionMapping mapping) throws ServletException, IOException {
210     	ActionForm form = processActionForm(request, response, mapping);
211         processPopulate(request, response, form, mapping);
212 
213         // need to make sure that we don't check CSRF until after the form is populated so that Struts will parse the
214         // multipart parameters into the request if it's a multipart request
215         if (!CsrfValidator.validateCsrf(request, response)) {
216             return;
217         }
218 
219         // Create or acquire the Action instance to process this request
220 		Action action = processActionCreate(request, response, mapping);
221 
222         if (action != null) {
223             // Call the Action instance itself
224 		    ActionForward forward = processActionPerform(request, response, action, form, mapping);
225 
226             if (forward != null) {
227                 if (forward.getRedirect() && forward.getName()!= null && forward.getName().equals(KRADConstants.KRAD_INITIATED_DOCUMENT_VIEW_NAME)) {
228                     LOG.info("Attempt to open a document with a status of \"Initiated\" detected");
229                     return;
230                 }
231                 // ProcessDefinition the returned ActionForward instance
232 			    processForwardConfig(request, response, forward);
233             }
234         }
235     }
236 
237 
238 	/**
239 	 * This method gets the document number from the request.  The request should have been processed already 
240 	 * before this is called if it is multipart.  
241 	 * 
242 	 * @param request
243 	 * @return the document number, or null if one can't be found in the request.
244 	 */
245 	private String getDocumentNumber(HttpServletRequest request) {
246 		String documentNumber = request.getParameter(KRADConstants.DOCUMENT_DOCUMENT_NUMBER);
247 
248 		// from lookup pages.
249 		if (documentNumber == null) {
250 			documentNumber = request.getParameter(KRADConstants.DOC_NUM);
251 		}
252 		
253 		if (documentNumber == null) {
254 			documentNumber = request.getParameter("documentId");
255 		}
256 		
257 		return documentNumber;
258 	}
259 
260 	/**
261 	 * Hooks into populate process to call form populate method if form is an
262 	 * instanceof PojoForm.
263 	 */
264 	@Override
265 	protected void processPopulate(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping) throws ServletException {
266 		if (form instanceof KualiForm) {
267 			// Add the ActionForm to GlobalVariables
268 			// This will allow developers to retrieve both the Document and any
269 			// request parameters that are not
270 			// part of the Form and make them available in ValueFinder classes
271 			// and other places where they are needed.
272 			KNSGlobalVariables.setKualiForm((KualiForm) form);
273 		}
274 
275 		// if not PojoForm, call struts populate
276 		if (!(form instanceof PojoForm)) {
277 			super.processPopulate(request, response, form, mapping);
278 			return;
279 		}
280 		
281 		final String previousRequestGuid = request.getParameter(KualiRequestProcessor.PREVIOUS_REQUEST_EDITABLE_PROPERTIES_GUID_PARAMETER_NAME);
282 
283 		((PojoForm)form).clearEditablePropertyInformation();
284 		((PojoForm)form).registerStrutsActionMappingScope(mapping.getScope());
285 		
286 		String multipart = mapping.getMultipartClass();
287 		if (multipart != null) {
288 			request.setAttribute(Globals.MULTIPART_KEY, multipart);
289 		}
290 
291 		form.setServlet(this.servlet);
292 		form.reset(mapping, request);
293 
294 		((PojoForm)form).setPopulateEditablePropertiesGuid(previousRequestGuid);
295 		// call populate on ActionForm
296 		((PojoForm) form).populate(request);
297 		request.setAttribute("UnconvertedValues", ((PojoForm) form).getUnconvertedValues().keySet());
298 		request.setAttribute("UnconvertedHash", ((PojoForm) form).getUnconvertedValues());
299 	}
300 
301 	/**
302 	 * Hooks into validate to catch any errors from the populate, and translate
303 	 * the ErrorMap to ActionMessages.
304 	 */
305 	@Override
306 	protected boolean processValidate(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping) throws IOException, ServletException, InvalidCancelException {
307 
308 		// skip form validate if we had errors from populate
309 		if (GlobalVariables.getMessageMap().hasNoErrors()) {
310 			if (form == null) {
311 				return (true);
312 			}
313 			// Was this request cancelled?
314 			if (request.getAttribute(Globals.CANCEL_KEY) != null) {
315 				if (LOG.isDebugEnabled()) {
316 					LOG.debug(" Cancelled transaction, skipping validation");
317 				}
318 				return (true);
319 			}
320 
321 			// Has validation been turned off for this mapping?
322 			if (!mapping.getValidate()) {
323 				return (true);
324 			}
325 
326 			// call super to call forms validate
327 			super.processValidate(request, response, form, mapping);
328 		}
329 
330 		publishMessages(request);
331 		if (!GlobalVariables.getMessageMap().hasNoErrors()) {
332 			// Special handling for multipart request
333 			if (form.getMultipartRequestHandler() != null) {
334 				if (LOG.isDebugEnabled()) {
335 					LOG.debug("  Rolling back multipart request");
336 				}
337 				form.getMultipartRequestHandler().rollback();
338 			}
339 
340 			// Fix state that could be incorrect because of validation failure
341 			if (form instanceof PojoForm) {
342 				((PojoForm) form).processValidationFail();
343 			}
344 
345 			// Was an input path (or forward) specified for this mapping?
346 			String input = mapping.getInput();
347 			if (input == null) {
348 				if (LOG.isDebugEnabled()) {
349 					LOG.debug("  Validation failed but no input form available");
350 				}
351 				response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, getInternal().getMessage("noInput", mapping.getPath()));
352 				return (false);
353 			}
354 
355 			if (moduleConfig.getControllerConfig().getInputForward()) {
356 				ForwardConfig forward = mapping.findForward(input);
357 				processForwardConfig(request, response, forward);
358 			} else {
359 				internalModuleRelativeForward(input, request, response);
360 			}
361 
362 			return (false);
363 		}
364 		return true;
365 	}
366 
367 	/**
368 	 * Checks for return from a lookup or question, and restores the action form
369 	 * stored under the request parameter docFormKey.
370 	 */
371 	@Override
372 	protected ActionForm processActionForm(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) {
373 		
374 		String documentNumber = getDocumentNumber(request);
375 		if (documentNumber != null) { MDC.put(MDC_DOC_ID, documentNumber); }
376 		
377 		UserSession userSession = (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY);
378 
379 		String docFormKey = request.getParameter(KRADConstants.DOC_FORM_KEY);
380 		String methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
381 		String refreshCaller = request.getParameter(KRADConstants.REFRESH_CALLER);
382 //		String searchListRequestKey = request.getParameter(KRADConstants.SEARCH_LIST_REQUEST_KEY);
383 		String documentWebScope = request.getParameter(KRADConstants.DOCUMENT_WEB_SCOPE);
384 
385 		if (mapping.getPath().startsWith(KRADConstants.REFRESH_MAPPING_PREFIX) || KRADConstants.RETURN_METHOD_TO_CALL.equalsIgnoreCase(methodToCall) ||
386 				KRADConstants.QUESTION_REFRESH.equalsIgnoreCase(refreshCaller) || KRADConstants.TEXT_AREA_REFRESH.equalsIgnoreCase(refreshCaller) || KRADConstants
387                 .SESSION_SCOPE.equalsIgnoreCase(documentWebScope)) {
388 			ActionForm form = null;
389 			// check for search result storage and clear
390 			GlobalVariables.getUserSession().removeObjectsByPrefix(KRADConstants.SEARCH_LIST_KEY_PREFIX);
391 
392 			// We put different type of forms such as document form, lookup form
393 			// in session but we only store document form in
394 			// database.
395 			if (userSession.retrieveObject(docFormKey) != null) {
396 				LOG.debug("getDecomentForm KualiDocumentFormBase from session");
397 				form = (ActionForm) userSession.retrieveObject(docFormKey);
398 			} else if (StringUtils.isNotBlank(documentNumber)) {
399 				form = getSessionDocumentService().getDocumentForm(documentNumber, docFormKey, userSession, request.getRemoteAddr());
400 			}
401 			request.setAttribute(mapping.getAttribute(), form);
402 			if (!KRADConstants.SESSION_SCOPE.equalsIgnoreCase(documentWebScope)) {
403 				userSession.removeObject(docFormKey);
404 			}
405 			// we should check whether this is a multipart request because we
406 			// could have had a combination of query parameters and a multipart
407 			// request
408 			String contentType = request.getContentType();
409 			String method = request.getMethod();
410 			if (("POST".equalsIgnoreCase(method) && contentType != null && contentType.startsWith("multipart/form-data"))) {
411 				// this method parses the multipart request and adds new
412 				// non-file parameters into the request
413 				WebUtils.getMultipartParameters(request, null, form, mapping);
414 			}
415 			// The form can be null if the document is not a session document
416 			if (form != null) {
417 				return form;
418 			}
419 		}
420 
421 		// Rice has the ability to limit file upload sizes on a per-form basis,
422 		// so the max upload sizes may be accessed by calling methods on
423 		// PojoFormBase.
424 		// This requires that we are able know the file upload size limit (i.e.
425 		// retrieve a form instance) before we parse a mulitpart request.
426 		ActionForm form = super.processActionForm(request, response, mapping);
427 
428 		// for sessiondocument with multipart request
429 		String contentType = request.getContentType();
430 		String method = request.getMethod();
431 
432 		if ("GET".equalsIgnoreCase(method) && StringUtils.isNotBlank(methodToCall) && form instanceof PojoForm &&
433 				((PojoForm) form).getMethodToCallsToBypassSessionRetrievalForGETRequests().contains(methodToCall)) {
434 			return createNewActionForm(mapping, request);
435 		}
436 		
437 		// if we have a multipart request, parse it and return the stored form
438 		// from session if the doc form key is not blank. If it is blank, then
439 		// we just return the form
440 		// generated from the superclass processActionForm method. Either way,
441 		// we need to parse the mulitpart request now so that we may determine
442 		// what the value of the doc form key is.
443 		// This is generally against the contract of processActionForm, because
444 		// processPopulate should be responsible for parsing the mulitpart
445 		// request, but we need to parse it now
446 		// to determine the doc form key value.
447 		if (("POST".equalsIgnoreCase(method) && contentType != null && contentType.startsWith("multipart/form-data"))) {
448 			WebUtils.getMultipartParameters(request, null, form, mapping);
449 			docFormKey = request.getParameter(KRADConstants.DOC_FORM_KEY);
450 			documentWebScope = request.getParameter(KRADConstants.DOCUMENT_WEB_SCOPE);
451 
452 			documentNumber = getDocumentNumber(request);
453 
454 			if (KRADConstants.SESSION_SCOPE.equalsIgnoreCase(documentWebScope) ||
455 					(form instanceof KualiDocumentFormBase && WebUtils
456                             .isDocumentSession(((KualiDocumentFormBase) form).getDocument(),
457                                     (KualiDocumentFormBase) form))) {
458 
459 				Object userSessionObject = userSession.retrieveObject(docFormKey);
460 				if ( userSessionObject != null &&  userSessionObject instanceof ActionForm ) {
461 					LOG.debug("getDocumentForm KualiDocumentFormBase from session");
462 					form = (ActionForm) userSessionObject;
463 				} else {
464 					ActionForm tempForm = getSessionDocumentService().getDocumentForm(documentNumber, docFormKey, userSession, request.getRemoteAddr());
465 					if ( tempForm != null ) {
466 						form = tempForm;
467 					}
468 				}
469 
470 				request.setAttribute(mapping.getAttribute(), form);
471 				if (form != null) {
472 					return form;
473 				}
474 			}
475 		}
476 		return form;
477 	}
478 
479 	/**
480 	 * Hook into action perform to handle errors in the error map and catch
481 	 * exceptions.
482 	 * 
483 	 * <p>
484 	 * A transaction is started prior to the execution of the action. This
485 	 * allows for the action code to execute efficiently without the need for
486 	 * using PROPAGATION_SUPPORTS in the transaction definitions. The
487 	 * PROPAGATION_SUPPORTS propagation type does not work well with JTA.
488 	 */
489 	@Override
490 	protected ActionForward processActionPerform(final HttpServletRequest request, final HttpServletResponse response, final Action action, final ActionForm form, final ActionMapping mapping) throws IOException, ServletException {
491 		try {
492             TransactionTemplate template = new TransactionTemplate(getTransactionManager());
493 			ActionForward forward = null;
494 			try {
495 				forward = (ActionForward) template.execute(new TransactionCallback() {
496 					public Object doInTransaction(TransactionStatus status) {
497 						ActionForward actionForward = null;
498 						try {
499 							actionForward = action.execute(mapping, form, request, response);
500 						} catch (Exception e) {
501                             if (e.getMessage()!= null && e.getMessage().equals(KRADConstants.KRAD_INITIATED_DOCUMENT_VIEW_NAME)) {
502                                 ConfigurationService kualiConfigurationService = CoreApiServiceLocator
503                                         .getKualiConfigurationService();
504                                 StringBuffer sb = new StringBuffer();
505                                 sb.append(kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_URL_KEY));
506                                 sb.append(kualiConfigurationService.getPropertyValueAsString(KRADConstants.KRAD_INITIATED_DOCUMENT_URL_KEY));
507                                 return new ActionForward(KRADConstants.KRAD_INITIATED_DOCUMENT_VIEW_NAME, sb.toString() ,true);
508                             }
509 							// the doInTransaction method has no means for
510 							// throwing exceptions, so we will wrap the
511 							// exception in
512 							// a RuntimeException and re-throw. The one caveat
513 							// here is that this will always result in
514 							// the
515 							// transaction being rolled back (since
516 							// WrappedRuntimeException is a runtime exception).
517 							throw new WrappedRuntimeException(e);
518 						}
519 						if (status.isRollbackOnly()) {
520 							// this means that the struts action execution
521 							// caused the transaction to rollback, we want to
522 							// go ahead
523 							// and trigger the rollback by throwing an exception
524 							// here but then return the action forward
525 							// from this method
526 							throw new WrappedActionForwardRuntimeException(actionForward);
527 						}
528 						return actionForward;
529 					}
530 				});
531 			} catch (WrappedActionForwardRuntimeException e) {
532 				forward = e.getActionForward();
533 			}
534 
535 			publishMessages(request);
536 			saveMessages(request);
537 			saveAuditErrors(request);
538 
539 			if (form instanceof PojoForm) {
540 				if (((PojoForm)form).getEditableProperties() == null
541 						|| ((PojoForm)form).getEditableProperties().isEmpty()) {
542 					EditablePropertiesHistoryHolder holder = (EditablePropertiesHistoryHolder) GlobalVariables.getUserSession().getObjectMap().get(
543                             KRADConstants.EDITABLE_PROPERTIES_HISTORY_HOLDER_ATTR_NAME);
544 				    if (holder == null) {
545 				    	holder = new EditablePropertiesHistoryHolder();
546 				    }
547 
548 					final String guid = holder.addEditablePropertiesToHistory(((PojoForm)form).getEditableProperties());
549 				    ((PojoForm)form).setActionEditablePropertiesGuid(guid);
550 				    GlobalVariables.getUserSession().addObject(KRADConstants.EDITABLE_PROPERTIES_HISTORY_HOLDER_ATTR_NAME, holder);
551 				}
552 			}
553 
554 			return forward;
555 
556 		} catch (Exception e) {
557 			if (e instanceof WrappedRuntimeException) {
558 				e = (Exception) e.getCause();
559 			}
560 			if (e instanceof ValidationException) {
561 				// add a generic error message if there are none
562 				if (GlobalVariables.getMessageMap().hasNoErrors()) {
563 
564 					GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_CUSTOM, e.getMessage());
565 				}
566 
567 				if (form instanceof PojoForm) {
568 					if (((PojoForm)form).getEditableProperties() == null
569 							|| ((PojoForm)form).getEditableProperties().isEmpty()) {
570 					    EditablePropertiesHistoryHolder holder = (EditablePropertiesHistoryHolder) GlobalVariables.getUserSession().getObjectMap().get(
571                                 KRADConstants.EDITABLE_PROPERTIES_HISTORY_HOLDER_ATTR_NAME);
572 				        if (holder == null) {
573 				    	    holder = new EditablePropertiesHistoryHolder();
574 				        }
575 
576 					    final String guid = holder.addEditablePropertiesToHistory(((PojoForm)form).getEditableProperties());
577 				        ((PojoForm)form).setActionEditablePropertiesGuid(guid);
578 				        GlobalVariables.getUserSession().addObject(KRADConstants.EDITABLE_PROPERTIES_HISTORY_HOLDER_ATTR_NAME, holder);
579 					}
580 				}			
581 				// display error messages and return to originating page
582 				publishMessages(request);
583 				return mapping.findForward(RiceConstants.MAPPING_BASIC);
584 			}
585 
586 			publishMessages(request);
587 
588 			return (processException(request, response, e, form, mapping));
589 		}
590 	}
591 
592     private static class WrappedActionForwardRuntimeException extends RuntimeException {
593 		private ActionForward actionForward;
594 
595 		public WrappedActionForwardRuntimeException(ActionForward actionForward) {
596 			this.actionForward = actionForward;
597 		}
598 
599 		public ActionForward getActionForward() {
600 			return actionForward;
601 		}
602 	}
603 
604 	/**
605 	 * Adds more detailed logging for unhandled exceptions
606 	 * 
607 	 * @see org.apache.struts.action.RequestProcessor#processException(HttpServletRequest,
608 	 *      HttpServletResponse, Exception, ActionForm, ActionMapping)
609 	 */
610 	@Override
611 	protected ActionForward processException(HttpServletRequest request, HttpServletResponse response, Exception exception, ActionForm form, ActionMapping mapping) throws IOException, ServletException {
612 		ActionForward actionForward = null;
613 
614 		try {
615 			actionForward = super.processException(request, response, exception, form, mapping);
616 		} catch (IOException e) {
617 			logException(e);
618 			throw e;
619 		} catch (ServletException e) {
620 			// special case, to make OptimisticLockExceptions easier to read
621 			Throwable rootCause = e.getRootCause();
622 			if (rootCause instanceof OjbOperationException) {
623 				OjbOperationException ooe = (OjbOperationException) rootCause;
624 
625 				Throwable subcause = ooe.getCause();
626 				if ( subcause != null ) { 
627     				Object sourceObject = null;
628     				boolean optLockException = false;
629     				if ( subcause instanceof javax.persistence.OptimisticLockException ) {
630     				    javax.persistence.OptimisticLockException ole = (javax.persistence.OptimisticLockException) subcause;
631     				    sourceObject = ole.getEntity();
632     				    optLockException = true;
633     				} else if ( subcause instanceof OptimisticLockingFailureException ) {
634     				    OptimisticLockingFailureException ole = (OptimisticLockingFailureException) subcause;
635     				    sourceObject = ole.getMessage();
636     	                optLockException = true;
637     				} else {
638     				    if ( subcause.getClass().getName().equals( "org.apache.ojb.broker.OptimisticLockException" ) ) {
639     				        try {
640                                 sourceObject = PropertyUtils.getSimpleProperty(subcause, "sourceObject");
641                             } catch (Exception ex) {
642                                 LOG.warn( "Unable to retrieve source object from OJB OptimisticLockException", ex );
643                             }
644                             optLockException = true;
645                         }
646     				}
647     				if ( optLockException ) {
648                         StringBuilder message = new StringBuilder(e.getMessage());
649                         
650                         if (sourceObject != null) {
651                             if ( sourceObject instanceof String ) {
652                                 message.append(" Embedded Message: ").append( sourceObject );
653                             } else {
654                                 message.append(" (sourceObject is ");
655                                 message.append(sourceObject.getClass().getName());
656                                 message.append(")");
657                             }
658                         }
659     				    
660                         e = new ServletException(message.toString(), rootCause);
661     				}
662 				}
663 			}
664 
665 			logException(e);
666 			throw e;
667 		}
668 		return actionForward;
669 	}
670 
671 	private void logException(Exception e) {
672 		LOG.error("unhandled exception thrown by KualiRequestProcessor.processActionPerform", e);
673 	}
674 
675 	/**
676 	 * Checks for errors in the error map and transforms them to struts action
677 	 * messages then stores in the request.
678 	 */
679 	private void publishMessages(HttpServletRequest request) {
680 		MessageMap errorMap = GlobalVariables.getMessageMap();
681 		if (!errorMap.hasNoErrors()) {
682 			ErrorContainer errorContainer = new ErrorContainer(errorMap);
683 
684 			request.setAttribute("ErrorContainer", errorContainer);
685 			request.setAttribute(Globals.ERROR_KEY, errorContainer.getRequestErrors());
686 			request.setAttribute("ErrorPropertyList", errorContainer.getErrorPropertyList());
687 		}
688 		
689 		if (errorMap.hasWarnings()) {
690 			WarningContainer warningsContainer = new WarningContainer(errorMap);
691 			
692 			request.setAttribute("WarningContainer", warningsContainer);
693 			request.setAttribute("WarningActionMessages", warningsContainer.getRequestMessages());
694 			request.setAttribute("WarningPropertyList", warningsContainer.getMessagePropertyList());
695 		}
696 		
697 		if (errorMap.hasInfo()) {
698 			InfoContainer infoContainer = new InfoContainer(errorMap);
699 			
700 			request.setAttribute("InfoContainer", infoContainer);
701 			request.setAttribute("InfoActionMessages", infoContainer.getRequestMessages());
702 			request.setAttribute("InfoPropertyList", infoContainer.getMessagePropertyList());
703 		}
704 	}
705 
706 	/**
707 	 * Checks for messages in GlobalVariables and places list in request
708 	 * attribute.
709 	 */
710 	private void saveMessages(HttpServletRequest request) {
711 		if (!KNSGlobalVariables.getMessageList().isEmpty()) {
712 			request.setAttribute(KRADConstants.GLOBAL_MESSAGES, KNSGlobalVariables.getMessageList().toActionMessages());
713 		}
714 	}
715 
716 	/**
717 	 * Checks for messages in GlobalVariables and places list in request
718 	 * attribute.
719 	 */
720 	private void saveAuditErrors(HttpServletRequest request) {
721 		if (!GlobalVariables.getAuditErrorMap().isEmpty()) {
722 			request.setAttribute(KNSConstants.AUDIT_ERRORS, GlobalVariables.getAuditErrorMap());
723 		}
724 	}
725 
726 	/**
727 	 * A simple exception that allows us to wrap an exception that is thrown out
728 	 * of a transaction template.
729 	 */
730 	@SuppressWarnings("serial")
731 	private static class WrappedRuntimeException extends RuntimeException {
732 		public WrappedRuntimeException(Exception e) {
733 			super(e);
734 		}
735 	}
736 
737 	/**
738 	 * @return the sessionDocumentService
739 	 */
740 	public SessionDocumentService getSessionDocumentService() {
741 		if ( sessionDocumentService == null ) {
742 			sessionDocumentService = KNSServiceLocator.getSessionDocumentService();
743 		}
744 		return this.sessionDocumentService;
745 	}
746 
747 	/**
748 	 * @return the transactionManager
749 	 */
750 	public PlatformTransactionManager getTransactionManager() {
751 		if ( transactionManager == null ) {
752 			transactionManager = KNSServiceLocator.getTransactionManager();
753 		}
754 		return this.transactionManager;
755 	}
756 	
757 	private ActionForm createNewActionForm(ActionMapping mapping, HttpServletRequest request) {
758         String name = mapping.getName();
759         FormBeanConfig config = moduleConfig.findFormBeanConfig(name);
760         if (config == null) {
761             log.warn("No FormBeanConfig found under '" + name + "'");
762             return (null);
763         }
764         ActionForm instance = RequestUtils.createActionForm(config, servlet);
765         if ("request".equals(mapping.getScope())) {
766             request.setAttribute(mapping.getAttribute(), instance);
767         } else {
768             HttpSession session = request.getSession();
769             session.setAttribute(mapping.getAttribute(), instance);
770         }
771         return instance;
772 	}
773 
774 }