View Javadoc

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