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