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