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