View Javadoc

1   /**
2    * Copyright 2005-2012 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().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 }