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 }