001/** 002 * Copyright 2005-2015 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 */ 016package org.kuali.rice.kew.routelog.web; 017 018import org.apache.struts.action.ActionForm; 019import org.apache.struts.action.ActionForward; 020import org.apache.struts.action.ActionMapping; 021import org.kuali.rice.kew.actionrequest.ActionRequestValue; 022import org.kuali.rice.kew.actionrequest.service.ActionRequestService; 023import org.kuali.rice.kew.actiontaken.ActionTakenValue; 024import org.kuali.rice.kew.api.KewApiServiceLocator; 025import org.kuali.rice.kew.api.WorkflowRuntimeException; 026import org.kuali.rice.kew.api.action.ActionRequest; 027import org.kuali.rice.kew.api.action.ActionRequestStatus; 028import org.kuali.rice.kew.api.action.RoutingReportCriteria; 029import org.kuali.rice.kew.api.document.DocumentDetail; 030import org.kuali.rice.kew.api.document.node.RouteNodeInstanceState; 031import org.kuali.rice.kew.api.exception.InvalidActionTakenException; 032import org.kuali.rice.kew.doctype.SecuritySession; 033import org.kuali.rice.kew.doctype.service.DocumentSecurityService; 034import org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader; 035import org.kuali.rice.kew.engine.node.Branch; 036import org.kuali.rice.kew.engine.node.NodeState; 037import org.kuali.rice.kew.engine.node.RouteNode; 038import org.kuali.rice.kew.engine.node.RouteNodeInstance; 039import org.kuali.rice.kew.engine.node.service.RouteNodeService; 040import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 041import org.kuali.rice.kew.service.KEWServiceLocator; 042import org.kuali.rice.kew.util.Utilities; 043import org.kuali.rice.kew.web.KewKualiAction; 044import org.kuali.rice.krad.UserSession; 045import org.kuali.rice.krad.util.GlobalVariables; 046 047import javax.servlet.http.HttpServletRequest; 048import javax.servlet.http.HttpServletResponse; 049import java.util.ArrayList; 050import java.util.Collection; 051import java.util.Collections; 052import java.util.Comparator; 053import java.util.HashMap; 054import java.util.HashSet; 055import java.util.List; 056import java.util.Map; 057import 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 */ 065public 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 168 actionRequest.setChildrenRequests( new ArrayList<ActionRequestValue>(0) ); 169 actionRequest.setParentActionRequest(primaryDelegateRequest); 170 171 primaryDelegateRequest.getChildrenRequests().add(0, actionRequest); 172 173 for (ActionRequestValue delegateRequest : primaryDelegateRequest.getChildrenRequests()) { 174 delegateRequest.setParentActionRequest(primaryDelegateRequest); 175 } 176 177 return primaryDelegateRequest; 178 } 179 180 return actionRequest; 181 } 182 183 private List<ActionRequestValue> switchActionRequestPositionsIfPrimaryDelegatesPresent( Collection<ActionRequestValue> actionRequests ) { 184 List<ActionRequestValue> results = new ArrayList<ActionRequestValue>( actionRequests.size() ); 185 for ( ActionRequestValue actionRequest : actionRequests ) { 186 results.add( switchActionRequestPositionIfPrimaryDelegatePresent(actionRequest) ); 187 } 188 return results; 189 } 190 191 @SuppressWarnings("unchecked") 192 private void fixActionRequestsPositions(DocumentRouteHeaderValue routeHeader) { 193 for (ActionTakenValue actionTaken : routeHeader.getActionsTaken()) { 194 Collections.sort((List<ActionRequestValue>) actionTaken.getActionRequests(), ROUTE_LOG_ACTION_REQUEST_SORTER); 195 actionTaken.setActionRequests( actionTaken.getActionRequests() ); 196 } 197 } 198 199 /** 200 * executes a simulation of the future routing, and sets the futureRootRequests and futureActionRequestCount 201 * properties on the provided RouteLogForm. 202 * 203 * @param rlForm the RouteLogForm --used in a write-only fashion. 204 * @param document the DocumentRouteHeaderValue for the document whose future routing is being simulated. 205 * @throws Exception 206 */ 207 public void populateRouteLogFutureRequests(RouteLogForm rlForm, DocumentRouteHeaderValue document) throws Exception { 208 209 RoutingReportCriteria reportCriteria = RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentId()).build(); 210 String applicationId = document.getDocumentType().getApplicationId(); 211 212 // gather the IDs for action requests that predate the simulation 213 Set<String> preexistingActionRequestIds = getActionRequestIds(document); 214 215 // run the simulation 216 DocumentDetail documentDetail = KewApiServiceLocator.getWorkflowDocumentActionsService(applicationId).executeSimulation(reportCriteria); 217 218 // fabricate our ActionRequestValueS from the results 219 List<ActionRequestValue> futureActionRequests = 220 reconstituteActionRequestValues(documentDetail, preexistingActionRequestIds); 221 222 Collections.sort(futureActionRequests, ROUTE_LOG_ACTION_REQUEST_SORTER); 223 224 futureActionRequests = switchActionRequestPositionsIfPrimaryDelegatesPresent(futureActionRequests); 225 226 int pendingActionRequestCount = 0; 227 for (ActionRequestValue actionRequest: futureActionRequests) { 228 if (actionRequest.isPending()) { 229 pendingActionRequestCount++; 230 231 if (ActionRequestStatus.INITIALIZED.getCode().equals(actionRequest.getStatus())) { 232 actionRequest.setDisplayStatus("PENDING"); 233 } else if (ActionRequestStatus.ACTIVATED.getCode().equals(actionRequest.getStatus())) { 234 actionRequest.setDisplayStatus("IN ACTION LIST"); 235 } 236 } 237 } 238 239 rlForm.setFutureRootRequests(futureActionRequests); 240 rlForm.setFutureActionRequestCount(pendingActionRequestCount); 241 } 242 243 244 /** 245 * This utility method returns a Set of LongS containing the IDs for the ActionRequestValueS associated with 246 * this DocumentRouteHeaderValue. 247 */ 248 @SuppressWarnings("unchecked") 249 private Set<String> getActionRequestIds(DocumentRouteHeaderValue document) { 250 Set<String> actionRequestIds = new HashSet<String>(); 251 252 List<ActionRequestValue> actionRequests = 253 KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(document.getDocumentId()); 254 255 if (actionRequests != null) { 256 for (ActionRequestValue actionRequest : actionRequests) { 257 if (actionRequest.getActionRequestId() != null) { 258 actionRequestIds.add(actionRequest.getActionRequestId()); 259 } 260 } 261 } 262 return actionRequestIds; 263 } 264 265 /** 266 * This method creates ActionRequestValue objects from the DocumentDetailDTO output from 267 * 268 * @param documentDetail contains the DTOs from which the ActionRequestValues are reconstituted 269 * @param preexistingActionRequestIds this is a Set of ActionRequest IDs that will not be reconstituted 270 * @return the ActionRequestValueS that have been created 271 */ 272 private List<ActionRequestValue> reconstituteActionRequestValues(DocumentDetail documentDetail, 273 Set<String> preexistingActionRequestIds) { 274 275 RouteNodeInstanceFabricator routeNodeInstanceFabricator = 276 new RouteNodeInstanceFabricator(KEWServiceLocator.getRouteNodeService()); 277 278 if (documentDetail.getRouteNodeInstances() != null && !documentDetail.getRouteNodeInstances().isEmpty()) { 279 for (org.kuali.rice.kew.api.document.node.RouteNodeInstance routeNodeInstanceVO : documentDetail.getRouteNodeInstances()) { 280 routeNodeInstanceFabricator.importRouteNodeInstanceDTO(routeNodeInstanceVO); 281 } 282 } 283 284 List<ActionRequest> actionRequestVOs = documentDetail.getActionRequests(); 285 List<ActionRequestValue> futureActionRequests = new ArrayList<ActionRequestValue>(); 286 if (actionRequestVOs != null) { 287 for (ActionRequest actionRequestVO : actionRequestVOs) { 288 if (actionRequestVO != null) { 289 if (!preexistingActionRequestIds.contains(actionRequestVO.getId())) { 290 ActionRequestValue converted = ActionRequestValue.from(actionRequestVO, 291 routeNodeInstanceFabricator); 292 futureActionRequests.add(converted); 293 } 294 } 295 } 296 } 297 return futureActionRequests; 298 } 299 300 private ActionRequestService getActionRequestService() { 301 return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV); 302 } 303 304 private UserSession getUserSession() { 305 return GlobalVariables.getUserSession(); 306 } 307 308 /** 309 * Creates dummy RouteNodeInstances based on imported data from RouteNodeInstanceDTOs. 310 * It is then able to vend those RouteNodeInstanceS back by their IDs. 311 * 312 * @author Kuali Rice Team (rice.collab@kuali.org) 313 * 314 */ 315 private static class RouteNodeInstanceFabricator implements RouteNodeInstanceLoader { 316 317 private Map<String,Branch> branches = new HashMap<String, Branch>(); 318 private Map<String, RouteNodeInstance> routeNodeInstances = 319 new HashMap<String, RouteNodeInstance>(); 320 private Map<String,RouteNode> routeNodes = new HashMap<String, RouteNode>(); 321 private Map<String,NodeState> nodeStates = new HashMap<String, NodeState>(); 322 323 private RouteNodeService routeNodeService; 324 325 /** 326 * This constructs a FutureRouteNodeInstanceFabricator, which will generate bogus 327 * RouteNodeInstances for SimulationEngine results 328 * 329 */ 330 public RouteNodeInstanceFabricator(RouteNodeService routeNodeService) { 331 this.routeNodeService = routeNodeService; 332 } 333 334 /** 335 * 336 * This method looks at the given RouteNodeInstanceDTO and imports it (and all it's ancestors) 337 * as dummy RouteNodeInstanceS 338 * 339 * @param nodeInstanceDTO 340 */ 341 public void importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) { 342 _importRouteNodeInstanceDTO(nodeInstanceDTO); 343 } 344 345 /** 346 * helper method for {@link #importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance)} which does all 347 * the work. The public method just wraps this one but hides the returned RouteNodeInstance, 348 * which is used for the recursive call to populate the nextNodeInstanceS inside our 349 * RouteNodeInstanceS. 350 * 351 * @param nodeInstanceDTO 352 * @return 353 */ 354 private RouteNodeInstance _importRouteNodeInstanceDTO(org.kuali.rice.kew.api.document.node.RouteNodeInstance nodeInstanceDTO) { 355 if (nodeInstanceDTO == null) { 356 return null; 357 } 358 RouteNodeInstance nodeInstance = new RouteNodeInstance(); 359 nodeInstance.setActive(nodeInstanceDTO.isActive()); 360 361 nodeInstance.setComplete(nodeInstanceDTO.isComplete()); 362 nodeInstance.setDocumentId(nodeInstanceDTO.getDocumentId()); 363 nodeInstance.setInitial(nodeInstanceDTO.isInitial()); 364 365 Branch branch = getBranch(nodeInstanceDTO.getBranchId()); 366 nodeInstance.setBranch(branch); 367 368 if (nodeInstanceDTO.getRouteNodeId() != null) { 369 RouteNode routeNode = routeNodeService.findRouteNodeById(nodeInstanceDTO.getRouteNodeId()); 370 371 if (routeNode == null) { 372 routeNode = getRouteNode(nodeInstanceDTO.getRouteNodeId()); 373 routeNode.setNodeType(nodeInstanceDTO.getName()); 374 } 375 376 nodeInstance.setRouteNode(routeNode); 377 378 if (routeNode.getBranch() != null) { 379 branch.setName(routeNode.getBranch().getName()); 380 } 381 } 382 383 RouteNodeInstance process = getRouteNodeInstance(nodeInstanceDTO.getProcessId()); 384 nodeInstance.setProcess(process); 385 386 nodeInstance.setRouteNodeInstanceId(nodeInstanceDTO.getId()); 387 388 List<NodeState> nodeState = new ArrayList<NodeState>(); 389 if (nodeInstanceDTO.getState() != null) { 390 for (RouteNodeInstanceState stateDTO : nodeInstanceDTO.getState()) { 391 NodeState state = getNodeState(stateDTO.getId()); 392 if (state != null) { 393 state.setKey(stateDTO.getKey()); 394 state.setValue(stateDTO.getValue()); 395 state.setStateId(stateDTO.getId()); 396 state.setNodeInstance(nodeInstance); 397 nodeState.add(state); 398 } 399 } 400 } 401 nodeInstance.setState(nodeState); 402 403 List<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>(); 404 405 406 for (org.kuali.rice.kew.api.document.node.RouteNodeInstance nextNodeInstanceVO : nodeInstanceDTO.getNextNodeInstances()) { 407 // recurse to populate nextNodeInstances 408 nextNodeInstances.add(_importRouteNodeInstanceDTO(nextNodeInstanceVO)); 409 } 410 nodeInstance.setNextNodeInstances(nextNodeInstances); 411 412 routeNodeInstances.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance); 413 return nodeInstance; 414 } 415 416 /** 417 * This method returns a dummy RouteNodeInstance for the given ID, or null if it hasn't 418 * imported from a RouteNodeInstanceDTO with that ID 419 * 420 * @see org.kuali.rice.kew.dto.DTOConverter.RouteNodeInstanceLoader#load(String) 421 */ 422 @Override 423 public RouteNodeInstance load(String routeNodeInstanceID) { 424 return routeNodeInstances.get(routeNodeInstanceID); 425 } 426 427 428 /** 429 * This method creates bogus BranchES as needed 430 * 431 * @param branchId 432 * @return 433 */ 434 private Branch getBranch(String branchId) { 435 Branch result = null; 436 437 if (branchId != null) { 438 // if branch doesn't exist, create it 439 if (!branches.containsKey(branchId)) { 440 result = new Branch(); 441 result.setBranchId(branchId); 442 branches.put(branchId, result); 443 } else { 444 result = branches.get(branchId); 445 } 446 } 447 return result; 448 } 449 450 /** 451 * This method creates bogus RouteNodeS as needed 452 * 453 * @param routeNodeId 454 * @return 455 */ 456 private RouteNode getRouteNode(String routeNodeId) { 457 RouteNode result = null; 458 459 if (routeNodeId != null) { 460 // if RouteNode doesn't exist, create it 461 if (!routeNodes.containsKey(routeNodeId)) { 462 result = new RouteNode(); 463 result.setRouteNodeId(routeNodeId); 464 routeNodes.put(routeNodeId, result); 465 } else { 466 result = routeNodes.get(routeNodeId); 467 } 468 } 469 return result; 470 } 471 472 /** 473 * This method creates bogus RouteNodeInstanceS as needed 474 * 475 * @param routeNodeInstanceId 476 * @return 477 */ 478 public RouteNodeInstance getRouteNodeInstance(String routeNodeInstanceId) { 479 RouteNodeInstance result = null; 480 481 if (routeNodeInstanceId != null) { 482 // if RouteNodeInstance doesn't exist, create it 483 if (!routeNodeInstances.containsKey(routeNodeInstanceId)) { 484 result = new RouteNodeInstance(); 485 result.setRouteNodeInstanceId(routeNodeInstanceId); 486 routeNodeInstances.put(routeNodeInstanceId, result); 487 } else { 488 result = routeNodeInstances.get(routeNodeInstanceId); 489 } 490 } 491 return result; 492 } 493 494 /** 495 * This method creates bogus NodeStateS as needed 496 * 497 * @param nodeStateId 498 * @return 499 */ 500 private NodeState getNodeState(String nodeStateId) { 501 NodeState result = null; 502 503 if (nodeStateId != null) { 504 // if NodeState doesn't exist, create it 505 if (!nodeStates.containsKey(nodeStateId)) { 506 result = new NodeState(); 507 result.setNodeStateId(nodeStateId); 508 nodeStates.put(nodeStateId, result); 509 } else { 510 result = nodeStates.get(nodeStateId); 511 } 512 } 513 return result; 514 } 515 516 } // end inner class FutureRouteNodeInstanceFabricator 517 518 /** 519 * Logs a new message to the route log for the current document, then refreshes the action taken list to display 520 * back the new message in the route log tab. User must have permission to log a message for the doc type and the 521 * request must be coming from the route log tab display (not the route log page). 522 */ 523 public ActionForward logActionMessageInRouteLog(ActionMapping mapping, ActionForm form, HttpServletRequest request, 524 HttpServletResponse response) throws Exception { 525 RouteLogForm routeLogForm = (RouteLogForm) form; 526 527 String documentId = null; 528 if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocumentId())) { 529 documentId = routeLogForm.getDocumentId(); 530 } else if (!org.apache.commons.lang.StringUtils.isEmpty(routeLogForm.getDocId())) { 531 documentId = routeLogForm.getDocId(); 532 } else { 533 throw new WorkflowRuntimeException("No paramater provided to fetch document"); 534 } 535 536 DocumentRouteHeaderValue routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId); 537 538 // check user has permission to add a route log message 539 boolean isAuthorizedToAddRouteLogMessage = KEWServiceLocator.getDocumentTypePermissionService() 540 .canAddRouteLogMessage(GlobalVariables.getUserSession().getPrincipalId(), routeHeader); 541 542 if (!isAuthorizedToAddRouteLogMessage) { 543 throw new InvalidActionTakenException("Principal with name '" 544 + GlobalVariables.getUserSession().getPrincipalName() 545 + "' is not authorized to add route log messages for documents of type '" 546 + routeHeader.getDocumentType().getName()); 547 } 548 549 LOG.info("Logging new action message for user " + GlobalVariables.getUserSession().getPrincipalName() 550 + ", route header " + routeHeader); 551 KEWServiceLocator.getWorkflowDocumentService().logDocumentAction( 552 GlobalVariables.getUserSession().getPrincipalId(), routeHeader, 553 routeLogForm.getNewRouteLogActionMessage()); 554 555 routeLogForm.setNewRouteLogActionMessage(""); 556 557 // retrieve routeHeader again to pull new action taken 558 routeHeader = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId, true); 559 fixActionRequestsPositions(routeHeader); 560 request.setAttribute("routeHeader", routeHeader); 561 562 return mapping.findForward(getDefaultMapping()); 563 } 564 565}