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