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