View Javadoc

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