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.kew.routelog.web;
017
018import org.apache.struts.action.ActionForm;
019import org.apache.struts.action.ActionForward;
020import org.apache.struts.action.ActionMapping;
021import org.kuali.rice.kew.actionrequest.ActionRequestValue;
022import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
023import org.kuali.rice.kew.actiontaken.ActionTakenValue;
024import org.kuali.rice.kew.api.KewApiServiceLocator;
025import org.kuali.rice.kew.api.WorkflowRuntimeException;
026import org.kuali.rice.kew.api.action.ActionRequest;
027import org.kuali.rice.kew.api.action.ActionRequestStatus;
028import org.kuali.rice.kew.api.action.RoutingReportCriteria;
029import org.kuali.rice.kew.api.document.DocumentDetail;
030import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState;
031import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
032import org.kuali.rice.kew.doctype.SecuritySession;
033import org.kuali.rice.kew.doctype.service.DocumentSecurityService;
034import org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader;
035import org.kuali.rice.kew.engine.node.Branch;
036import org.kuali.rice.kew.engine.node.NodeState;
037import org.kuali.rice.kew.engine.node.RouteNode;
038import org.kuali.rice.kew.engine.node.RouteNodeInstance;
039import org.kuali.rice.kew.engine.node.service.RouteNodeService;
040import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
041import org.kuali.rice.kew.service.KEWServiceLocator;
042import org.kuali.rice.kew.util.Utilities;
043import org.kuali.rice.kew.web.KewKualiAction;
044import org.kuali.rice.krad.UserSession;
045import org.kuali.rice.krad.util.GlobalVariables;
046
047import javax.servlet.http.HttpServletRequest;
048import javax.servlet.http.HttpServletResponse;
049import java.util.ArrayList;
050import java.util.Collection;
051import java.util.Collections;
052import java.util.Comparator;
053import java.util.HashMap;
054import java.util.HashSet;
055import java.util.List;
056import java.util.Map;
057import java.util.Set;
058
059
060/**
061 * A Struts Action used to display the routelog.
062 *
063 * @author Kuali Rice Team (rice.collab@kuali.org)
064 */
065public class RouteLogAction extends KewKualiAction {
066
067    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RouteLogAction.class);
068    private static Comparator<ActionRequestValue> ROUTE_LOG_ACTION_REQUEST_SORTER = new Utilities.RouteLogActionRequestSorter();
069    
070    @Override
071        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
072
073        RouteLogForm rlForm = (RouteLogForm) form;
074        String documentId = null;
075        if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocumentId())) {
076                documentId = rlForm.getDocumentId();
077        } else if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocId())) {
078                documentId =rlForm.getDocId();
079        } else {
080                throw new WorkflowRuntimeException("No paramater provided to fetch document");
081        }
082
083        DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
084
085        DocumentSecurityService security = KEWServiceLocator.getDocumentSecurityService();
086        if (!security.routeLogAuthorized(getUserSession().getPrincipalId(), routeHeader, new SecuritySession(GlobalVariables.getUserSession().getPrincipalId()))) {
087          return mapping.findForward("NotAuthorized");
088        }
089        
090        fixActionRequestsPositions(routeHeader);
091        populateRouteLogFormActionRequests(rlForm, routeHeader);
092
093        rlForm.setLookFuture(routeHeader.getDocumentType().getLookIntoFuturePolicy().getPolicyValue().booleanValue());
094
095        if (rlForm.isShowFuture()) {
096            try {
097                populateRouteLogFutureRequests(rlForm, routeHeader);
098            } catch (Exception e) {
099                String errorMsg = "Unable to determine Future Action Requests";
100                LOG.info(errorMsg,e);
101                rlForm.setShowFutureError(errorMsg);
102            }
103        }
104        request.setAttribute("routeHeader", routeHeader);
105        
106                // check whether action message logging should be enabled, user must
107                // have KIM permission for doc type 
108        boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService()
109                                .canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader);
110                if (isAuthorizedToAddRouteLogMessage) {
111                        rlForm.setEnableLogAction(true);
112                } else {
113                        rlForm.setEnableLogAction(false);
114                }
115        
116        return super.execute(mapping, rlForm, request, response);
117    }
118
119    @SuppressWarnings("unchecked")
120        public void populateRouteLogFormActionRequests(RouteLogForm rlForm, DocumentRouteHeaderValue routeHeader) {
121        List<ActionRequestValue> rootRequests = getActionRequestService().getRootRequests(routeHeader.getActionRequests());
122        Collections.sort(rootRequests, ROUTE_LOG_ACTION_REQUEST_SORTER);
123        rootRequests = switchActionRequestPositionsIfPrimaryDelegatesPresent(rootRequests);
124        int arCount = 0;
125        for ( ActionRequestValue actionRequest : rootRequests ) {
126            if (actionRequest.isPending()) {
127                arCount++;
128
129                if (ActionRequestStatus.INITIALIZED.getCode().equals(actionRequest.getStatus())) {
130                    actionRequest.setDisplayStatus("PENDING");
131                } else if (ActionRequestStatus.ACTIVATED.getCode().equals(actionRequest.getStatus())) {
132                    actionRequest.setDisplayStatus("IN ACTION LIST");
133                }
134            }
135        }
136        rlForm.setRootRequests(rootRequests);
137        rlForm.setPendingActionRequestCount(arCount);
138    }
139
140    @SuppressWarnings("unchecked")
141        private ActionRequestValue switchActionRequestPositionIfPrimaryDelegatePresent( ActionRequestValue actionRequest ) {
142        
143        /**
144         * KULRICE-4756 - The main goal here is to fix the regression of what happened in Rice 1.0.2 with the display
145         * of primary delegate requests.  The delegate is displayed at the top-most level correctly on action requests
146         * that are "rooted" at a "role" request.
147         * 
148         * If they are rooted at a principal or group request, then the display of the primary delegator at the top-most
149         * level does not happen (instead it shows the delegator and you have to expand the request to see the primary
150         * delegate).
151         * 
152         * Ultimately, the KAI group and Rice BA need to come up with a specification for how the Route Log should
153         * display delegate information.  For now, will fix this so that in the non "role" case, it will put the
154         * primary delegate as the outermost request *except* in the case where there is more than one primary delegate.
155         */
156        
157        if (!actionRequest.isRoleRequest()) {
158                List<ActionRequestValue> primaryDelegateRequests = actionRequest.getPrimaryDelegateRequests();
159                // only display primary delegate request at top if there is only *one* primary delegate request
160                if ( primaryDelegateRequests.size() != 1) {
161                        return actionRequest;
162                }
163                ActionRequestValue primaryDelegateRequest = primaryDelegateRequests.get(0);
164                actionRequest.getChildrenRequests().remove(primaryDelegateRequest);
165                primaryDelegateRequest.setChildrenRequests(actionRequest.getChildrenRequests());
166                primaryDelegateRequest.setParentActionRequest(actionRequest.getParentActionRequest());
167                primaryDelegateRequest.setParentActionRequestId(actionRequest.getParentActionRequestId());
168                
169                actionRequest.setChildrenRequests( new ArrayList<ActionRequestValue>(0) );
170                actionRequest.setParentActionRequest(primaryDelegateRequest);
171                actionRequest.setParentActionRequestId(primaryDelegateRequest.getActionRequestId());
172                
173                primaryDelegateRequest.getChildrenRequests().add(0, actionRequest);
174                
175                for (ActionRequestValue delegateRequest : primaryDelegateRequest.getChildrenRequests()) {
176                        delegateRequest.setParentActionRequest(primaryDelegateRequest);
177                        delegateRequest.setParentActionRequestId(primaryDelegateRequest.getActionRequestId());
178                }
179                
180                return primaryDelegateRequest;
181        }
182        
183        return actionRequest;
184    }
185
186    private List<ActionRequestValue> switchActionRequestPositionsIfPrimaryDelegatesPresent( Collection<ActionRequestValue> actionRequests ) {
187        List<ActionRequestValue> results = new ArrayList<ActionRequestValue>( actionRequests.size() );
188        for ( ActionRequestValue actionRequest : actionRequests ) {
189                        results.add( switchActionRequestPositionIfPrimaryDelegatePresent(actionRequest) );
190        }
191        return results;
192    }
193    
194    @SuppressWarnings("unchecked")
195    private void fixActionRequestsPositions(DocumentRouteHeaderValue routeHeader) {
196        for (ActionTakenValue actionTaken : routeHeader.getActionsTaken()) {
197            Collections.sort((List<ActionRequestValue>) actionTaken.getActionRequests(), ROUTE_LOG_ACTION_REQUEST_SORTER);
198            actionTaken.setActionRequests( actionTaken.getActionRequests() );
199        }
200    }
201    
202    /**
203     * executes a simulation of the future routing, and sets the futureRootRequests and futureActionRequestCount
204     * properties on the provided RouteLogForm.
205     * 
206     * @param rlForm the RouteLogForm --used in a write-only fashion.
207     * @param document the DocumentRouteHeaderValue for the document whose future routing is being simulated.
208     * @throws Exception
209     */
210    public void populateRouteLogFutureRequests(RouteLogForm rlForm, DocumentRouteHeaderValue document) throws Exception {
211
212        RoutingReportCriteria reportCriteria = RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentId()).build();
213        String applicationId = document.getDocumentType().getApplicationId();
214
215        // gather the IDs for action requests that predate the simulation
216                Set<String> preexistingActionRequestIds = getActionRequestIds(document);
217        
218                // run the simulation
219        DocumentDetail documentDetail = KewApiServiceLocator.getWorkflowDocumentActionsService(applicationId).executeSimulation(reportCriteria);
220
221        // fabricate our ActionRequestValueS from the results
222        List<ActionRequestValue> futureActionRequests = 
223                reconstituteActionRequestValues(documentDetail, preexistingActionRequestIds);
224
225        Collections.sort(futureActionRequests, ROUTE_LOG_ACTION_REQUEST_SORTER);
226        
227        futureActionRequests = switchActionRequestPositionsIfPrimaryDelegatesPresent(futureActionRequests);
228        
229        int pendingActionRequestCount = 0;
230        for (ActionRequestValue actionRequest: futureActionRequests) {
231            if (actionRequest.isPending()) {
232                pendingActionRequestCount++;
233
234                if (ActionRequestStatus.INITIALIZED.getCode().equals(actionRequest.getStatus())) {
235                    actionRequest.setDisplayStatus("PENDING");
236                } else if (ActionRequestStatus.ACTIVATED.getCode().equals(actionRequest.getStatus())) {
237                    actionRequest.setDisplayStatus("IN ACTION LIST");
238                }
239            }
240        }
241
242        rlForm.setFutureRootRequests(futureActionRequests);
243        rlForm.setFutureActionRequestCount(pendingActionRequestCount);
244    }
245
246
247        /**
248         * This utility method returns a Set of LongS containing the IDs for the ActionRequestValueS associated with 
249         * this DocumentRouteHeaderValue. 
250         */
251        @SuppressWarnings("unchecked")
252        private Set<String> getActionRequestIds(DocumentRouteHeaderValue document) {
253                Set<String> actionRequestIds = new HashSet<String>();
254
255                List<ActionRequestValue> actionRequests = 
256                        KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(document.getDocumentId());
257                
258                if (actionRequests != null) {
259                        for (ActionRequestValue actionRequest : actionRequests) {
260                                if (actionRequest.getActionRequestId() != null) {
261                                        actionRequestIds.add(actionRequest.getActionRequestId());
262                                }
263                        }
264                }
265                return actionRequestIds;
266        }
267
268        /**
269         * This method creates ActionRequestValue objects from the DocumentDetailDTO output from
270         * 
271         * @param documentDetail contains the DTOs from which the ActionRequestValues are reconstituted
272         * @param preexistingActionRequestIds this is a Set of ActionRequest IDs that will not be reconstituted
273         * @return the ActionRequestValueS that have been created
274         */
275        private List<ActionRequestValue> reconstituteActionRequestValues(DocumentDetail documentDetail,
276                        Set<String> preexistingActionRequestIds) {
277
278        RouteNodeInstanceFabricator routeNodeInstanceFabricator = 
279                new RouteNodeInstanceFabricator(KEWServiceLocator.getRouteNodeService());
280
281        if (documentDetail.getRouteNodeInstances() != null && !documentDetail.getRouteNodeInstances().isEmpty()) {
282                for (org.kuali.rice.kew.api.document.node.RouteNodeInstance routeNodeInstanceVO : documentDetail.getRouteNodeInstances()) {
283                        routeNodeInstanceFabricator.importRouteNodeInstanceDTO(routeNodeInstanceVO);
284                }
285                }
286        
287        List<ActionRequest> actionRequestVOs = documentDetail.getActionRequests();
288        List<ActionRequestValue> futureActionRequests = new ArrayList<ActionRequestValue>();
289        if (actionRequestVOs != null) {
290                        for (ActionRequest actionRequestVO : actionRequestVOs) {
291                                if (actionRequestVO != null) {
292                                        if (!preexistingActionRequestIds.contains(actionRequestVO.getId())) {
293                                                ActionRequestValue converted = ActionRequestValue.from(actionRequestVO,
294                                routeNodeInstanceFabricator);
295                                                futureActionRequests.add(converted);
296                                        }
297                                }
298                        }
299                }
300                return futureActionRequests;
301        }
302    
303    private ActionRequestService getActionRequestService() {
304        return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
305    }
306    
307    private UserSession getUserSession() {
308        return GlobalVariables.getUserSession();
309    }
310    
311    /**
312     * Creates dummy RouteNodeInstances based on imported data from RouteNodeInstanceDTOs.
313     * It is then able to vend those RouteNodeInstanceS back by their IDs.
314     * 
315     * @author Kuali Rice Team (rice.collab@kuali.org)
316     *
317     */
318    private static class RouteNodeInstanceFabricator implements RouteNodeInstanceLoader {
319
320        private Map<String,Branch> branches = new HashMap<String, Branch>();
321        private Map<String, RouteNodeInstance> routeNodeInstances =
322                new HashMap<String, RouteNodeInstance>();
323        private Map<String,RouteNode> routeNodes = new HashMap<String, RouteNode>();
324        private Map<String,NodeState> nodeStates = new HashMap<String, NodeState>();
325
326        private RouteNodeService routeNodeService;
327        
328        /**
329                 * This constructs a FutureRouteNodeInstanceFabricator, which will generate bogus
330                 * RouteNodeInstances for SimulationEngine results
331                 * 
332                 */
333                public RouteNodeInstanceFabricator(RouteNodeService routeNodeService) {
334                        this.routeNodeService = routeNodeService;
335                }
336
337                /**
338                 * 
339                 * This method looks at the given RouteNodeInstanceDTO and imports it (and all it's ancestors)
340                 * as dummy RouteNodeInstanceS
341                 * 
342                 * @param nodeInstanceDTO
343                 */
344                public void importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
345                        _importRouteNodeInstanceDTO(nodeInstanceDTO);
346                }
347                
348                /**
349                 * helper method for {@link #importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance)} which does all
350                 * the work.  The public method just wraps this one but hides the returned RouteNodeInstance,
351                 * which is used for the recursive call to populate the nextNodeInstanceS inside our 
352                 * RouteNodeInstanceS.
353                 * 
354                 * @param nodeInstanceDTO
355                 * @return
356                 */
357        private RouteNodeInstance _importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
358                if (nodeInstanceDTO == null) {
359                        return null;
360                }
361                RouteNodeInstance nodeInstance = new RouteNodeInstance();
362                nodeInstance.setActive(nodeInstanceDTO.isActive());
363
364                nodeInstance.setComplete(nodeInstanceDTO.isComplete());
365                nodeInstance.setDocumentId(nodeInstanceDTO.getDocumentId());
366                nodeInstance.setInitial(nodeInstanceDTO.isInitial());
367
368                Branch branch = getBranch(nodeInstanceDTO.getBranchId());
369                nodeInstance.setBranch(branch);
370
371                if (nodeInstanceDTO.getRouteNodeId() != null) {
372                        RouteNode routeNode = routeNodeService.findRouteNodeById(nodeInstanceDTO.getRouteNodeId());
373
374                        if (routeNode == null) {
375                                routeNode = getRouteNode(nodeInstanceDTO.getRouteNodeId());
376                                routeNode.setNodeType(nodeInstanceDTO.getName());
377                        }
378
379                        nodeInstance.setRouteNode(routeNode);
380
381                        if (routeNode.getBranch() != null) {
382                                branch.setName(routeNode.getBranch().getName());
383                        } 
384                }
385
386                RouteNodeInstance process = getRouteNodeInstance(nodeInstanceDTO.getProcessId());
387                nodeInstance.setProcess(process);
388
389                nodeInstance.setRouteNodeInstanceId(nodeInstanceDTO.getId());
390
391                List<NodeState> nodeState = new ArrayList<NodeState>();
392                if (nodeInstanceDTO.getState() != null) {
393                                for (RouteNodeInstanceState stateDTO : nodeInstanceDTO.getState()) {
394                                        NodeState state = getNodeState(stateDTO.getId());
395                                        if (state != null) {
396                                                state.setKey(stateDTO.getKey());
397                                                state.setValue(stateDTO.getValue());
398                                                state.setStateId(stateDTO.getId());
399                                                state.setNodeInstance(nodeInstance);
400                                                nodeState.add(state);
401                                        }
402                                }
403                        }
404                nodeInstance.setState(nodeState);
405
406                List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>();
407
408
409                for (org.kuali.rice.kew.api.document.node.RouteNodeInstance nextNodeInstanceVO : nodeInstanceDTO.getNextNodeInstances()) {
410                        // recurse to populate nextNodeInstances
411                        nextNodeInstances.add(_importRouteNodeInstanceDTO(nextNodeInstanceVO));
412                }
413            nodeInstance.setNextNodeInstances(nextNodeInstances);
414
415                routeNodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
416                return nodeInstance;
417        }
418        
419                /**
420                 * This method returns a dummy RouteNodeInstance for the given ID, or null if it hasn't
421                 * imported from a RouteNodeInstanceDTO with that ID
422                 * 
423                 * @see org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader#load(String)
424                 */
425                @Override
426                public RouteNodeInstance load(String routeNodeInstanceID) {
427                        return routeNodeInstances.get(routeNodeInstanceID);
428                }
429
430
431        /**
432         * This method creates bogus BranchES as needed
433         * 
434         * @param branchId
435         * @return
436         */
437        private Branch getBranch(String branchId) {
438                Branch result = null;
439
440                if (branchId != null) {
441                        // if branch doesn't exist, create it
442                        if (!branches.containsKey(branchId)) {
443                                result = new Branch();
444                                result.setBranchId(branchId);
445                                branches.put(branchId, result);
446                        } else {
447                                result = branches.get(branchId);
448                        }
449                }
450                return result;
451        }
452
453        /**
454         * This method creates bogus RouteNodeS as needed
455         * 
456         * @param routeNodeId
457         * @return
458         */
459        private RouteNode getRouteNode(String routeNodeId) {
460                RouteNode result = null;
461
462                if (routeNodeId != null) {
463                        // if RouteNode doesn't exist, create it
464                        if (!routeNodes.containsKey(routeNodeId)) {
465                                result = new RouteNode();
466                                result.setRouteNodeId(routeNodeId);
467                                routeNodes.put(routeNodeId, result);
468                        } else {
469                                result = routeNodes.get(routeNodeId);
470                        }
471                }
472                return result;
473        }
474
475        /**
476         * This method creates bogus RouteNodeInstanceS as needed
477         * 
478         * @param routeNodeInstanceId
479         * @return
480         */
481        public RouteNodeInstance getRouteNodeInstance(String routeNodeInstanceId) {
482                RouteNodeInstance result = null;
483
484                if (routeNodeInstanceId != null) {
485                        // if RouteNodeInstance doesn't exist, create it
486                        if (!routeNodeInstances.containsKey(routeNodeInstanceId)) {
487                    result = new RouteNodeInstance();
488                                result.setRouteNodeInstanceId(routeNodeInstanceId);
489                                routeNodeInstances.put(routeNodeInstanceId, result);
490                        } else {
491                                result = routeNodeInstances.get(routeNodeInstanceId);
492                        }
493                }
494                return result;
495        }
496
497        /**
498         * This method creates bogus NodeStateS as needed
499         * 
500         * @param nodeStateId
501         * @return
502         */
503        private NodeState getNodeState(String nodeStateId) {
504                NodeState result = null;
505
506                if (nodeStateId != null) {
507                        // if NodeState doesn't exist, create it
508                        if (!nodeStates.containsKey(nodeStateId)) {
509                                result = new NodeState();
510                                result.setNodeStateId(nodeStateId);
511                                nodeStates.put(nodeStateId, result);
512                        } else {
513                                result = nodeStates.get(nodeStateId);
514                        }
515                }
516                return result;
517        }
518
519    } // end inner class FutureRouteNodeInstanceFabricator
520
521    /**
522     * Logs a new message to the route log for the current document, then refreshes the action taken list to display
523     * back the new message in the route log tab. User must have permission to log a message for the doc type and the
524     * request must be coming from the route log tab display (not the route log page).
525     */
526        public ActionForward logActionMessageInRouteLog(ActionMapping mapping, ActionForm form, HttpServletRequest request,
527                        HttpServletResponse response) throws Exception {
528                RouteLogForm routeLogForm = (RouteLogForm) form;
529
530                String documentId = null;
531                if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocumentId())) {
532                        documentId = routeLogForm.getDocumentId();
533                } else if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocId())) {
534                        documentId = routeLogForm.getDocId();
535                } else {
536                        throw new WorkflowRuntimeException("No paramater provided to fetch document");
537                }
538                
539                DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
540                
541                // check user has permission to add a route log message
542                boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService()
543                                .canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader);
544
545                if (!isAuthorizedToAddRouteLogMessage) {
546                        throw new InvalidActionTakenException("Principal with name '"
547                                        + GlobalVariables.getUserSession().getPrincipalName()
548                                        + "' is not authorized to add route log messages for documents of type '"
549                                        + routeHeader.getDocumentType().getName());
550                }
551
552                LOG.info("Logging new action message for user " + GlobalVariables.getUserSession().getPrincipalName()
553                                + ", route header " + routeHeader);
554                KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(
555                                GlobalVariables.getUserSession().getPrincipalId(), routeHeader,
556                                routeLogForm.getNewRouteLogActionMessage());
557
558                routeLogForm.setNewRouteLogActionMessage("");
559
560                // retrieve routeHeader again to pull new action taken
561                routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId, true);
562                fixActionRequestsPositions(routeHeader);
563                request.setAttribute("routeHeader", routeHeader);
564
565                return mapping.findForward(getDefaultMapping());
566        }
567    
568}