View Javadoc

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