View Javadoc

1   /**
2    * Copyright 2005-2013 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     		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 }