View Javadoc

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