View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.routelog.web;
17  
18  import org.apache.struts.action.ActionForm;
19  import org.apache.struts.action.ActionForward;
20  import org.apache.struts.action.ActionMapping;
21  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
22  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
23  import org.kuali.rice.kew.actiontaken.ActionTakenValue;
24  import org.kuali.rice.kew.api.KewApiServiceLocator;
25  import org.kuali.rice.kew.api.WorkflowRuntimeException;
26  import org.kuali.rice.kew.api.action.ActionRequest;
27  import org.kuali.rice.kew.api.action.ActionRequestStatus;
28  import org.kuali.rice.kew.api.action.RoutingReportCriteria;
29  import org.kuali.rice.kew.api.document.DocumentDetail;
30  import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState;
31  import org.kuali.rice.kew.api.exception.InvalidActionTakenException;
32  import org.kuali.rice.kew.doctype.SecuritySession;
33  import org.kuali.rice.kew.doctype.service.DocumentSecurityService;
34  import org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader;
35  import org.kuali.rice.kew.engine.node.Branch;
36  import org.kuali.rice.kew.engine.node.NodeState;
37  import org.kuali.rice.kew.engine.node.RouteNode;
38  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
39  import org.kuali.rice.kew.engine.node.service.RouteNodeService;
40  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
41  import org.kuali.rice.kew.service.KEWServiceLocator;
42  import org.kuali.rice.kew.util.Utilities;
43  import org.kuali.rice.kew.web.KewKualiAction;
44  import org.kuali.rice.krad.UserSession;
45  import org.kuali.rice.krad.util.GlobalVariables;
46  
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletResponse;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.Comparator;
53  import java.util.HashMap;
54  import java.util.HashSet;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.Set;
58  
59  
60  /**
61   * A Struts Action used to display the routelog.
62   *
63   * @author Kuali Rice Team (rice.collab@kuali.org)
64   */
65  public class RouteLogAction extends KewKualiAction {
66  
67      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RouteLogAction.class);
68      private static Comparator<ActionRequestValue> ROUTE_LOG_ACTION_REQUEST_SORTER = new Utilities.RouteLogActionRequestSorter();
69      
70      @Override
71  	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
72  
73          RouteLogForm rlForm = (RouteLogForm) form;
74          String documentId = null;
75          if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocumentId())) {
76          	documentId = rlForm.getDocumentId();
77          } else if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocId())) {
78          	documentId =rlForm.getDocId();
79          } else {
80          	throw new WorkflowRuntimeException("No paramater provided to fetch document");
81          }
82  
83          DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
84  
85          DocumentSecurityService security = KEWServiceLocator.getDocumentSecurityService();
86          if (!security.routeLogAuthorized(getUserSession().getPrincipalId(), routeHeader, new SecuritySession(GlobalVariables.getUserSession().getPrincipalId()))) {
87            return mapping.findForward("NotAuthorized");
88          }
89          
90          fixActionRequestsPositions(routeHeader);
91          populateRouteLogFormActionRequests(rlForm, routeHeader);
92  
93          rlForm.setLookFuture(routeHeader.getDocumentType().getLookIntoFuturePolicy().getPolicyValue().booleanValue());
94  
95          if (rlForm.isShowFuture()) {
96              try {
97                  populateRouteLogFutureRequests(rlForm, routeHeader);
98              } catch (Exception e) {
99                  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 
168     		actionRequest.setChildrenRequests( new ArrayList<ActionRequestValue>(0) );
169     		actionRequest.setParentActionRequest(primaryDelegateRequest);
170 
171     		primaryDelegateRequest.getChildrenRequests().add(0, actionRequest);
172     		
173     		for (ActionRequestValue delegateRequest : primaryDelegateRequest.getChildrenRequests()) {
174     			delegateRequest.setParentActionRequest(primaryDelegateRequest);
175     		}
176     		
177     		return primaryDelegateRequest;
178     	}
179     	
180     	return actionRequest;
181     }
182 
183     private List<ActionRequestValue> switchActionRequestPositionsIfPrimaryDelegatesPresent( Collection<ActionRequestValue> actionRequests ) {
184     	List<ActionRequestValue> results = new ArrayList<ActionRequestValue>( actionRequests.size() );
185     	for ( ActionRequestValue actionRequest : actionRequests ) {
186 			results.add( switchActionRequestPositionIfPrimaryDelegatePresent(actionRequest) );
187     	}
188     	return results;
189     }
190     
191     @SuppressWarnings("unchecked")
192     private void fixActionRequestsPositions(DocumentRouteHeaderValue routeHeader) {
193         for (ActionTakenValue actionTaken : routeHeader.getActionsTaken()) {
194             Collections.sort((List<ActionRequestValue>) actionTaken.getActionRequests(), ROUTE_LOG_ACTION_REQUEST_SORTER);
195             actionTaken.setActionRequests( actionTaken.getActionRequests() );
196         }
197     }
198     
199     /**
200      * executes a simulation of the future routing, and sets the futureRootRequests and futureActionRequestCount
201      * properties on the provided RouteLogForm.
202      * 
203      * @param rlForm the RouteLogForm --used in a write-only fashion.
204      * @param document the DocumentRouteHeaderValue for the document whose future routing is being simulated.
205      * @throws Exception
206      */
207     public void populateRouteLogFutureRequests(RouteLogForm rlForm, DocumentRouteHeaderValue document) throws Exception {
208 
209         RoutingReportCriteria reportCriteria = RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentId()).build();
210         String applicationId = document.getDocumentType().getApplicationId();
211 
212         // gather the IDs for action requests that predate the simulation
213 		Set<String> preexistingActionRequestIds = getActionRequestIds(document);
214         
215 		// run the simulation
216         DocumentDetail documentDetail = KewApiServiceLocator.getWorkflowDocumentActionsService(applicationId).executeSimulation(reportCriteria);
217 
218         // fabricate our ActionRequestValueS from the results
219         List<ActionRequestValue> futureActionRequests = 
220         	reconstituteActionRequestValues(documentDetail, preexistingActionRequestIds);
221 
222         Collections.sort(futureActionRequests, ROUTE_LOG_ACTION_REQUEST_SORTER);
223         
224         futureActionRequests = switchActionRequestPositionsIfPrimaryDelegatesPresent(futureActionRequests);
225         
226         int pendingActionRequestCount = 0;
227         for (ActionRequestValue actionRequest: futureActionRequests) {
228             if (actionRequest.isPending()) {
229                 pendingActionRequestCount++;
230 
231                 if (ActionRequestStatus.INITIALIZED.getCode().equals(actionRequest.getStatus())) {
232                     actionRequest.setDisplayStatus("PENDING");
233                 } else if (ActionRequestStatus.ACTIVATED.getCode().equals(actionRequest.getStatus())) {
234                     actionRequest.setDisplayStatus("IN ACTION LIST");
235                 }
236             }
237         }
238 
239         rlForm.setFutureRootRequests(futureActionRequests);
240         rlForm.setFutureActionRequestCount(pendingActionRequestCount);
241     }
242 
243 
244 	/**
245 	 * This utility method returns a Set of LongS containing the IDs for the ActionRequestValueS associated with 
246 	 * this DocumentRouteHeaderValue. 
247 	 */
248 	@SuppressWarnings("unchecked")
249 	private Set<String> getActionRequestIds(DocumentRouteHeaderValue document) {
250 		Set<String> actionRequestIds = new HashSet<String>();
251 
252 		List<ActionRequestValue> actionRequests = 
253 			KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(document.getDocumentId());
254 		
255 		if (actionRequests != null) {
256 			for (ActionRequestValue actionRequest : actionRequests) {
257 				if (actionRequest.getActionRequestId() != null) {
258 					actionRequestIds.add(actionRequest.getActionRequestId());
259 				}
260 			}
261 		}
262 		return actionRequestIds;
263 	}
264 
265 	/**
266 	 * This method creates ActionRequestValue objects from the DocumentDetailDTO output from
267 	 * 
268 	 * @param documentDetail contains the DTOs from which the ActionRequestValues are reconstituted
269 	 * @param preexistingActionRequestIds this is a Set of ActionRequest IDs that will not be reconstituted
270 	 * @return the ActionRequestValueS that have been created
271 	 */
272 	private List<ActionRequestValue> reconstituteActionRequestValues(DocumentDetail documentDetail,
273 			Set<String> preexistingActionRequestIds) {
274 
275         RouteNodeInstanceFabricator routeNodeInstanceFabricator = 
276     		new RouteNodeInstanceFabricator(KEWServiceLocator.getRouteNodeService());
277 
278         if (documentDetail.getRouteNodeInstances() != null && !documentDetail.getRouteNodeInstances().isEmpty()) {
279         	for (org.kuali.rice.kew.api.document.node.RouteNodeInstance routeNodeInstanceVO : documentDetail.getRouteNodeInstances()) {
280         		routeNodeInstanceFabricator.importRouteNodeInstanceDTO(routeNodeInstanceVO);
281         	}
282 		}
283         
284         List<ActionRequest> actionRequestVOs = documentDetail.getActionRequests();
285         List<ActionRequestValue> futureActionRequests = new ArrayList<ActionRequestValue>();
286         if (actionRequestVOs != null) {
287 			for (ActionRequest actionRequestVO : actionRequestVOs) {
288 				if (actionRequestVO != null) {
289 					if (!preexistingActionRequestIds.contains(actionRequestVO.getId())) {
290 						ActionRequestValue converted = ActionRequestValue.from(actionRequestVO,
291                                 routeNodeInstanceFabricator);
292 						futureActionRequests.add(converted);
293 					}
294 				}
295 			}
296 		}
297 		return futureActionRequests;
298 	}
299     
300     private ActionRequestService getActionRequestService() {
301         return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
302     }
303     
304     private UserSession getUserSession() {
305         return GlobalVariables.getUserSession();
306     }
307     
308     /**
309      * Creates dummy RouteNodeInstances based on imported data from RouteNodeInstanceDTOs.
310      * It is then able to vend those RouteNodeInstanceS back by their IDs.
311      * 
312      * @author Kuali Rice Team (rice.collab@kuali.org)
313      *
314      */
315     private static class RouteNodeInstanceFabricator implements RouteNodeInstanceLoader {
316 
317     	private Map<String,Branch> branches = new HashMap<String, Branch>();
318     	private Map<String, RouteNodeInstance> routeNodeInstances =
319                 new HashMap<String, RouteNodeInstance>();
320     	private Map<String,RouteNode> routeNodes = new HashMap<String, RouteNode>();
321     	private Map<String,NodeState> nodeStates = new HashMap<String, NodeState>();
322 
323     	private RouteNodeService routeNodeService;
324     	
325     	/**
326 		 * This constructs a FutureRouteNodeInstanceFabricator, which will generate bogus
327 		 * RouteNodeInstances for SimulationEngine results
328 		 * 
329 		 */
330 		public RouteNodeInstanceFabricator(RouteNodeService routeNodeService) {
331 			this.routeNodeService = routeNodeService;
332 		}
333 
334 		/**
335 		 * 
336 		 * This method looks at the given RouteNodeInstanceDTO and imports it (and all it's ancestors)
337 		 * as dummy RouteNodeInstanceS
338 		 * 
339 		 * @param nodeInstanceDTO
340 		 */
341 		public void importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
342 			_importRouteNodeInstanceDTO(nodeInstanceDTO);
343 		}
344 		
345 		/**
346 		 * helper method for {@link #importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance)} which does all
347 		 * the work.  The public method just wraps this one but hides the returned RouteNodeInstance,
348 		 * which is used for the recursive call to populate the nextNodeInstanceS inside our 
349 		 * RouteNodeInstanceS.
350 		 * 
351 		 * @param nodeInstanceDTO
352 		 * @return
353 		 */
354     	private RouteNodeInstance _importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) {
355     		if (nodeInstanceDTO == null) {
356     			return null;
357     		}
358     		RouteNodeInstance nodeInstance = new RouteNodeInstance();
359     		nodeInstance.setActive(nodeInstanceDTO.isActive());
360 
361     		nodeInstance.setComplete(nodeInstanceDTO.isComplete());
362     		nodeInstance.setDocumentId(nodeInstanceDTO.getDocumentId());
363     		nodeInstance.setInitial(nodeInstanceDTO.isInitial());
364 
365     		Branch branch = getBranch(nodeInstanceDTO.getBranchId());
366     		nodeInstance.setBranch(branch);
367 
368     		if (nodeInstanceDTO.getRouteNodeId() != null) {
369     			RouteNode routeNode = routeNodeService.findRouteNodeById(nodeInstanceDTO.getRouteNodeId());
370 
371     			if (routeNode == null) {
372     				routeNode = getRouteNode(nodeInstanceDTO.getRouteNodeId());
373     				routeNode.setNodeType(nodeInstanceDTO.getName());
374     			}
375 
376     			nodeInstance.setRouteNode(routeNode);
377 
378     			if (routeNode.getBranch() != null) {
379         			branch.setName(routeNode.getBranch().getName());
380         		} 
381     		}
382 
383     		RouteNodeInstance process = getRouteNodeInstance(nodeInstanceDTO.getProcessId());
384     		nodeInstance.setProcess(process);
385 
386     		nodeInstance.setRouteNodeInstanceId(nodeInstanceDTO.getId());
387 
388     		List<NodeState> nodeState = new ArrayList<NodeState>();
389     		if (nodeInstanceDTO.getState() != null) {
390 				for (RouteNodeInstanceState stateDTO : nodeInstanceDTO.getState()) {
391 					NodeState state = getNodeState(stateDTO.getId());
392 					if (state != null) {
393 						state.setKey(stateDTO.getKey());
394 						state.setValue(stateDTO.getValue());
395 						state.setStateId(stateDTO.getId());
396 						state.setNodeInstance(nodeInstance);
397 						nodeState.add(state);
398 					}
399 				}
400 			}
401     		nodeInstance.setState(nodeState);
402 
403     		List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>();
404 
405 
406     		for (org.kuali.rice.kew.api.document.node.RouteNodeInstance nextNodeInstanceVO : nodeInstanceDTO.getNextNodeInstances()) {
407     			// recurse to populate nextNodeInstances
408     			nextNodeInstances.add(_importRouteNodeInstanceDTO(nextNodeInstanceVO));
409     		}
410             nodeInstance.setNextNodeInstances(nextNodeInstances);
411 
412     		routeNodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
413     		return nodeInstance;
414     	}
415     	
416 		/**
417 		 * This method returns a dummy RouteNodeInstance for the given ID, or null if it hasn't
418 		 * imported from a RouteNodeInstanceDTO with that ID
419 		 * 
420 		 * @see org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader#load(String)
421 		 */
422 		@Override
423 		public RouteNodeInstance load(String routeNodeInstanceID) {
424 			return routeNodeInstances.get(routeNodeInstanceID);
425 		}
426 
427 
428     	/**
429     	 * This method creates bogus BranchES as needed
430     	 * 
431     	 * @param branchId
432     	 * @return
433     	 */
434     	private Branch getBranch(String branchId) {
435     		Branch result = null;
436 
437     		if (branchId != null) {
438     			// if branch doesn't exist, create it
439     			if (!branches.containsKey(branchId)) {
440     				result = new Branch();
441     				result.setBranchId(branchId);
442     				branches.put(branchId, result);
443     			} else {
444     				result = branches.get(branchId);
445     			}
446     		}
447     		return result;
448     	}
449 
450     	/**
451     	 * This method creates bogus RouteNodeS as needed
452     	 * 
453     	 * @param routeNodeId
454     	 * @return
455     	 */
456     	private RouteNode getRouteNode(String routeNodeId) {
457     		RouteNode result = null;
458 
459     		if (routeNodeId != null) {
460     			// if RouteNode doesn't exist, create it
461     			if (!routeNodes.containsKey(routeNodeId)) {
462     				result = new RouteNode();
463     				result.setRouteNodeId(routeNodeId);
464     				routeNodes.put(routeNodeId, result);
465     			} else {
466     				result = routeNodes.get(routeNodeId);
467     			}
468     		}
469     		return result;
470     	}
471 
472     	/**
473     	 * This method creates bogus RouteNodeInstanceS as needed
474     	 * 
475     	 * @param routeNodeInstanceId
476     	 * @return
477     	 */
478     	public RouteNodeInstance getRouteNodeInstance(String routeNodeInstanceId) {
479     		RouteNodeInstance result = null;
480 
481     		if (routeNodeInstanceId != null) {
482     			// if RouteNodeInstance doesn't exist, create it
483     			if (!routeNodeInstances.containsKey(routeNodeInstanceId)) {
484                     result = new RouteNodeInstance();
485     				result.setRouteNodeInstanceId(routeNodeInstanceId);
486     				routeNodeInstances.put(routeNodeInstanceId, result);
487     			} else {
488     				result = routeNodeInstances.get(routeNodeInstanceId);
489     			}
490     		}
491     		return result;
492     	}
493 
494     	/**
495     	 * This method creates bogus NodeStateS as needed
496     	 * 
497     	 * @param nodeStateId
498     	 * @return
499     	 */
500     	private NodeState getNodeState(String nodeStateId) {
501     		NodeState result = null;
502 
503     		if (nodeStateId != null) {
504     			// if NodeState doesn't exist, create it
505     			if (!nodeStates.containsKey(nodeStateId)) {
506     				result = new NodeState();
507     				result.setNodeStateId(nodeStateId);
508     				nodeStates.put(nodeStateId, result);
509     			} else {
510     				result = nodeStates.get(nodeStateId);
511     			}
512     		}
513     		return result;
514     	}
515 
516     } // end inner class FutureRouteNodeInstanceFabricator
517 
518     /**
519      * Logs a new message to the route log for the current document, then refreshes the action taken list to display
520      * back the new message in the route log tab. User must have permission to log a message for the doc type and the
521      * request must be coming from the route log tab display (not the route log page).
522      */
523 	public ActionForward logActionMessageInRouteLog(ActionMapping mapping, ActionForm form, HttpServletRequest request,
524 			HttpServletResponse response) throws Exception {
525 		RouteLogForm routeLogForm = (RouteLogForm) form;
526 
527 		String documentId = null;
528 		if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocumentId())) {
529 			documentId = routeLogForm.getDocumentId();
530 		} else if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocId())) {
531 			documentId = routeLogForm.getDocId();
532 		} else {
533 			throw new WorkflowRuntimeException("No paramater provided to fetch document");
534 		}
535 		
536 		DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
537 		
538 		// check user has permission to add a route log message
539 		boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService()
540 				.canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader);
541 
542 		if (!isAuthorizedToAddRouteLogMessage) {
543 			throw new InvalidActionTakenException("Principal with name '"
544 					+ GlobalVariables.getUserSession().getPrincipalName()
545 					+ "' is not authorized to add route log messages for documents of type '"
546 					+ routeHeader.getDocumentType().getName());
547 		}
548 
549 		LOG.info("Logging new action message for user " + GlobalVariables.getUserSession().getPrincipalName()
550 				+ ", route header " + routeHeader);
551 		KEWServiceLocator.getWorkflowDocumentService().logDocumentAction(
552 				GlobalVariables.getUserSession().getPrincipalId(), routeHeader,
553 				routeLogForm.getNewRouteLogActionMessage());
554 
555 		routeLogForm.setNewRouteLogActionMessage("");
556 
557 		// retrieve routeHeader again to pull new action taken
558 		routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId, true);
559 		fixActionRequestsPositions(routeHeader);
560 		request.setAttribute("routeHeader", routeHeader);
561 
562 		return mapping.findForward(getDefaultMapping());
563 	}
564     
565 }