Coverage Report - org.kuali.rice.kew.actions.ReturnToPreviousNodeAction
 
Classes in this File Line Coverage Branch Coverage Complexity
ReturnToPreviousNodeAction
0%
0/201
0%
0/92
3.5
 
 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.actions;
 18  
 
 19  
 import org.apache.log4j.MDC;
 20  
 import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
 21  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 22  
 import org.kuali.rice.kew.actionrequest.Recipient;
 23  
 import org.kuali.rice.kew.actiontaken.ActionTakenValue;
 24  
 import org.kuali.rice.kew.api.WorkflowRuntimeException;
 25  
 import org.kuali.rice.kew.engine.CompatUtils;
 26  
 import org.kuali.rice.kew.engine.RouteHelper;
 27  
 import org.kuali.rice.kew.engine.node.NodeGraphSearchCriteria;
 28  
 import org.kuali.rice.kew.engine.node.NodeGraphSearchResult;
 29  
 import org.kuali.rice.kew.engine.node.RouteNode;
 30  
 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
 31  
 import org.kuali.rice.kew.engine.node.service.RouteNodeService;
 32  
 import org.kuali.rice.kew.exception.InvalidActionTakenException;
 33  
 import org.kuali.rice.kew.postprocessor.DocumentRouteLevelChange;
 34  
 import org.kuali.rice.kew.postprocessor.PostProcessor;
 35  
 import org.kuali.rice.kew.postprocessor.ProcessDocReport;
 36  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 37  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 38  
 import org.kuali.rice.kew.util.KEWConstants;
 39  
 import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
 40  
 
 41  
 
 42  
 import java.util.ArrayList;
 43  
 import java.util.Collection;
 44  
 import java.util.Iterator;
 45  
 import java.util.List;
 46  
 
 47  
 
 48  
 /**
 49  
  * Returns a document to a previous node in the route.
 50  
  *
 51  
  * Current implementation only supports returning to a node on the main branch of the
 52  
  * document.
 53  
  *
 54  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 55  
  */
 56  
 public class ReturnToPreviousNodeAction extends ActionTakenEvent {
 57  
 
 58  0
     protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
 59  
 
 60  0
     private RouteHelper helper = new RouteHelper();
 61  
     private String nodeName;
 62  
     private boolean superUserUsage;
 63  0
     private boolean sendNotifications = true;
 64  
 
 65  
     public ReturnToPreviousNodeAction(DocumentRouteHeaderValue routeHeader, PrincipalContract principal) {
 66  0
         super(KEWConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD, routeHeader, principal);
 67  0
     }
 68  
 
 69  
     public ReturnToPreviousNodeAction(DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, String nodeName, boolean sendNotifications) {
 70  0
         super(KEWConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD, routeHeader, principal, annotation);
 71  0
         this.nodeName = nodeName;
 72  0
         this.sendNotifications = sendNotifications;
 73  0
     }
 74  
     
 75  
     public ReturnToPreviousNodeAction(DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, String nodeName, boolean sendNotifications, boolean runPostProcessorLogic) {
 76  0
         super(KEWConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD, routeHeader, principal, annotation, runPostProcessorLogic);
 77  0
         this.nodeName = nodeName;
 78  0
         this.sendNotifications = sendNotifications;
 79  0
     }
 80  
 
 81  
     /**
 82  
      * Constructor used to override the action taken code...e.g. when being performed as part of a Move action
 83  
      */
 84  
     protected ReturnToPreviousNodeAction(String overrideActionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, String nodeName, boolean sendNotifications) {
 85  0
         super(overrideActionTakenCode, routeHeader, principal, annotation);
 86  0
         this.nodeName = nodeName;
 87  0
         this.sendNotifications = sendNotifications;
 88  0
     }
 89  
     
 90  
     /**
 91  
      * Constructor used to override the action taken code...e.g. when being performed as part of a Move action
 92  
      */
 93  
     public ReturnToPreviousNodeAction(String overrideActionTakenCode, DocumentRouteHeaderValue routeHeader, PrincipalContract principal, String annotation, String nodeName, boolean sendNotifications, boolean runPostProcessorLogic) {
 94  0
         super(overrideActionTakenCode, routeHeader, principal, annotation, runPostProcessorLogic);
 95  0
         this.nodeName = nodeName;
 96  0
         this.sendNotifications = sendNotifications;
 97  0
     }
 98  
 
 99  
     /**
 100  
      * TODO will this work properly in the case of an ALL APPROVE role requests with some of the requests already completed?
 101  
      */
 102  
     private void revokePendingRequests(List<ActionRequestValue> pendingRequests, ActionTakenValue actionTaken, Recipient delegator) {
 103  0
         revokeRequests(pendingRequests);
 104  0
         getActionRequestService().deactivateRequests(actionTaken, pendingRequests);
 105  0
         if (sendNotifications) {
 106  0
                 ActionRequestFactory arFactory = new ActionRequestFactory(getRouteHeader());
 107  0
                 List<ActionRequestValue> notificationRequests = arFactory.generateNotifications(pendingRequests, getPrincipal(), delegator, KEWConstants.ACTION_REQUEST_FYI_REQ, getActionTakenCode());
 108  0
                 getActionRequestService().activateRequests(notificationRequests);
 109  
         }
 110  0
     }
 111  
 
 112  
     /**
 113  
      * Takes a list of root action requests and marks them and all of their children as "non-current".
 114  
      */
 115  
     private void revokeRequests(List<ActionRequestValue> actionRequests) {
 116  0
         for (Iterator<ActionRequestValue> iterator = actionRequests.iterator(); iterator.hasNext();) {
 117  0
             ActionRequestValue actionRequest = iterator.next();
 118  0
             actionRequest.setCurrentIndicator(Boolean.FALSE);
 119  0
             if (actionRequest.getActionTaken() != null) {
 120  0
                 actionRequest.getActionTaken().setCurrentIndicator(Boolean.FALSE);
 121  0
                 KEWServiceLocator.getActionTakenService().saveActionTaken(actionRequest.getActionTaken());
 122  
             }
 123  0
             revokeRequests(actionRequest.getChildrenRequests());
 124  0
             KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
 125  0
         }
 126  0
     }
 127  
 
 128  
     private void processReturnToInitiator(RouteNodeInstance newNodeInstance) {
 129  
         // important to pull this from the RouteNode's DocumentType so we get the proper version
 130  0
         RouteNode initialNode = newNodeInstance.getRouteNode().getDocumentType().getPrimaryProcess().getInitialRouteNode();
 131  0
         if (newNodeInstance.getRouteNode().getRouteNodeId().equals(initialNode.getRouteNodeId())) {
 132  0
             LOG.debug("Document was returned to initiator");
 133  0
             ActionRequestFactory arFactory = new ActionRequestFactory(getRouteHeader(), newNodeInstance);
 134  0
             ActionRequestValue notificationRequest = arFactory.createNotificationRequest(KEWConstants.ACTION_REQUEST_APPROVE_REQ, getRouteHeader().getInitiatorPrincipal(), getActionTakenCode(), getPrincipal(), "Document initiator");
 135  0
             getActionRequestService().activateRequest(notificationRequest);
 136  
         }
 137  0
     }
 138  
 
 139  
     /* (non-Javadoc)
 140  
      * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List)
 141  
      */
 142  
     @Override
 143  
     public String validateActionRules() {
 144  0
             return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId()));
 145  
     }
 146  
 
 147  
     public String validateActionRules(List<ActionRequestValue> actionRequests) {
 148  0
         if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) {
 149  0
             String docStatus = getRouteHeader().getDocRouteStatus();
 150  0
             return "Document of status '" + docStatus + "' cannot taken action '" + KEWConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS + "' to node name "+nodeName;
 151  
         }
 152  0
         List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KEWConstants.ACTION_REQUEST_COMPLETE_REQ);
 153  0
         if (! isActionCompatibleRequest(filteredActionRequests) && ! isSuperUserUsage()) {
 154  0
             return "No request for the user is compatible with the RETURN TO PREVIOUS NODE action";
 155  
         }
 156  0
         return "";
 157  
     }
 158  
 
 159  
     /* (non-Javadoc)
 160  
      * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List)
 161  
      */
 162  
     @Override
 163  
     public boolean isActionCompatibleRequest(List<ActionRequestValue> requests) {
 164  0
         String actionTakenCode = getActionPerformedCode();
 165  
 
 166  
         // Move is always correct because the client application has authorized it
 167  0
         if (KEWConstants.ACTION_TAKEN_MOVE_CD.equals(actionTakenCode)) {
 168  0
             return true;
 169  
         }
 170  
 
 171  
         // can always cancel saved or initiated document
 172  0
         if (routeHeader.isStateInitiated() || routeHeader.isStateSaved()) {
 173  0
             return true;
 174  
         }
 175  
 
 176  0
         boolean actionCompatible = false;
 177  0
         Iterator<ActionRequestValue> ars = requests.iterator();
 178  0
         ActionRequestValue actionRequest = null;
 179  
 
 180  0
         while (ars.hasNext()) {
 181  0
             actionRequest = ars.next();
 182  
 
 183  
             //if (actionRequest.isWorkgroupRequest() && !actionRequest.getWorkgroup().hasMember(this.delegator)) {
 184  
             // TODO might not need this, if so, do role check
 185  
             /*if (actionRequest.isWorkgroupRequest() && !actionRequest.getWorkgroup().hasMember(this.user)) {
 186  
                 continue;
 187  
             }*/
 188  
 
 189  0
             String request = actionRequest.getActionRequested();
 190  
 
 191  0
             if ( (KEWConstants.ACTION_REQUEST_FYI_REQ.equals(request)) ||
 192  
                  (KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(request)) ||
 193  
                  (KEWConstants.ACTION_REQUEST_APPROVE_REQ.equals(request)) ||
 194  
                  (KEWConstants.ACTION_REQUEST_COMPLETE_REQ.equals(request)) ) {
 195  0
                 actionCompatible = true;
 196  0
                 break;
 197  
             }
 198  
 
 199  
             // RETURN_TO_PREVIOUS_ROUTE_LEVEL action available only if you've been routed a complete or approve request
 200  0
             if (KEWConstants.ACTION_TAKEN_RETURNED_TO_PREVIOUS_CD.equals(actionTakenCode) &&
 201  
                     (KEWConstants.ACTION_REQUEST_COMPLETE_REQ.equals(request) || KEWConstants.ACTION_REQUEST_APPROVE_REQ.equals(request))) {
 202  0
                 actionCompatible = true;
 203  
             }
 204  0
         }
 205  
 
 206  0
         return actionCompatible;
 207  
     }
 208  
 
 209  
     public void recordAction() throws InvalidActionTakenException {
 210  0
         MDC.put("docId", getRouteHeader().getDocumentId());
 211  0
         updateSearchableAttributesIfPossible();
 212  0
         LOG.debug("Returning document " + getRouteHeader().getDocumentId() + " to previous node: " + nodeName + ", annotation: " + annotation);
 213  
 
 214  0
         List actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KEWConstants.ACTION_REQUEST_COMPLETE_REQ);
 215  0
         String errorMessage = validateActionRules(actionRequests);
 216  0
         if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) {
 217  0
             throw new InvalidActionTakenException(errorMessage);
 218  
         }
 219  
 
 220  0
             Collection activeNodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(getRouteHeader().getDocumentId());
 221  0
             NodeGraphSearchCriteria criteria = new NodeGraphSearchCriteria(NodeGraphSearchCriteria.SEARCH_DIRECTION_BACKWARD, activeNodeInstances, nodeName);
 222  0
             NodeGraphSearchResult result = KEWServiceLocator.getRouteNodeService().searchNodeGraph(criteria);
 223  0
             validateReturnPoint(nodeName, activeNodeInstances, result);
 224  
 
 225  0
             LOG.debug("Record the returnToPreviousNode action");
 226  0
             Recipient delegator = findDelegatorForActionRequests(actionRequests);
 227  0
             ActionTakenValue actionTaken = saveActionTaken(Boolean.FALSE, delegator);
 228  
 
 229  0
             LOG.debug("Finding requests in return path and setting current indicator to FALSE");
 230  0
             List<ActionRequestValue> doneRequests = new ArrayList<ActionRequestValue>();
 231  0
             List<ActionRequestValue> pendingRequests = new ArrayList<ActionRequestValue>();
 232  0
             for (Iterator iterator = result.getPath().iterator(); iterator.hasNext();) {
 233  0
                     RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
 234  
                     // mark the node instance as having been revoked
 235  0
                     KEWServiceLocator.getRouteNodeService().revokeNodeInstance(getRouteHeader(), nodeInstance);
 236  0
                 List nodeRequests = getActionRequestService().findRootRequestsByDocIdAtRouteNode(getRouteHeader().getDocumentId(), nodeInstance.getRouteNodeInstanceId());
 237  0
                 for (Iterator requestIt = nodeRequests.iterator(); requestIt.hasNext();) {
 238  0
                     ActionRequestValue request = (ActionRequestValue) requestIt.next();
 239  0
                     if (request.isDone()) {
 240  0
                         doneRequests.add(request);
 241  
                     } else {
 242  0
                         pendingRequests.add(request);
 243  
                     }
 244  0
                 }
 245  0
             }
 246  0
             revokeRequests(doneRequests);
 247  0
             LOG.debug("Change pending requests to FYI and activate for docId " + getRouteHeader().getDocumentId());
 248  0
             revokePendingRequests(pendingRequests, actionTaken, delegator);
 249  0
             notifyActionTaken(actionTaken);
 250  0
             executeNodeChange(activeNodeInstances, result);
 251  0
     }
 252  
 
 253  
     /**
 254  
      * This method runs various validation checks on the nodes we ended up at so as to make sure we don't
 255  
      * invoke strange return scenarios.
 256  
      */
 257  
     private void validateReturnPoint(String nodeName, Collection activeNodeInstances, NodeGraphSearchResult result) throws InvalidActionTakenException {
 258  0
             RouteNodeInstance resultNodeInstance = result.getResultNodeInstance();
 259  0
         if (result.getResultNodeInstance() == null) {
 260  0
             throw new InvalidActionTakenException("Could not locate return point for node name '"+nodeName+"'.");
 261  
         }
 262  0
         assertValidNodeType(resultNodeInstance);
 263  0
         assertValidBranch(resultNodeInstance, activeNodeInstances);
 264  0
         assertValidProcess(resultNodeInstance, activeNodeInstances);
 265  0
         assertFinalApprovalNodeNotInPath(result.getPath());
 266  0
     }
 267  
 
 268  
     private void assertValidNodeType(RouteNodeInstance resultNodeInstance) throws InvalidActionTakenException {
 269  
         // the return point can only be a simple or a split node
 270  0
         if (!helper.isSimpleNode(resultNodeInstance.getRouteNode()) && !helper.isSplitNode(resultNodeInstance.getRouteNode())) {
 271  0
                 throw new InvalidActionTakenException("Can only return to a simple or a split node, attempting to return to " + resultNodeInstance.getRouteNode().getNodeType());
 272  
         }
 273  0
     }
 274  
 
 275  
     private void assertValidBranch(RouteNodeInstance resultNodeInstance, Collection activeNodeInstances) throws InvalidActionTakenException {
 276  
         // the branch of the return point needs to be the same as one of the branches of the active nodes or the same as the root branch
 277  0
         boolean inValidBranch = false;
 278  0
         if (resultNodeInstance.getBranch().getParentBranch() == null) {
 279  0
                 inValidBranch = true;
 280  
         } else {
 281  0
                 for (Iterator iterator = activeNodeInstances.iterator(); iterator.hasNext(); ) {
 282  0
                                 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
 283  0
                                 if (nodeInstance.getBranch().getBranchId().equals(resultNodeInstance.getBranch().getBranchId())) {
 284  0
                                         inValidBranch = true;
 285  0
                                         break;
 286  
                                 }
 287  0
                         }
 288  
         }
 289  0
         if (!inValidBranch) {
 290  0
                 throw new InvalidActionTakenException("Returning to an illegal branch, can only return to node within the same branch as an active node or to the primary branch.");
 291  
         }
 292  0
     }
 293  
 
 294  
     private void assertValidProcess(RouteNodeInstance resultNodeInstance, Collection activeNodeInstances) throws InvalidActionTakenException {
 295  
         // if we are in a process, we need to return within the same process
 296  0
         if (resultNodeInstance.isInProcess()) {
 297  0
                 boolean inValidProcess = false;
 298  0
                 for (Iterator iterator = activeNodeInstances.iterator(); iterator.hasNext(); ) {
 299  0
                                 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
 300  0
                                 if (nodeInstance.isInProcess() && nodeInstance.getProcess().getRouteNodeInstanceId().equals(nodeInstance.getProcess().getRouteNodeInstanceId())) {
 301  0
                                         inValidProcess = true;
 302  0
                                         break;
 303  
                                 }
 304  0
                 }
 305  0
                 if (!inValidProcess) {
 306  0
                         throw new InvalidActionTakenException("Returning into an illegal process, cannot return to node within a previously executing process.");
 307  
                 }
 308  
         }
 309  0
     }
 310  
 
 311  
     /**
 312  
      * Cannot return past a COMPLETE final approval node.  This means that you can return from an active and incomplete final approval node.
 313  
      * @param path
 314  
      * @throws InvalidActionTakenException
 315  
      */
 316  
     private void assertFinalApprovalNodeNotInPath(List path) throws InvalidActionTakenException {
 317  0
             for (Iterator iterator = path.iterator(); iterator.hasNext(); ) {
 318  0
                         RouteNodeInstance  nodeInstance = (RouteNodeInstance ) iterator.next();
 319  
                         // if we have a complete final approval node in our path, we cannot return past it
 320  0
                         if (nodeInstance.isComplete() && Boolean.TRUE.equals(nodeInstance.getRouteNode().getFinalApprovalInd())) {
 321  0
                                 throw new InvalidActionTakenException("Cannot return past or through the final approval node '"+nodeInstance.getName()+"'.");
 322  
                         }
 323  0
                 }
 324  0
     }
 325  
 
 326  
     private void executeNodeChange(Collection activeNodes, NodeGraphSearchResult result) throws InvalidActionTakenException {
 327  0
         Integer oldRouteLevel = null;
 328  0
         Integer newRouteLevel = null;
 329  0
         if (CompatUtils.isRouteLevelCompatible(getRouteHeader())) {
 330  0
             int returnPathLength = result.getPath().size()-1;
 331  0
             oldRouteLevel = getRouteHeader().getDocRouteLevel();
 332  0
             newRouteLevel = oldRouteLevel - returnPathLength;
 333  0
             LOG.debug("Changing route header "+ getRouteHeader().getDocumentId()+" route level for backward compatibility to "+newRouteLevel);
 334  0
             getRouteHeader().setDocRouteLevel(newRouteLevel);
 335  0
             KEWServiceLocator.getRouteHeaderService().saveRouteHeader(routeHeader);
 336  
         }
 337  0
         List<RouteNodeInstance> startingNodes = determineStartingNodes(result.getPath(), activeNodes);
 338  0
         RouteNodeInstance newNodeInstance = materializeReturnPoint(startingNodes, result);
 339  0
         for (RouteNodeInstance activeNode : startingNodes)
 340  
         {
 341  0
             notifyNodeChange(oldRouteLevel, newRouteLevel, activeNode, newNodeInstance);
 342  
         }
 343  0
         processReturnToInitiator(newNodeInstance);
 344  0
     }
 345  
 
 346  
     private void notifyNodeChange(Integer oldRouteLevel, Integer newRouteLevel, RouteNodeInstance oldNodeInstance, RouteNodeInstance newNodeInstance) throws InvalidActionTakenException {
 347  
         try {
 348  0
             LOG.debug("Notifying post processor of route node change '"+oldNodeInstance.getName()+"'->'"+newNodeInstance.getName());
 349  0
             PostProcessor postProcessor = routeHeader.getDocumentType().getPostProcessor();
 350  0
             KEWServiceLocator.getRouteHeaderService().saveRouteHeader(getRouteHeader());
 351  0
             DocumentRouteLevelChange routeNodeChange = new DocumentRouteLevelChange(routeHeader.getDocumentId(),
 352  
                     routeHeader.getAppDocId(),
 353  
                     oldRouteLevel, newRouteLevel,
 354  
                     oldNodeInstance.getName(), newNodeInstance.getName(),
 355  
                     oldNodeInstance.getRouteNodeInstanceId(), newNodeInstance.getRouteNodeInstanceId());
 356  0
             ProcessDocReport report = postProcessor.doRouteLevelChange(routeNodeChange);
 357  0
             setRouteHeader(KEWServiceLocator.getRouteHeaderService().getRouteHeader(getDocumentId()));
 358  0
             if (!report.isSuccess()) {
 359  0
                 LOG.warn(report.getMessage(), report.getProcessException());
 360  0
                 throw new InvalidActionTakenException(report.getMessage());
 361  
             }
 362  0
         } catch (Exception ex) {
 363  0
             throw new WorkflowRuntimeException(ex.getMessage());
 364  0
         }
 365  0
     }
 366  
 
 367  
     private List<RouteNodeInstance> determineStartingNodes(List path, Collection<RouteNodeInstance> activeNodes) {
 368  0
             List<RouteNodeInstance> startingNodes = new ArrayList<RouteNodeInstance>();
 369  0
         for (RouteNodeInstance activeNodeInstance : activeNodes)
 370  
         {
 371  0
             if (isInPath(activeNodeInstance, path))
 372  
             {
 373  0
                 startingNodes.add(activeNodeInstance);
 374  
             }
 375  
         }
 376  0
             return startingNodes;
 377  
     }
 378  
 
 379  
     private boolean isInPath(RouteNodeInstance nodeInstance, List<RouteNodeInstance> path) {
 380  0
         for (RouteNodeInstance pathNodeInstance : path)
 381  
         {
 382  0
             if (pathNodeInstance.getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId()))
 383  
             {
 384  0
                 return true;
 385  
             }
 386  
         }
 387  0
             return false;
 388  
     }
 389  
 
 390  
     private RouteNodeInstance materializeReturnPoint(Collection<RouteNodeInstance> startingNodes, NodeGraphSearchResult result) {
 391  0
         RouteNodeService nodeService = KEWServiceLocator.getRouteNodeService();
 392  0
         RouteNodeInstance returnInstance = result.getResultNodeInstance();
 393  0
         RouteNodeInstance newNodeInstance = helper.getNodeFactory().createRouteNodeInstance(getDocumentId(), returnInstance.getRouteNode());
 394  0
         newNodeInstance.setBranch(returnInstance.getBranch());
 395  0
         newNodeInstance.setProcess(returnInstance.getProcess());
 396  0
         newNodeInstance.setComplete(false);
 397  0
         newNodeInstance.setActive(true);
 398  0
         nodeService.save(newNodeInstance);
 399  0
         for (RouteNodeInstance activeNodeInstance : startingNodes) {
 400  
             // TODO what if the activeNodeInstance already has next nodes?
 401  0
             activeNodeInstance.setComplete(true);
 402  0
             activeNodeInstance.setActive(false);
 403  0
             activeNodeInstance.setInitial(false);
 404  0
             activeNodeInstance.addNextNodeInstance(newNodeInstance);
 405  
         }
 406  0
         for (RouteNodeInstance activeNodeInstance : startingNodes)
 407  
         {
 408  0
             nodeService.save(activeNodeInstance);
 409  
         }
 410  
         // TODO really we need to call transitionTo on this node, how can we do that?
 411  
         // this isn't an issue yet because we only allow simple nodes and split nodes at the moment which do no real
 412  
         // work on transitionTo but we may need to enhance that in the future
 413  0
         return newNodeInstance;
 414  
     }
 415  
 
 416  
     public boolean isSuperUserUsage() {
 417  0
         return superUserUsage;
 418  
     }
 419  
     public void setSuperUserUsage(boolean superUserUsage) {
 420  0
         this.superUserUsage = superUserUsage;
 421  0
     }
 422  
 
 423  
 }