001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.kew.routelog.web;
017    
018    import org.apache.struts.action.ActionForm;
019    import org.apache.struts.action.ActionForward;
020    import org.apache.struts.action.ActionMapping;
021    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
022    import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
023    import org.kuali.rice.kew.actiontaken.ActionTakenValue;
024    import org.kuali.rice.kew.api.KewApiServiceLocator;
025    import org.kuali.rice.kew.api.WorkflowRuntimeException;
026    import org.kuali.rice.kew.api.action.ActionRequest;
027    import org.kuali.rice.kew.api.action.ActionRequestStatus;
028    import org.kuali.rice.kew.api.action.RoutingReportCriteria;
029    import org.kuali.rice.kew.api.document.DocumentDetail;
030    import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState;
031    import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
032    import org.kuali.rice.kew.doctype.SecuritySession;
033    import org.kuali.rice.kew.doctype.service.DocumentSecurityService;
034    import org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader;
035    import org.kuali.rice.kew.engine.node.Branch;
036    import org.kuali.rice.kew.engine.node.NodeState;
037    import org.kuali.rice.kew.engine.node.RouteNode;
038    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
039    import org.kuali.rice.kew.engine.node.service.RouteNodeService;
040    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
041    import org.kuali.rice.kew.service.KEWServiceLocator;
042    import org.kuali.rice.kew.util.Utilities;
043    import org.kuali.rice.kew.web.KewKualiAction;
044    import org.kuali.rice.krad.UserSession;
045    import org.kuali.rice.krad.util.GlobalVariables;
046    
047    import javax.servlet.http.HttpServletRequest;
048    import javax.servlet.http.HttpServletResponse;
049    import java.util.ArrayList;
050    import java.util.Collection;
051    import java.util.Collections;
052    import java.util.Comparator;
053    import java.util.HashMap;
054    import java.util.HashSet;
055    import java.util.List;
056    import java.util.Map;
057    import 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     */
065    public 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().executeSimulation(
220                    reportCriteria);
221    
222            // fabricate our ActionRequestValueS from the results
223            List<ActionRequestValue> futureActionRequests = 
224                    reconstituteActionRequestValues(documentDetail, preexistingActionRequestIds);
225    
226            Collections.sort(futureActionRequests, ROUTE_LOG_ACTION_REQUEST_SORTER);
227            
228            futureActionRequests = switchActionRequestPositionsIfPrimaryDelegatesPresent(futureActionRequests);
229            
230            int pendingActionRequestCount = 0;
231            for (ActionRequestValue actionRequest: futureActionRequests) {
232                if (actionRequest.isPending()) {
233                    pendingActionRequestCount++;
234    
235                    if (ActionRequestStatus.INITIALIZED.getCode().equals(actionRequest.getStatus())) {
236                        actionRequest.setDisplayStatus("PENDING");
237                    } else if (ActionRequestStatus.ACTIVATED.getCode().equals(actionRequest.getStatus())) {
238                        actionRequest.setDisplayStatus("IN ACTION LIST");
239                    }
240                }
241            }
242    
243            rlForm.setFutureRootRequests(futureActionRequests);
244            rlForm.setFutureActionRequestCount(pendingActionRequestCount);
245        }
246    
247    
248            /**
249             * This utility method returns a Set of LongS containing the IDs for the ActionRequestValueS associated with 
250             * this DocumentRouteHeaderValue. 
251             */
252            @SuppressWarnings("unchecked")
253            private Set<String> getActionRequestIds(DocumentRouteHeaderValue document) {
254                    Set<String> actionRequestIds = new HashSet<String>();
255    
256                    List<ActionRequestValue> actionRequests = 
257                            KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(document.getDocumentId());
258                    
259                    if (actionRequests != null) {
260                            for (ActionRequestValue actionRequest : actionRequests) {
261                                    if (actionRequest.getActionRequestId() != null) {
262                                            actionRequestIds.add(actionRequest.getActionRequestId());
263                                    }
264                            }
265                    }
266                    return actionRequestIds;
267            }
268    
269            /**
270             * This method creates ActionRequestValue objects from the DocumentDetailDTO output from
271             * 
272             * @param documentDetail contains the DTOs from which the ActionRequestValues are reconstituted
273             * @param preexistingActionRequestIds this is a Set of ActionRequest IDs that will not be reconstituted
274             * @return the ActionRequestValueS that have been created
275             */
276            private List<ActionRequestValue> reconstituteActionRequestValues(DocumentDetail documentDetail,
277                            Set<String> preexistingActionRequestIds) {
278    
279            RouteNodeInstanceFabricator routeNodeInstanceFabricator = 
280                    new RouteNodeInstanceFabricator(KEWServiceLocator.getRouteNodeService());
281    
282            if (documentDetail.getRouteNodeInstances() != null && !documentDetail.getRouteNodeInstances().isEmpty()) {
283                    for (org.kuali.rice.kew.api.document.node.RouteNodeInstance routeNodeInstanceVO : documentDetail.getRouteNodeInstances()) {
284                            routeNodeInstanceFabricator.importRouteNodeInstanceDTO(routeNodeInstanceVO);
285                    }
286                    }
287            
288            List<ActionRequest> actionRequestVOs = documentDetail.getActionRequests();
289            List<ActionRequestValue> futureActionRequests = new ArrayList<ActionRequestValue>();
290            if (actionRequestVOs != null) {
291                            for (ActionRequest actionRequestVO : actionRequestVOs) {
292                                    if (actionRequestVO != null) {
293                                            if (!preexistingActionRequestIds.contains(actionRequestVO.getId())) {
294                                                    ActionRequestValue converted = ActionRequestValue.from(actionRequestVO,
295                                    routeNodeInstanceFabricator);
296                                                    futureActionRequests.add(converted);
297                                            }
298                                    }
299                            }
300                    }
301                    return futureActionRequests;
302            }
303        
304        private ActionRequestService getActionRequestService() {
305            return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
306        }
307        
308        private UserSession getUserSession() {
309            return GlobalVariables.getUserSession();
310        }
311        
312        /**
313         * Creates dummy RouteNodeInstances based on imported data from RouteNodeInstanceDTOs.
314         * It is then able to vend those RouteNodeInstanceS back by their IDs.
315         * 
316         * @author Kuali Rice Team (rice.collab@kuali.org)
317         *
318         */
319        private static class RouteNodeInstanceFabricator implements RouteNodeInstanceLoader {
320    
321            private Map<String,Branch> branches = new HashMap<String, Branch>();
322            private Map<String, RouteNodeInstance> routeNodeInstances =
323                    new HashMap<String, RouteNodeInstance>();
324            private Map<String,RouteNode> routeNodes = new HashMap<String, RouteNode>();
325            private Map<String,NodeState> nodeStates = new HashMap<String, NodeState>();
326    
327            private RouteNodeService routeNodeService;
328            
329            /**
330                     * This constructs a FutureRouteNodeInstanceFabricator, which will generate bogus
331                     * RouteNodeInstances for SimulationEngine results
332                     * 
333                     */
334                    public RouteNodeInstanceFabricator(RouteNodeService routeNodeService) {
335                            this.routeNodeService = routeNodeService;
336                    }
337    
338                    /**
339                     * 
340                     * This method looks at the given RouteNodeInstanceDTO and imports it (and all it's ancestors)
341                     * as dummy RouteNodeInstanceS
342                     * 
343                     * @param nodeInstanceDTO
344                     */
345                    public void importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
346                            _importRouteNodeInstanceDTO(nodeInstanceDTO);
347                    }
348                    
349                    /**
350                     * helper method for {@link #importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance)} which does all
351                     * the work.  The public method just wraps this one but hides the returned RouteNodeInstance,
352                     * which is used for the recursive call to populate the nextNodeInstanceS inside our 
353                     * RouteNodeInstanceS.
354                     * 
355                     * @param nodeInstanceDTO
356                     * @return
357                     */
358            private RouteNodeInstance _importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
359                    if (nodeInstanceDTO == null) {
360                            return null;
361                    }
362                    RouteNodeInstance nodeInstance = new RouteNodeInstance();
363                    nodeInstance.setActive(nodeInstanceDTO.isActive());
364    
365                    nodeInstance.setComplete(nodeInstanceDTO.isComplete());
366                    nodeInstance.setDocumentId(nodeInstanceDTO.getDocumentId());
367                    nodeInstance.setInitial(nodeInstanceDTO.isInitial());
368    
369                    Branch branch = getBranch(nodeInstanceDTO.getBranchId());
370                    nodeInstance.setBranch(branch);
371    
372                    if (nodeInstanceDTO.getRouteNodeId() != null) {
373                            RouteNode routeNode = routeNodeService.findRouteNodeById(nodeInstanceDTO.getRouteNodeId());
374    
375                            if (routeNode == null) {
376                                    routeNode = getRouteNode(nodeInstanceDTO.getRouteNodeId());
377                                    routeNode.setNodeType(nodeInstanceDTO.getName());
378                            }
379    
380                            nodeInstance.setRouteNode(routeNode);
381    
382                            if (routeNode.getBranch() != null) {
383                                    branch.setName(routeNode.getBranch().getName());
384                            } 
385                    }
386    
387                    RouteNodeInstance process = getRouteNodeInstance(nodeInstanceDTO.getProcessId());
388                    nodeInstance.setProcess(process);
389    
390                    nodeInstance.setRouteNodeInstanceId(nodeInstanceDTO.getId());
391    
392                    List<NodeState> nodeState = new ArrayList<NodeState>();
393                    if (nodeInstanceDTO.getState() != null) {
394                                    for (RouteNodeInstanceState stateDTO : nodeInstanceDTO.getState()) {
395                                            NodeState state = getNodeState(stateDTO.getId());
396                                            if (state != null) {
397                                                    state.setKey(stateDTO.getKey());
398                                                    state.setValue(stateDTO.getValue());
399                                                    state.setStateId(stateDTO.getId());
400                                                    state.setNodeInstance(nodeInstance);
401                                                    nodeState.add(state);
402                                            }
403                                    }
404                            }
405                    nodeInstance.setState(nodeState);
406    
407                    List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>();
408    
409    
410                    for (org.kuali.rice.kew.api.document.node.RouteNodeInstance nextNodeInstanceVO : nodeInstanceDTO.getNextNodeInstances()) {
411                            // recurse to populate nextNodeInstances
412                            nextNodeInstances.add(_importRouteNodeInstanceDTO(nextNodeInstanceVO));
413                    }
414                nodeInstance.setNextNodeInstances(nextNodeInstances);
415    
416                    routeNodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
417                    return nodeInstance;
418            }
419            
420                    /**
421                     * This method returns a dummy RouteNodeInstance for the given ID, or null if it hasn't
422                     * imported from a RouteNodeInstanceDTO with that ID
423                     * 
424                     * @see org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader#load(String)
425                     */
426                    @Override
427                    public RouteNodeInstance load(String routeNodeInstanceID) {
428                            return routeNodeInstances.get(routeNodeInstanceID);
429                    }
430    
431    
432            /**
433             * This method creates bogus BranchES as needed
434             * 
435             * @param branchId
436             * @return
437             */
438            private Branch getBranch(String branchId) {
439                    Branch result = null;
440    
441                    if (branchId != null) {
442                            // if branch doesn't exist, create it
443                            if (!branches.containsKey(branchId)) {
444                                    result = new Branch();
445                                    result.setBranchId(branchId);
446                                    branches.put(branchId, result);
447                            } else {
448                                    result = branches.get(branchId);
449                            }
450                    }
451                    return result;
452            }
453    
454            /**
455             * This method creates bogus RouteNodeS as needed
456             * 
457             * @param routeNodeId
458             * @return
459             */
460            private RouteNode getRouteNode(String routeNodeId) {
461                    RouteNode result = null;
462    
463                    if (routeNodeId != null) {
464                            // if RouteNode doesn't exist, create it
465                            if (!routeNodes.containsKey(routeNodeId)) {
466                                    result = new RouteNode();
467                                    result.setRouteNodeId(routeNodeId);
468                                    routeNodes.put(routeNodeId, result);
469                            } else {
470                                    result = routeNodes.get(routeNodeId);
471                            }
472                    }
473                    return result;
474            }
475    
476            /**
477             * This method creates bogus RouteNodeInstanceS as needed
478             * 
479             * @param routeNodeInstanceId
480             * @return
481             */
482            public RouteNodeInstance getRouteNodeInstance(String routeNodeInstanceId) {
483                    RouteNodeInstance result = null;
484    
485                    if (routeNodeInstanceId != null) {
486                            // if RouteNodeInstance doesn't exist, create it
487                            if (!routeNodeInstances.containsKey(routeNodeInstanceId)) {
488                        result = new RouteNodeInstance();
489                                    result.setRouteNodeInstanceId(routeNodeInstanceId);
490                                    routeNodeInstances.put(routeNodeInstanceId, result);
491                            } else {
492                                    result = routeNodeInstances.get(routeNodeInstanceId);
493                            }
494                    }
495                    return result;
496            }
497    
498            /**
499             * This method creates bogus NodeStateS as needed
500             * 
501             * @param nodeStateId
502             * @return
503             */
504            private NodeState getNodeState(String nodeStateId) {
505                    NodeState result = null;
506    
507                    if (nodeStateId != null) {
508                            // if NodeState doesn't exist, create it
509                            if (!nodeStates.containsKey(nodeStateId)) {
510                                    result = new NodeState();
511                                    result.setNodeStateId(nodeStateId);
512                                    nodeStates.put(nodeStateId, result);
513                            } else {
514                                    result = nodeStates.get(nodeStateId);
515                            }
516                    }
517                    return result;
518            }
519    
520        } // end inner class FutureRouteNodeInstanceFabricator
521    
522        /**
523         * Logs a new message to the route log for the current document, then refreshes the action taken list to display
524         * back the new message in the route log tab. User must have permission to log a message for the doc type and the
525         * request must be coming from the route log tab display (not the route log page).
526         */
527            public ActionForward logActionMessageInRouteLog(ActionMapping mapping, ActionForm form, HttpServletRequest request,
528                            HttpServletResponse response) throws Exception {
529                    RouteLogForm routeLogForm = (RouteLogForm) form;
530    
531                    String documentId = null;
532                    if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocumentId())) {
533                            documentId = routeLogForm.getDocumentId();
534                    } else if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocId())) {
535                            documentId = routeLogForm.getDocId();
536                    } else {
537                            throw new WorkflowRuntimeException("No paramater provided to fetch document");
538                    }
539                    
540                    DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
541                    
542                    // check user has permission to add a route log message
543                    boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService()
544                                    .canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader);
545    
546                    if (!isAuthorizedToAddRouteLogMessage) {
547                            throw new InvalidActionTakenException("Principal with name '"
548                                            + GlobalVariables.getUserSession().getPrincipalName()
549                                            + "' is not authorized to add route log messages for documents of type '"
550                                            + routeHeader.getDocumentType().getName());
551                    }
552    
553                    LOG.info("Logging new action message for user " + GlobalVariables.getUserSession().getPrincipalName()
554                                    + ", route header " + routeHeader);
555                    KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(
556                                    GlobalVariables.getUserSession().getPrincipalId(), routeHeader,
557                                    routeLogForm.getNewRouteLogActionMessage());
558    
559                    routeLogForm.setNewRouteLogActionMessage("");
560    
561                    // retrieve routeHeader again to pull new action taken
562                    routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId, true);
563                    fixActionRequestsPositions(routeHeader);
564                    request.setAttribute("routeHeader", routeHeader);
565    
566                    return mapping.findForward(getDefaultMapping());
567            }
568        
569    }