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