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