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