Coverage Report - org.kuali.rice.kew.engine.simulation.SimulationEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
SimulationEngine
0%
0/303
0%
0/194
6.65
 
 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.engine.simulation;
 18  
 
 19  
 import org.apache.log4j.MDC;
 20  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 21  
 import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
 22  
 import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
 23  
 import org.kuali.rice.kew.actionrequest.Recipient;
 24  
 import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
 25  
 import org.kuali.rice.kew.actiontaken.ActionTakenValue;
 26  
 import org.kuali.rice.kew.doctype.bo.DocumentType;
 27  
 import org.kuali.rice.kew.engine.*;
 28  
 import org.kuali.rice.kew.engine.node.*;
 29  
 import org.kuali.rice.kew.engine.node.Process;
 30  
 import org.kuali.rice.kew.exception.DocumentSimulatedRouteException;
 31  
 import org.kuali.rice.kew.exception.InvalidActionTakenException;
 32  
 import org.kuali.rice.kew.exception.ResourceUnavailableException;
 33  
 import org.kuali.rice.kew.exception.WorkflowRuntimeException;
 34  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 35  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 36  
 import org.kuali.rice.kew.util.KEWConstants;
 37  
 import org.kuali.rice.kew.util.PerformanceLogger;
 38  
 import org.kuali.rice.kew.util.Utilities;
 39  
 import org.kuali.rice.kim.bo.Group;
 40  
 import org.kuali.rice.kim.bo.Person;
 41  
 
 42  
 import java.io.*;
 43  
 import java.sql.Timestamp;
 44  
 import java.util.*;
 45  
 
 46  
 
 47  
 /**
 48  
  * A WorkflowEngine implementation which runs simulations.  This object is not thread-safe
 49  
  * and therefore a new instance needs to be instantiated on every use.
 50  
  *
 51  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 52  
  */
 53  0
 public class SimulationEngine extends StandardWorkflowEngine {
 54  
 
 55  0
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SimulationEngine.class);
 56  
         
 57  
         private SimulationCriteria criteria;
 58  0
     private SimulationResults results = new SimulationResults();
 59  0
     private RouteHelper helper = new RouteHelper();
 60  
 
 61  
     public SimulationResults runSimulation(SimulationCriteria criteria) throws Exception {
 62  0
         this.criteria = criteria;
 63  0
         validateCriteria(criteria);
 64  0
         process(criteria.getDocumentId(), null);
 65  0
         return results;
 66  
     }
 67  
 
 68  
     public void process(Long documentId, Long nodeInstanceId) throws InvalidActionTakenException, DocumentSimulatedRouteException {
 69  0
             RouteContext context = RouteContext.createNewRouteContext();
 70  
             try {
 71  0
                     ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION);
 72  0
                     if (criteria.isActivateRequests() == null) {
 73  0
                         activationContext.setActivateRequests(!criteria.getActionsToTake().isEmpty());
 74  
                     } else {
 75  0
                         activationContext.setActivateRequests(criteria.isActivateRequests().booleanValue());
 76  
                     }
 77  0
                     context.setActivationContext(activationContext);
 78  0
                     context.setEngineState(new EngineState());
 79  
                     // suppress policy errors when running a simulation for the purposes of display on the route log
 80  0
                     RequestsNode.setSupressPolicyErrors(context);
 81  0
                     DocumentRouteHeaderValue document = createSimulationDocument(documentId, criteria, context);
 82  0
                     if ( (criteria.isDocumentSimulation()) && ( (document.isProcessed()) || (document.isFinal()) ) ) {
 83  0
                             results.setDocument(document);
 84  0
                             return;
 85  
                     }
 86  0
                     routeDocumentIfNecessary(document, criteria, context);
 87  0
                     results.setDocument(document);
 88  0
                     documentId = document.getRouteHeaderId();
 89  
                     
 90  
                     // detect if MDC already has docId param (to avoid nuking it below)
 91  0
                     boolean mdcHadDocId = MDC.get("docId") != null;
 92  0
                     if (!mdcHadDocId) { MDC.put("docId", documentId); }
 93  
                     
 94  0
                     PerformanceLogger perfLog = new PerformanceLogger(documentId);
 95  
                     try {
 96  0
                         if ( LOG.isInfoEnabled() ) {
 97  0
                             LOG.info("Processing document for Simulation: " + documentId);
 98  
                         }
 99  0
                             List<RouteNodeInstance> activeNodeInstances = getRouteNodeService().getActiveNodeInstances(document);
 100  0
                             List<RouteNodeInstance> nodeInstancesToProcess = determineNodeInstancesToProcess(activeNodeInstances, criteria.getDestinationNodeName());
 101  
 
 102  0
                             context.setDocument(document);
 103  
                             // TODO set document content
 104  0
                             context.setEngineState(new EngineState());
 105  0
                             ProcessContext processContext = new ProcessContext(true, nodeInstancesToProcess);
 106  0
                             while (! nodeInstancesToProcess.isEmpty()) {
 107  0
                                     RouteNodeInstance nodeInstance = (RouteNodeInstance)nodeInstancesToProcess.remove(0);
 108  0
                                     if ( !nodeInstance.isActive() ) {
 109  0
                                             continue;
 110  
                                     }
 111  0
                                     NodeJotter.jotNodeInstance(context.getDocument(), nodeInstance);
 112  0
                                     context.setNodeInstance(nodeInstance);
 113  0
                                     processContext = processNodeInstance(context, helper);
 114  0
                                     if (!hasReachedCompletion(processContext, context.getEngineState().getGeneratedRequests(), nodeInstance, criteria)) {
 115  0
                                             if (processContext.isComplete()) {
 116  0
                                                     if (!processContext.getNextNodeInstances().isEmpty()) {
 117  0
                                                             nodeInstancesToProcess.addAll(processContext.getNextNodeInstances());
 118  
                                                     }
 119  0
                                                     context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(context, document, nodeInstance, criteria));
 120  
                                             }
 121  
                                     } else {
 122  0
                                             context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(context, document, nodeInstance, criteria));
 123  
                                     }
 124  0
                             }
 125  0
                             List simulatedActionRequests = context.getEngineState().getGeneratedRequests();
 126  0
                             Collections.sort(simulatedActionRequests, new Utilities.RouteLogActionRequestSorter());
 127  0
                             results.setSimulatedActionRequests(simulatedActionRequests);
 128  0
                             results.setSimulatedActionsTaken(context.getActivationContext().getSimulatedActionsTaken());
 129  0
             } catch (InvalidActionTakenException e) {
 130  0
                 throw e;
 131  0
             } catch (Exception e) {
 132  0
                 String errorMsg = "Error running simulation for document " + ((criteria.isDocumentSimulation()) ? "id " + documentId.toString() : "type " + criteria.getDocumentTypeName());
 133  0
                 LOG.error(errorMsg,e);
 134  0
                 throw new DocumentSimulatedRouteException(errorMsg, e);
 135  
                     } finally {
 136  0
                             perfLog.log("Time to run simulation.");
 137  0
                             RouteContext.clearCurrentRouteContext();
 138  
                             
 139  0
                             if (!mdcHadDocId) { MDC.remove("docID"); }
 140  0
                     }
 141  0
             } finally {
 142  0
                     RouteContext.releaseCurrentRouteContext();
 143  0
             }
 144  0
     }
 145  
 
 146  
     /**
 147  
      * If there are multiple paths, we need to figure out which ones we need to follow for blanket approval.
 148  
      * This method will throw an exception if a node with the given name could not be located in the routing path.
 149  
      * This method is written in such a way that it should be impossible for there to be an infinate loop, even if
 150  
      * there is extensive looping in the node graph.
 151  
      */
 152  
     private List<RouteNodeInstance> determineNodeInstancesToProcess(List<RouteNodeInstance> activeNodeInstances, String nodeName) throws InvalidActionTakenException {
 153  0
         if (Utilities.isEmpty(nodeName)) {
 154  0
             return activeNodeInstances;
 155  
         }
 156  0
         List<RouteNodeInstance> nodeInstancesToProcess = new ArrayList<RouteNodeInstance>();
 157  0
         for (RouteNodeInstance nodeInstance : activeNodeInstances) {
 158  0
             if (nodeName.equals(nodeInstance.getName())) {
 159  
                 // one of active node instances is node instance to stop at
 160  0
                 return new ArrayList<RouteNodeInstance>();
 161  
             } else {
 162  0
                 if (isNodeNameInPath(nodeName, nodeInstance)) {
 163  0
                     nodeInstancesToProcess.add(nodeInstance);
 164  
                 }
 165  
             }
 166  
         }
 167  0
         if (nodeInstancesToProcess.size() == 0) {
 168  0
             throw new InvalidActionTakenException("Could not locate a node with the given name in the blanket approval path '" + nodeName + "'.  " +
 169  
                     "The document is probably already passed the specified node or does not contain the node.");
 170  
         }
 171  0
         return nodeInstancesToProcess;
 172  
     }
 173  
 
 174  
     private boolean isNodeNameInPath(String nodeName, RouteNodeInstance nodeInstance) {
 175  0
         boolean isInPath = false;
 176  0
         for (Iterator<RouteNode> iterator = nodeInstance.getRouteNode().getNextNodes().iterator(); iterator.hasNext();) {
 177  0
             RouteNode nextNode = (RouteNode) iterator.next();
 178  0
             isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, new HashSet<Long>());
 179  0
         }
 180  0
         return isInPath;
 181  
     }
 182  
 
 183  
     private boolean isNodeNameInPath(String nodeName, RouteNode node, Set<Long> inspected) {
 184  0
         boolean isInPath = !inspected.contains(node.getRouteNodeId()) && node.getRouteNodeName().equals(nodeName);
 185  0
         inspected.add(node.getRouteNodeId());
 186  0
         if (helper.isSubProcessNode(node)) {
 187  0
             Process subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
 188  0
             RouteNode subNode = subProcess.getInitialRouteNode();
 189  0
             isInPath = isInPath || isNodeNameInPath(nodeName, subNode, inspected);
 190  
         }
 191  0
         for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
 192  0
             RouteNode nextNode = (RouteNode) iterator.next();
 193  0
             isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, inspected);
 194  0
         }
 195  0
         return isInPath;
 196  
     }
 197  
 
 198  
     private boolean hasReachedCompletion(ProcessContext processContext, List actionRequests, RouteNodeInstance nodeInstance, SimulationCriteria criteria) {
 199  0
         if (!criteria.getDestinationRecipients().isEmpty()) {
 200  0
             for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
 201  0
                 ActionRequestValue request = (ActionRequestValue) iterator.next();
 202  0
                 for (Iterator<Recipient> userIt = criteria.getDestinationRecipients().iterator(); userIt.hasNext();) {
 203  0
                     Recipient recipient = (Recipient) userIt.next();
 204  0
                     if (request.isRecipientRoutedRequest(recipient)) {
 205  0
                         if ( (Utilities.isEmpty(criteria.getDestinationNodeName())) || (criteria.getDestinationNodeName().equals(request.getNodeInstance().getName())) ) {
 206  0
                             return true;
 207  
                         }
 208  
                     }
 209  0
                 }
 210  0
             }
 211  
         }
 212  0
         return (Utilities.isEmpty(criteria.getDestinationNodeName()) && processContext.isComplete() && processContext.getNextNodeInstances().isEmpty())
 213  
             || nodeInstance.getRouteNode().getRouteNodeName().equals(criteria.getDestinationNodeName());
 214  
     }
 215  
 
 216  
     private List<ActionTakenValue> processPotentialActionsTaken(RouteContext routeContext, DocumentRouteHeaderValue routeHeader, RouteNodeInstance justProcessedNode, SimulationCriteria criteria) {
 217  0
             List<ActionTakenValue> actionsTaken = new ArrayList<ActionTakenValue>();
 218  0
             List requestsToCheck = new ArrayList();
 219  0
             requestsToCheck.addAll(routeContext.getEngineState().getGeneratedRequests());
 220  0
         requestsToCheck.addAll(routeHeader.getActionRequests());
 221  0
             List<ActionRequestValue> pendingActionRequestValues = getCriteriaActionsToDoByNodeName(requestsToCheck, justProcessedNode.getName());
 222  0
         List<ActionTakenValue> actionsToTakeForNode = generateActionsToTakeForNode(justProcessedNode.getName(), routeHeader, criteria, pendingActionRequestValues);
 223  
 
 224  0
         for (ActionTakenValue actionTaken : actionsToTakeForNode)
 225  
         {
 226  0
             KEWServiceLocator.getActionRequestService().deactivateRequests(actionTaken, pendingActionRequestValues, routeContext.getActivationContext());
 227  0
             actionsTaken.add(actionTaken);
 228  
 //            routeContext.getActivationContext().getSimulatedActionsTaken().add(actionTaken);
 229  
         }
 230  0
             return actionsTaken;
 231  
     }
 232  
 
 233  
     private List<ActionTakenValue> generateActionsToTakeForNode(String nodeName, DocumentRouteHeaderValue routeHeader, SimulationCriteria criteria, List<ActionRequestValue> pendingActionRequests) {
 234  0
         List<ActionTakenValue> actions = new ArrayList<ActionTakenValue>();
 235  0
         if ( (criteria.getActionsToTake() != null) && (!criteria.getActionsToTake().isEmpty()) ) {
 236  0
             for (SimulationActionToTake simAction : criteria.getActionsToTake()) {
 237  0
                 if (nodeName.equals(simAction.getNodeName())) {
 238  0
                     actions.add(createDummyActionTaken(routeHeader, simAction.getUser(), simAction.getActionToPerform(), findDelegatorForActionRequests(pendingActionRequests)));
 239  
                 }
 240  
             }
 241  
         }
 242  0
         return actions;
 243  
     }
 244  
 
 245  
     private List<ActionRequestValue> getCriteriaActionsToDoByNodeName(List generatedRequests, String nodeName) {
 246  0
             List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
 247  0
         for (Iterator iterator = generatedRequests.iterator(); iterator.hasNext();) {
 248  0
             ActionRequestValue request = (ActionRequestValue) iterator.next();
 249  0
             if ( (request.isPending()) && request.getNodeInstance() != null && nodeName.equals(request.getNodeInstance().getName())) {
 250  0
                     requests.add(request);
 251  
             }
 252  0
         }
 253  0
         return requests;
 254  
     }
 255  
 
 256  
     private void validateCriteria(SimulationCriteria criteria) {
 257  0
             if (criteria.getDocumentId() == null && Utilities.isEmpty(criteria.getDocumentTypeName())) {
 258  0
                 throw new IllegalArgumentException("No document type name or route header id given, cannot simulate a document without a document type name or a route header id.");
 259  
             }
 260  0
             if (criteria.getXmlContent() == null) {
 261  0
                     criteria.setXmlContent("");
 262  
             }
 263  0
     }
 264  
 
 265  
     /**
 266  
      * Creates the document to run the simulation against by loading it from the database or creating a fake document for
 267  
      * simulation purposes depending on the passed simulation criteria.
 268  
      *
 269  
      * If the documentId is available, we load the document from the database, otherwise we create one based on the given
 270  
      * DocumentType and xml content.
 271  
      */
 272  
     private DocumentRouteHeaderValue createSimulationDocument(Long documentId, SimulationCriteria criteria, RouteContext context) {
 273  0
             DocumentRouteHeaderValue document = null;
 274  0
             if (criteria.isDocumentSimulation()) {
 275  0
             document = getDocumentForSimulation(documentId);
 276  0
             if (!Utilities.isEmpty(criteria.getXmlContent())) {
 277  0
                 document.setDocContent(criteria.getXmlContent());
 278  
             }
 279  0
             } else if (criteria.isDocumentTypeSimulation()) {
 280  0
                 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(criteria.getDocumentTypeName());
 281  0
                 if (documentType == null) {
 282  0
                         throw new IllegalArgumentException("Specified document type could not be found for name '"+criteria.getDocumentTypeName()+"'");
 283  
                 }
 284  0
                 documentId = context.getEngineState().getNextSimulationId();
 285  0
                 document = new DocumentRouteHeaderValue();
 286  0
                 context.setDocument(document);
 287  0
                 document.setRouteHeaderId(documentId);
 288  0
                 document.setCreateDate(new Timestamp(System.currentTimeMillis()));
 289  0
                 document.setDocContent(criteria.getXmlContent());
 290  0
                 document.setDocRouteLevel(new Integer(0));
 291  0
                 document.setDocumentTypeId(documentType.getDocumentTypeId());
 292  0
                     document.setDocRouteStatus(KEWConstants.ROUTE_HEADER_INITIATED_CD);
 293  0
                     initializeDocument(document);
 294  
         }
 295  0
         if (document == null) {
 296  0
                 throw new IllegalArgumentException("Workflow simulation engine could not locate document with id "+documentId);
 297  
         }
 298  0
         context.setDocument(document);
 299  0
         installSimulationNodeInstances(context, criteria);
 300  0
                 return document;
 301  
     }
 302  
 
 303  
     private DocumentRouteHeaderValue getDocumentForSimulation(Long documentId) {
 304  0
         DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId);
 305  0
         return (DocumentRouteHeaderValue)deepCopy(document);
 306  
     }
 307  
 
 308  
     private Serializable deepCopy(Serializable src) {
 309  0
         Serializable obj = null;
 310  0
         if (src != null) {
 311  0
             ObjectOutputStream oos = null;
 312  0
             ObjectInputStream ois = null;
 313  
             try {
 314  0
                 ByteArrayOutputStream serializer = new ByteArrayOutputStream();
 315  0
                 oos = new ObjectOutputStream(serializer);
 316  0
                 oos.writeObject(src);
 317  
 
 318  0
                 ByteArrayInputStream deserializer = new ByteArrayInputStream(serializer.toByteArray());
 319  0
                 ois = new ObjectInputStream(deserializer);
 320  0
                 obj = (Serializable) ois.readObject();
 321  0
             }
 322  0
             catch (IOException e) {
 323  0
                 throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'", e);
 324  
             }
 325  0
             catch (ClassNotFoundException e) {
 326  0
                 throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'", e);
 327  
             }
 328  
             finally {
 329  0
                 try {
 330  0
                     if (oos != null) {
 331  0
                         oos.close();
 332  
                     }
 333  0
                     if (ois != null) {
 334  0
                         ois.close();
 335  
                     }
 336  
                 }
 337  0
                 catch (IOException e) {
 338  
                     // ignoring this IOException, since the streams are going to be abandoned now anyway
 339  0
                 }
 340  0
             }
 341  
         }
 342  0
         return obj;
 343  
     }
 344  
 
 345  
     private void routeDocumentIfNecessary(DocumentRouteHeaderValue document, SimulationCriteria criteria, RouteContext routeContext) throws InvalidActionTakenException {
 346  0
             if (criteria.getRoutingUser() != null) {
 347  0
             ActionTakenValue action = createDummyActionTaken(document, criteria.getRoutingUser(), KEWConstants.ACTION_TAKEN_ROUTED_CD, null);
 348  0
                     routeContext.getActivationContext().getSimulatedActionsTaken().add(action);
 349  0
             simulateDocumentRoute(action, document, criteria.getRoutingUser(), routeContext);
 350  
             }
 351  0
     }
 352  
 
 353  
     /**
 354  
      * Looks at the rule templates and/or the startNodeName and creates the appropriate node instances to run simulation against.
 355  
      * After creating the node instances, it hooks them all together and installs a "terminal" simulation node to stop the simulation
 356  
      * node at the end of the simulation.
 357  
      */
 358  
     private void installSimulationNodeInstances(RouteContext context, SimulationCriteria criteria) {
 359  0
             DocumentRouteHeaderValue document = context.getDocument();
 360  0
             List<RouteNode> simulationNodes = new ArrayList<RouteNode>();
 361  0
             if (!criteria.getNodeNames().isEmpty()) {
 362  0
                     for (String nodeName : criteria.getNodeNames()) {
 363  0
                                 if ( LOG.isDebugEnabled() ) {
 364  0
                                     LOG.debug("Installing simulation starting node '"+nodeName+"'");
 365  
                                 }
 366  0
                             List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
 367  0
                             boolean foundNode = false;
 368  0
                             for (RouteNode node : nodes) {
 369  0
                                         if (node.getRouteNodeName().equals(nodeName)) {
 370  0
                                                 simulationNodes.add(node);
 371  0
                                                 foundNode = true;
 372  0
                                                 break;
 373  
                                         }
 374  
                                 }
 375  0
                             if (!foundNode) {
 376  0
                                     throw new IllegalArgumentException("Could not find node on the document type for the given name '"+nodeName+"'");
 377  
                             }
 378  0
                     }
 379  0
             } else if (!criteria.getRuleTemplateNames().isEmpty()) {
 380  0
                     List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
 381  0
                     for (String ruleTemplateName : criteria.getRuleTemplateNames()) {
 382  0
                                 boolean foundNode = false;
 383  0
                                 for (RouteNode node : nodes) {
 384  0
                                         String routeMethodName = node.getRouteMethodName();
 385  0
                                         if (node.isFlexRM() && ruleTemplateName.equals(routeMethodName)) {
 386  0
                                                 simulationNodes.add(node);
 387  0
                                                 foundNode = true;
 388  0
                                                 break;
 389  
                                         }
 390  0
                                 }
 391  0
                                 if (!foundNode) {
 392  0
                                     throw new IllegalArgumentException("Could not find node on the document type with the given rule template name '"+ruleTemplateName+"'");
 393  
                             }
 394  0
                         }
 395  0
             } else if (criteria.isFlattenNodes()) {
 396  
                     // if they want to flatten the nodes, we will essentially process all simple nodes that are defined on the DocumentType
 397  0
             List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
 398  0
             for ( RouteNode node : nodes ) {
 399  
                 try {
 400  0
                         if ( NodeType.fromNode( node ).isTypeOf( SimpleNode.class ) 
 401  
                                         && !NodeType.fromNode( node ).isTypeOf( NoOpNode.class ) ) {
 402  0
                             simulationNodes.add(node);
 403  
                         }
 404  0
                 } catch (ResourceUnavailableException ex) {
 405  0
                                         LOG.warn( "Unable to determine node type in simulator: " + ex.getMessage() );
 406  0
                                 }
 407  
             }
 408  0
             } else {
 409  
                 // in this case, we want to let the document proceed from it's current active node
 410  0
                     return;
 411  
             }
 412  
             
 413  
             // hook all of the simulation nodes together
 414  0
             Branch defaultBranch = document.getInitialRouteNodeInstances().get(0).getBranch();
 415  
             // clear out the initial route node instances, we are going to build a new node path based on what we want to simulate
 416  0
             document.getInitialRouteNodeInstances().clear();
 417  
 
 418  0
             RouteNodeInstance currentNodeInstance = null;//initialNodeInstance;
 419  0
             for (RouteNode simulationNode : simulationNodes) {
 420  0
                         RouteNodeInstance nodeInstance = helper.getNodeFactory().createRouteNodeInstance(document.getRouteHeaderId(), simulationNode);
 421  0
                         nodeInstance.setBranch(defaultBranch);
 422  0
                         if (currentNodeInstance == null) {
 423  0
                                 document.getInitialRouteNodeInstances().add(nodeInstance);
 424  0
                                 nodeInstance.setActive(true);
 425  0
                                 saveNode(context, nodeInstance);
 426  
                         } else {
 427  0
                                 currentNodeInstance.addNextNodeInstance(nodeInstance);
 428  0
                                 saveNode(context, currentNodeInstance);
 429  
                         }
 430  0
                         currentNodeInstance = nodeInstance;
 431  0
                 }
 432  0
             installSimulationTerminationNode(context, document.getDocumentType(), currentNodeInstance);
 433  0
     }
 434  
 
 435  
     private void installSimulationTerminationNode(RouteContext context, DocumentType documentType, RouteNodeInstance lastNodeInstance) {
 436  0
             RouteNode terminationNode = new RouteNode();
 437  0
             terminationNode.setDocumentType(documentType);
 438  0
             terminationNode.setDocumentTypeId(documentType.getDocumentTypeId());
 439  0
             terminationNode.setNodeType(NoOpNode.class.getName());
 440  0
             terminationNode.setRouteNodeName("SIMULATION_TERMINATION_NODE");
 441  0
             RouteNodeInstance terminationNodeInstance = helper.getNodeFactory().createRouteNodeInstance(lastNodeInstance.getDocumentId(), terminationNode);
 442  0
             terminationNodeInstance.setBranch(lastNodeInstance.getBranch());
 443  0
             lastNodeInstance.addNextNodeInstance(terminationNodeInstance);
 444  0
             saveNode(context, lastNodeInstance);
 445  0
     }
 446  
 
 447  
     // below is pretty much a copy of RouteDocumentAction... but actions have to be faked for now
 448  
     private void simulateDocumentRoute(ActionTakenValue actionTaken, DocumentRouteHeaderValue document, Person user, RouteContext routeContext) throws InvalidActionTakenException {
 449  0
         if (document.isRouted()) {
 450  0
             throw new WorkflowRuntimeException("Document can not simulate a route if it has already been routed");
 451  
         }
 452  0
             ActionRequestService actionRequestService = KEWServiceLocator.getActionRequestService();
 453  
         // TODO delyea - deep copy below
 454  0
         List<ActionRequestValue> actionRequests = new ArrayList<ActionRequestValue>();
 455  0
         for (Iterator iter = actionRequestService.findPendingByDoc(document.getRouteHeaderId()).iterator(); iter.hasNext();) {
 456  0
             ActionRequestValue arv = (ActionRequestValue) iter.next();
 457  0
             actionRequests.add((ActionRequestValue)deepCopy(arv));
 458  0
         }
 459  
 //        actionRequests.addAll(actionRequestService.findPendingByDoc(document.getRouteHeaderId()));
 460  0
         LOG.debug("Simulate Deactivating all pending action requests");
 461  
         // deactivate any requests for the user that routed the document.
 462  0
         for (Iterator<ActionRequestValue> iter = actionRequests.iterator(); iter.hasNext();) {
 463  0
             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
 464  
             // requests generated to the user who is routing the document should be deactivated
 465  0
             if ( (user.getPrincipalId().equals(actionRequest.getPrincipalId())) && (actionRequest.isActive()) ) {
 466  0
                     actionRequestService.deactivateRequest(actionTaken, actionRequest, routeContext.getActivationContext());
 467  
             }
 468  
             // requests generated by a save action should be deactivated
 469  0
             else if (KEWConstants.SAVED_REQUEST_RESPONSIBILITY_ID.equals(actionRequest.getResponsibilityId())) {
 470  0
                     actionRequestService.deactivateRequest(actionTaken, actionRequest, routeContext.getActivationContext());
 471  
             }
 472  0
         }
 473  
 
 474  
 //        String oldStatus = document.getDocRouteStatus();
 475  0
         document.markDocumentEnroute();
 476  
 //        String newStatus = document.getDocRouteStatus();
 477  
 //        notifyStatusChange(newStatus, oldStatus);
 478  
 //        getRouteHeaderService().saveRouteHeader(document);
 479  0
     }
 480  
 
 481  
     private ActionTakenValue createDummyActionTaken(DocumentRouteHeaderValue routeHeader, Person userToPerformAction, String actionToPerform, Recipient delegator) {
 482  0
         ActionTakenValue val = new ActionTakenValue();
 483  0
         val.setActionTaken(actionToPerform);
 484  0
         if (KEWConstants.ACTION_TAKEN_ROUTED_CD.equals(actionToPerform)) {
 485  0
             val.setActionTaken(KEWConstants.ACTION_TAKEN_COMPLETED_CD);
 486  
         }
 487  0
                 val.setAnnotation("");
 488  0
                 val.setDocVersion(routeHeader.getDocVersion());
 489  0
                 val.setRouteHeaderId(routeHeader.getRouteHeaderId());
 490  0
                 val.setPrincipalId(userToPerformAction.getPrincipalId());
 491  
 
 492  0
                 if (delegator != null) {
 493  0
                         if (delegator instanceof KimPrincipalRecipient) {
 494  0
                                 val.setDelegatorPrincipalId(((KimPrincipalRecipient) delegator).getPrincipalId());
 495  0
                         } else if (delegator instanceof KimGroupRecipient) {
 496  0
                                 Group group = ((KimGroupRecipient) delegator).getGroup();
 497  0
                                 val.setDelegatorGroupId(group.getGroupId());
 498  0
                         } else{
 499  0
                                 throw new IllegalArgumentException("Invalid Recipient type received: " + delegator.getClass().getName());
 500  
                         }
 501  
                 }
 502  
 
 503  0
                 val.setRouteHeader(routeHeader);
 504  0
                 val.setCurrentIndicator(Boolean.TRUE);
 505  0
                 return val;
 506  
     }
 507  
 
 508  
         /**
 509  
          * Used by actions taken
 510  
          *
 511  
          * Returns the highest priority delegator in the list of action requests.
 512  
          */
 513  
         private Recipient findDelegatorForActionRequests(List<ActionRequestValue> actionRequests) {
 514  0
                 return KEWServiceLocator.getActionRequestService().findDelegator(actionRequests);
 515  
         }
 516  
 
 517  
     /**
 518  
      * Executes a "saveNode" for the simulation engine, this does not actually save the document, but rather
 519  
      * assigns it some simulation ids.
 520  
      *
 521  
      * Resolves KULRICE-368
 522  
      */
 523  
     @Override
 524  
     protected void saveNode(RouteContext context, RouteNodeInstance nodeInstance) {
 525  
                 // we shold be in simulation mode here
 526  
 
 527  0
             if (nodeInstance.getRouteNodeInstanceId() == null) {
 528  0
                     nodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
 529  
             }
 530  
             
 531  
             // if we are in simulation mode, lets go ahead and assign some id
 532  
             // values to our beans
 533  0
             for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
 534  0
                     RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iterator.next();
 535  0
                     if (routeNodeInstance.getRouteNodeInstanceId() == null) {
 536  0
                             routeNodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
 537  
                     }
 538  0
             }
 539  0
             if (nodeInstance.getProcess() != null && nodeInstance.getProcess().getRouteNodeInstanceId() == null) {
 540  0
                     nodeInstance.getProcess().setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
 541  
             }
 542  0
             if (nodeInstance.getBranch() != null && nodeInstance.getBranch().getBranchId() == null) {
 543  0
                     nodeInstance.getBranch().setBranchId(context.getEngineState().getNextSimulationId());
 544  
             }
 545  0
     }
 546  
 
 547  
 }