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