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