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