001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kew.routelog.web; 017 018 import org.apache.struts.action.ActionForm; 019 import org.apache.struts.action.ActionForward; 020 import org.apache.struts.action.ActionMapping; 021 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 022 import org.kuali.rice.kew.actionrequest.service.ActionRequestService; 023 import org.kuali.rice.kew.actiontaken.ActionTakenValue; 024 import org.kuali.rice.kew.api.KewApiServiceLocator; 025 import org.kuali.rice.kew.api.WorkflowRuntimeException; 026 import org.kuali.rice.kew.api.action.ActionRequest; 027 import org.kuali.rice.kew.api.action.ActionRequestStatus; 028 import org.kuali.rice.kew.api.action.RoutingReportCriteria; 029 import org.kuali.rice.kew.api.document.DocumentDetail; 030 import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState; 031 import org.kuali.rice.kew.api.exception.InvalidActionTakenException; 032 import org.kuali.rice.kew.doctype.SecuritySession; 033 import org.kuali.rice.kew.doctype.service.DocumentSecurityService; 034 import org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader; 035 import org.kuali.rice.kew.engine.node.Branch; 036 import org.kuali.rice.kew.engine.node.NodeState; 037 import org.kuali.rice.kew.engine.node.RouteNode; 038 import org.kuali.rice.kew.engine.node.RouteNodeInstance; 039 import org.kuali.rice.kew.engine.node.service.RouteNodeService; 040 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 041 import org.kuali.rice.kew.service.KEWServiceLocator; 042 import org.kuali.rice.kew.util.Utilities; 043 import org.kuali.rice.kew.web.KewKualiAction; 044 import org.kuali.rice.krad.UserSession; 045 import org.kuali.rice.krad.util.GlobalVariables; 046 047 import javax.servlet.http.HttpServletRequest; 048 import javax.servlet.http.HttpServletResponse; 049 import java.util.ArrayList; 050 import java.util.Collection; 051 import java.util.Collections; 052 import java.util.Comparator; 053 import java.util.HashMap; 054 import java.util.HashSet; 055 import java.util.List; 056 import java.util.Map; 057 import java.util.Set; 058 059 060 /** 061 * A Struts Action used to display the routelog. 062 * 063 * @author Kuali Rice Team (rice.collab@kuali.org) 064 */ 065 public class RouteLogAction extends KewKualiAction { 066 067 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RouteLogAction.class); 068 private static Comparator<ActionRequestValue> ROUTE_LOG_ACTION_REQUEST_SORTER = new Utilities.RouteLogActionRequestSorter(); 069 070 @Override 071 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 072 073 RouteLogForm rlForm = (RouteLogForm) form; 074 String documentId = null; 075 if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocumentId())) { 076 documentId = rlForm.getDocumentId(); 077 } else if (! org.apache.commons.lang.StringUtils.isEmpty(rlForm.getDocId())) { 078 documentId =rlForm.getDocId(); 079 } else { 080 throw new WorkflowRuntimeException("No paramater provided to fetch document"); 081 } 082 083 DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId); 084 085 DocumentSecurityService security = KEWServiceLocator.getDocumentSecurityService(); 086 if (!security.routeLogAuthorized(getUserSession().getPrincipalId(), routeHeader, new SecuritySession(GlobalVariables.getUserSession().getPrincipalId()))) { 087 return mapping.findForward("NotAuthorized"); 088 } 089 090 fixActionRequestsPositions(routeHeader); 091 populateRouteLogFormActionRequests(rlForm, routeHeader); 092 093 rlForm.setLookFuture(routeHeader.getDocumentType().getLookIntoFuturePolicy().getPolicyValue().booleanValue()); 094 095 if (rlForm.isShowFuture()) { 096 try { 097 populateRouteLogFutureRequests(rlForm, routeHeader); 098 } catch (Exception e) { 099 String errorMsg = "Unable to determine Future Action Requests"; 100 LOG.info(errorMsg,e); 101 rlForm.setShowFutureError(errorMsg); 102 } 103 } 104 request.setAttribute("routeHeader", routeHeader); 105 106 // check whether action message logging should be enabled, user must 107 // have KIM permission for doc type 108 boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService() 109 .canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader); 110 if (isAuthorizedToAddRouteLogMessage) { 111 rlForm.setEnableLogAction(true); 112 } else { 113 rlForm.setEnableLogAction(false); 114 } 115 116 return super.execute(mapping, rlForm, request, response); 117 } 118 119 @SuppressWarnings("unchecked") 120 public void populateRouteLogFormActionRequests(RouteLogForm rlForm, DocumentRouteHeaderValue routeHeader) { 121 List<ActionRequestValue> rootRequests = getActionRequestService().getRootRequests(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 }