001 /** 002 * Copyright 2005-2013 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.engine.simulation; 017 018 import org.apache.log4j.MDC; 019 import org.kuali.rice.coreservice.framework.parameter.ParameterService; 020 import org.kuali.rice.kew.actionitem.ActionItem; 021 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 022 import org.kuali.rice.kew.actionrequest.KimGroupRecipient; 023 import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient; 024 import org.kuali.rice.kew.actionrequest.Recipient; 025 import org.kuali.rice.kew.actionrequest.service.ActionRequestService; 026 import org.kuali.rice.kew.actiontaken.ActionTakenValue; 027 import org.kuali.rice.kew.api.WorkflowRuntimeException; 028 import org.kuali.rice.kew.api.exception.DocumentSimulatedRouteException; 029 import org.kuali.rice.kew.doctype.bo.DocumentType; 030 import org.kuali.rice.kew.engine.ActivationContext; 031 import org.kuali.rice.kew.engine.EngineState; 032 import org.kuali.rice.kew.engine.OrchestrationConfig; 033 import org.kuali.rice.kew.engine.ProcessContext; 034 import org.kuali.rice.kew.engine.RouteContext; 035 import org.kuali.rice.kew.engine.StandardWorkflowEngine; 036 import org.kuali.rice.kew.engine.node.Branch; 037 import org.kuali.rice.kew.engine.node.NoOpNode; 038 import org.kuali.rice.kew.engine.node.NodeJotter; 039 import org.kuali.rice.kew.engine.node.NodeType; 040 import org.kuali.rice.kew.engine.node.ProcessDefinitionBo; 041 import org.kuali.rice.kew.engine.node.RequestsNode; 042 import org.kuali.rice.kew.engine.node.RouteNode; 043 import org.kuali.rice.kew.engine.node.RouteNodeInstance; 044 import org.kuali.rice.kew.engine.node.SimpleNode; 045 import org.kuali.rice.kew.engine.node.service.RouteNodeService; 046 import org.kuali.rice.kew.api.exception.InvalidActionTakenException; 047 import org.kuali.rice.kew.api.exception.ResourceUnavailableException; 048 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 049 import org.kuali.rice.kew.routeheader.service.RouteHeaderService; 050 import org.kuali.rice.kew.service.KEWServiceLocator; 051 import org.kuali.rice.kew.api.KewApiConstants; 052 import org.kuali.rice.kew.util.PerformanceLogger; 053 import org.kuali.rice.kew.util.Utilities; 054 import org.kuali.rice.kim.api.group.Group; 055 import org.kuali.rice.kim.api.identity.Person; 056 057 import java.io.ByteArrayInputStream; 058 import java.io.ByteArrayOutputStream; 059 import java.io.IOException; 060 import java.io.ObjectInputStream; 061 import java.io.ObjectOutputStream; 062 import java.io.Serializable; 063 import java.sql.Timestamp; 064 import java.util.ArrayList; 065 import java.util.Collections; 066 import java.util.HashSet; 067 import java.util.Iterator; 068 import java.util.List; 069 import java.util.Set; 070 071 072 /** 073 * A WorkflowEngine implementation which runs simulations. This object is not thread-safe 074 * and therefore a new instance needs to be instantiated on every use. 075 * 076 * @author Kuali Rice Team (rice.collab@kuali.org) 077 */ 078 public class SimulationEngine extends StandardWorkflowEngine implements SimulationWorkflowEngine { 079 080 public SimulationEngine () { 081 super(); 082 } 083 public SimulationEngine(RouteNodeService routeNodeService, RouteHeaderService routeHeaderService, 084 ParameterService parameterService, OrchestrationConfig config) { 085 super(routeNodeService, routeHeaderService, parameterService, config); 086 } 087 088 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SimulationEngine.class); 089 090 private SimulationCriteria criteria; 091 private SimulationResults results; 092 093 @Override 094 public SimulationResults runSimulation(SimulationCriteria criteria) throws Exception { 095 try { 096 this.criteria = criteria; 097 this.results = new SimulationResults(); 098 validateCriteria(criteria); 099 process(criteria.getDocumentId(), null); 100 return results; 101 } finally { 102 //nulling out the results & criteria since these really should only be local variables. 103 this.criteria = null; 104 this.results = null; 105 } 106 } 107 108 @Override 109 public void process(String documentId, String nodeInstanceId) throws InvalidActionTakenException, DocumentSimulatedRouteException { 110 RouteContext context = RouteContext.createNewRouteContext(); 111 try { 112 ActivationContext activationContext = new ActivationContext(ActivationContext.CONTEXT_IS_SIMULATION); 113 if (criteria.isActivateRequests() == null) { 114 activationContext.setActivateRequests(!criteria.getActionsToTake().isEmpty()); 115 } else { 116 activationContext.setActivateRequests(criteria.isActivateRequests().booleanValue()); 117 } 118 context.setActivationContext(activationContext); 119 context.setEngineState(new EngineState()); 120 // suppress policy errors when running a simulation for the purposes of display on the route log 121 RequestsNode.setSuppressPolicyErrors(context); 122 DocumentRouteHeaderValue document = createSimulationDocument(documentId, criteria, context); 123 document.setInitiatorWorkflowId("simulation"); 124 if ( (criteria.isDocumentSimulation()) && ( (document.isProcessed()) || (document.isFinal()) ) ) { 125 results.setDocument(document); 126 return; 127 } 128 routeDocumentIfNecessary(document, criteria, context); 129 results.setDocument(document); 130 documentId = document.getDocumentId(); 131 132 // detect if MDC already has docId param (to avoid nuking it below) 133 boolean mdcHadDocId = MDC.get("docId") != null; 134 if (!mdcHadDocId) { MDC.put("docId", documentId); } 135 136 PerformanceLogger perfLog = new PerformanceLogger(documentId); 137 try { 138 if ( LOG.isInfoEnabled() ) { 139 LOG.info("Processing document for Simulation: " + documentId); 140 } 141 List<RouteNodeInstance> activeNodeInstances = getRouteNodeService().getActiveNodeInstances(document); 142 List<RouteNodeInstance> nodeInstancesToProcess = determineNodeInstancesToProcess(activeNodeInstances, criteria.getDestinationNodeName()); 143 144 context.setDocument(document); 145 // TODO set document content 146 context.setEngineState(new EngineState()); 147 ProcessContext processContext = new ProcessContext(true, nodeInstancesToProcess); 148 while (! nodeInstancesToProcess.isEmpty()) { 149 RouteNodeInstance nodeInstance = (RouteNodeInstance)nodeInstancesToProcess.remove(0); 150 if ( !nodeInstance.isActive() ) { 151 continue; 152 } 153 NodeJotter.jotNodeInstance(context.getDocument(), nodeInstance); 154 context.setNodeInstance(nodeInstance); 155 processContext = processNodeInstance(context, helper); 156 if (!hasReachedCompletion(processContext, context.getEngineState().getGeneratedRequests(), nodeInstance, criteria)) { 157 if (processContext.isComplete()) { 158 if (!processContext.getNextNodeInstances().isEmpty()) { 159 nodeInstancesToProcess.addAll(processContext.getNextNodeInstances()); 160 } 161 context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(context, document, nodeInstance, criteria)); 162 } 163 } else { 164 context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(context, document, nodeInstance, criteria)); 165 } 166 } 167 List simulatedActionRequests = context.getEngineState().getGeneratedRequests(); 168 Collections.sort(simulatedActionRequests, new Utilities.RouteLogActionRequestSorter()); 169 results.setSimulatedActionRequests(simulatedActionRequests); 170 results.setSimulatedActionsTaken(context.getActivationContext().getSimulatedActionsTaken()); 171 } catch (InvalidActionTakenException e) { 172 throw e; 173 } catch (Exception e) { 174 String errorMsg = "Error running simulation for document " + ((criteria.isDocumentSimulation()) ? "id " + documentId.toString() : "type " + criteria.getDocumentTypeName()); 175 LOG.error(errorMsg,e); 176 throw new DocumentSimulatedRouteException(errorMsg, e); 177 } finally { 178 perfLog.log("Time to run simulation."); 179 RouteContext.clearCurrentRouteContext(); 180 181 if (!mdcHadDocId) { MDC.remove("docID"); } 182 } 183 } finally { 184 RouteContext.releaseCurrentRouteContext(); 185 } 186 } 187 188 /** 189 * If there are multiple paths, we need to figure out which ones we need to follow for blanket approval. 190 * This method will throw an exception if a node with the given name could not be located in the routing path. 191 * This method is written in such a way that it should be impossible for there to be an infinate loop, even if 192 * there is extensive looping in the node graph. 193 */ 194 private List<RouteNodeInstance> determineNodeInstancesToProcess(List<RouteNodeInstance> activeNodeInstances, String nodeName) throws InvalidActionTakenException { 195 if (org.apache.commons.lang.StringUtils.isEmpty(nodeName)) { 196 return activeNodeInstances; 197 } 198 List<RouteNodeInstance> nodeInstancesToProcess = new ArrayList<RouteNodeInstance>(); 199 for (RouteNodeInstance nodeInstance : activeNodeInstances) { 200 if (nodeName.equals(nodeInstance.getName())) { 201 // one of active node instances is node instance to stop at 202 return new ArrayList<RouteNodeInstance>(); 203 } else { 204 if (isNodeNameInPath(nodeName, nodeInstance)) { 205 nodeInstancesToProcess.add(nodeInstance); 206 } 207 } 208 } 209 if (nodeInstancesToProcess.size() == 0) { 210 throw new InvalidActionTakenException("Could not locate a node with the given name in the blanket approval path '" + nodeName + "'. " + 211 "The document is probably already passed the specified node or does not contain the node."); 212 } 213 return nodeInstancesToProcess; 214 } 215 216 private boolean isNodeNameInPath(String nodeName, RouteNodeInstance nodeInstance) { 217 boolean isInPath = false; 218 for (Iterator<RouteNode> iterator = nodeInstance.getRouteNode().getNextNodes().iterator(); iterator.hasNext();) { 219 RouteNode nextNode = (RouteNode) iterator.next(); 220 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, new HashSet<String>()); 221 } 222 return isInPath; 223 } 224 225 private boolean isNodeNameInPath(String nodeName, RouteNode node, Set<String> inspected) { 226 boolean isInPath = !inspected.contains(node.getRouteNodeId()) && node.getRouteNodeName().equals(nodeName); 227 inspected.add(node.getRouteNodeId()); 228 if (helper.isSubProcessNode(node)) { 229 ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName()); 230 RouteNode subNode = subProcess.getInitialRouteNode(); 231 if (subNode != null) { 232 isInPath = isInPath || isNodeNameInPath(nodeName, subNode, inspected); 233 } 234 } 235 for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) { 236 RouteNode nextNode = (RouteNode) iterator.next(); 237 isInPath = isInPath || isNodeNameInPath(nodeName, nextNode, inspected); 238 } 239 return isInPath; 240 } 241 242 private boolean hasReachedCompletion(ProcessContext processContext, List actionRequests, RouteNodeInstance nodeInstance, SimulationCriteria criteria) { 243 if (!criteria.getDestinationRecipients().isEmpty()) { 244 for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) { 245 ActionRequestValue request = (ActionRequestValue) iterator.next(); 246 for (Iterator<Recipient> userIt = criteria.getDestinationRecipients().iterator(); userIt.hasNext();) { 247 Recipient recipient = (Recipient) userIt.next(); 248 if (request.isRecipientRoutedRequest(recipient)) { 249 if ( (org.apache.commons.lang.StringUtils.isEmpty(criteria.getDestinationNodeName())) || (criteria.getDestinationNodeName().equals(request.getNodeInstance().getName())) ) { 250 return true; 251 } 252 } 253 } 254 } 255 } 256 return (org.apache.commons.lang.StringUtils.isEmpty(criteria.getDestinationNodeName()) && processContext.isComplete() && processContext.getNextNodeInstances().isEmpty()) 257 || nodeInstance.getRouteNode().getRouteNodeName().equals(criteria.getDestinationNodeName()); 258 } 259 260 private List<ActionTakenValue> processPotentialActionsTaken(RouteContext routeContext, DocumentRouteHeaderValue routeHeader, RouteNodeInstance justProcessedNode, SimulationCriteria criteria) { 261 List<ActionTakenValue> actionsTaken = new ArrayList<ActionTakenValue>(); 262 List requestsToCheck = new ArrayList(); 263 requestsToCheck.addAll(routeContext.getEngineState().getGeneratedRequests()); 264 requestsToCheck.addAll(routeHeader.getActionRequests()); 265 List<ActionRequestValue> pendingActionRequestValues = getCriteriaActionsToDoByNodeName(requestsToCheck, justProcessedNode.getName()); 266 List<ActionTakenValue> actionsToTakeForNode = generateActionsToTakeForNode(justProcessedNode.getName(), routeHeader, criteria, pendingActionRequestValues); 267 268 for (ActionTakenValue actionTaken : actionsToTakeForNode) 269 { 270 KEWServiceLocator.getActionRequestService().deactivateRequests(actionTaken, pendingActionRequestValues, routeContext.getActivationContext()); 271 actionsTaken.add(actionTaken); 272 // routeContext.getActivationContext().getSimulatedActionsTaken().add(actionTaken); 273 } 274 return actionsTaken; 275 } 276 277 private List<ActionTakenValue> generateActionsToTakeForNode(String nodeName, DocumentRouteHeaderValue routeHeader, SimulationCriteria criteria, List<ActionRequestValue> pendingActionRequests) { 278 List<ActionTakenValue> actions = new ArrayList<ActionTakenValue>(); 279 if ( (criteria.getActionsToTake() != null) && (!criteria.getActionsToTake().isEmpty()) ) { 280 for (SimulationActionToTake simAction : criteria.getActionsToTake()) { 281 if (nodeName.equals(simAction.getNodeName())) { 282 actions.add(createDummyActionTaken(routeHeader, simAction.getUser(), simAction.getActionToPerform(), findDelegatorForActionRequests(pendingActionRequests))); 283 } 284 } 285 } 286 return actions; 287 } 288 289 private List<ActionRequestValue> getCriteriaActionsToDoByNodeName(List generatedRequests, String nodeName) { 290 List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>(); 291 for (Iterator iterator = generatedRequests.iterator(); iterator.hasNext();) { 292 ActionRequestValue request = (ActionRequestValue) iterator.next(); 293 if ( (request.isPending()) && request.getNodeInstance() != null && nodeName.equals(request.getNodeInstance().getName())) { 294 requests.add(request); 295 } 296 } 297 return requests; 298 } 299 300 private void validateCriteria(SimulationCriteria criteria) { 301 if (criteria.getDocumentId() == null && org.apache.commons.lang.StringUtils.isEmpty(criteria.getDocumentTypeName())) { 302 throw new IllegalArgumentException("No document type name or document id given, cannot simulate a document without a document type name or a document id."); 303 } 304 if (criteria.getXmlContent() == null) { 305 criteria.setXmlContent(""); 306 } 307 } 308 309 /** 310 * Creates the document to run the simulation against by loading it from the database or creating a fake document for 311 * simulation purposes depending on the passed simulation criteria. 312 * 313 * If the documentId is available, we load the document from the database, otherwise we create one based on the given 314 * DocumentType and xml content. 315 */ 316 private DocumentRouteHeaderValue createSimulationDocument(String documentId, SimulationCriteria criteria, RouteContext context) { 317 DocumentRouteHeaderValue document = null; 318 if (criteria.isDocumentSimulation()) { 319 document = getDocumentForSimulation(documentId); 320 if (!org.apache.commons.lang.StringUtils.isEmpty(criteria.getXmlContent())) { 321 document.setDocContent(criteria.getXmlContent()); 322 } 323 } else if (criteria.isDocumentTypeSimulation()) { 324 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(criteria.getDocumentTypeName()); 325 if (documentType == null) { 326 throw new IllegalArgumentException("Specified document type could not be found for name '"+criteria.getDocumentTypeName()+"'"); 327 } 328 documentId = context.getEngineState().getNextSimulationId().toString(); 329 document = new DocumentRouteHeaderValue(); 330 context.setDocument(document); 331 document.setDocumentId(documentId); 332 document.setCreateDate(new Timestamp(System.currentTimeMillis())); 333 document.setDocContent(criteria.getXmlContent()); 334 document.setDocRouteLevel(new Integer(0)); 335 document.setDocumentTypeId(documentType.getDocumentTypeId()); 336 document.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD); 337 initializeDocument(document); 338 } 339 if (document == null) { 340 throw new IllegalArgumentException("Workflow simulation engine could not locate document with id "+documentId); 341 } 342 for (ActionRequestValue actionRequest : document.getActionRequests()) { 343 actionRequest = (ActionRequestValue) deepCopy(actionRequest); 344 document.getSimulatedActionRequests().add(actionRequest); 345 for (ActionItem actionItem : actionRequest.getActionItems()) { 346 actionRequest.getSimulatedActionItems().add((ActionItem) deepCopy(actionItem)); 347 } 348 } 349 context.setDocument(document); 350 installSimulationNodeInstances(context, criteria); 351 return document; 352 } 353 354 private DocumentRouteHeaderValue getDocumentForSimulation(String documentId) { 355 DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId); 356 return (DocumentRouteHeaderValue)deepCopy(document); 357 } 358 359 private Serializable deepCopy(Serializable src) { 360 Serializable obj = null; 361 if (src != null) { 362 ObjectOutputStream oos = null; 363 ObjectInputStream ois = null; 364 try { 365 ByteArrayOutputStream serializer = new ByteArrayOutputStream(); 366 oos = new ObjectOutputStream(serializer); 367 oos.writeObject(src); 368 369 ByteArrayInputStream deserializer = new ByteArrayInputStream(serializer.toByteArray()); 370 ois = new ObjectInputStream(deserializer); 371 obj = (Serializable) ois.readObject(); 372 } 373 catch (IOException e) { 374 throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'", e); 375 } 376 catch (ClassNotFoundException e) { 377 throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'", e); 378 } 379 finally { 380 try { 381 if (oos != null) { 382 oos.close(); 383 } 384 if (ois != null) { 385 ois.close(); 386 } 387 } 388 catch (IOException e) { 389 // ignoring this IOException, since the streams are going to be abandoned now anyway 390 } 391 } 392 } 393 return obj; 394 } 395 396 private void routeDocumentIfNecessary(DocumentRouteHeaderValue document, SimulationCriteria criteria, RouteContext routeContext) throws InvalidActionTakenException { 397 if (criteria.getRoutingUser() != null) { 398 ActionTakenValue action = createDummyActionTaken(document, criteria.getRoutingUser(), KewApiConstants.ACTION_TAKEN_ROUTED_CD, null); 399 routeContext.getActivationContext().getSimulatedActionsTaken().add(action); 400 simulateDocumentRoute(action, document, criteria.getRoutingUser(), routeContext); 401 } 402 } 403 404 /** 405 * Looks at the rule templates and/or the startNodeName and creates the appropriate node instances to run simulation against. 406 * After creating the node instances, it hooks them all together and installs a "terminal" simulation node to stop the simulation 407 * node at the end of the simulation. 408 */ 409 private void installSimulationNodeInstances(RouteContext context, SimulationCriteria criteria) { 410 DocumentRouteHeaderValue document = context.getDocument(); 411 List<RouteNode> simulationNodes = new ArrayList<RouteNode>(); 412 if (!criteria.getNodeNames().isEmpty()) { 413 for (String nodeName : criteria.getNodeNames()) { 414 if ( LOG.isDebugEnabled() ) { 415 LOG.debug("Installing simulation starting node '"+nodeName+"'"); 416 } 417 List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true); 418 boolean foundNode = false; 419 for (RouteNode node : nodes) { 420 if (node.getRouteNodeName().equals(nodeName)) { 421 simulationNodes.add(node); 422 foundNode = true; 423 break; 424 } 425 } 426 if (!foundNode) { 427 throw new IllegalArgumentException("Could not find node on the document type for the given name '"+nodeName+"'"); 428 } 429 } 430 } else if (!criteria.getRuleTemplateNames().isEmpty()) { 431 List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true); 432 for (String ruleTemplateName : criteria.getRuleTemplateNames()) { 433 boolean foundNode = false; 434 for (RouteNode node : nodes) { 435 String routeMethodName = node.getRouteMethodName(); 436 if (node.isFlexRM() && ruleTemplateName.equals(routeMethodName)) { 437 simulationNodes.add(node); 438 foundNode = true; 439 break; 440 } 441 } 442 if (!foundNode) { 443 throw new IllegalArgumentException("Could not find node on the document type with the given rule template name '"+ruleTemplateName+"'"); 444 } 445 } 446 } else if (criteria.isFlattenNodes()) { 447 // if they want to flatten the nodes, we will essentially process all simple nodes that are defined on the DocumentType 448 List<RouteNode> nodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true); 449 for ( RouteNode node : nodes ) { 450 try { 451 if ( NodeType.fromNode( node ).isTypeOf( SimpleNode.class ) 452 && !NodeType.fromNode( node ).isTypeOf( NoOpNode.class ) ) { 453 simulationNodes.add(node); 454 } 455 } catch (ResourceUnavailableException ex) { 456 LOG.warn( "Unable to determine node type in simulator: " + ex.getMessage() ); 457 } 458 } 459 } else { 460 // in this case, we want to let the document proceed from it's current active node 461 return; 462 } 463 464 // hook all of the simulation nodes together 465 Branch defaultBranch = document.getInitialRouteNodeInstances().get(0).getBranch(); 466 // clear out the initial route node instances, we are going to build a new node path based on what we want to simulate 467 document.getInitialRouteNodeInstances().clear(); 468 469 RouteNodeInstance currentNodeInstance = null;//initialNodeInstance; 470 for (RouteNode simulationNode : simulationNodes) { 471 RouteNodeInstance nodeInstance = helper.getNodeFactory().createRouteNodeInstance(document.getDocumentId(), simulationNode); 472 nodeInstance.setBranch(defaultBranch); 473 if (currentNodeInstance == null) { 474 document.getInitialRouteNodeInstances().add(nodeInstance); 475 nodeInstance.setActive(true); 476 saveNode(context, nodeInstance); 477 } else { 478 currentNodeInstance.addNextNodeInstance(nodeInstance); 479 saveNode(context, currentNodeInstance); 480 } 481 currentNodeInstance = nodeInstance; 482 } 483 installSimulationTerminationNode(context, document.getDocumentType(), currentNodeInstance); 484 } 485 486 private void installSimulationTerminationNode(RouteContext context, DocumentType documentType, RouteNodeInstance lastNodeInstance) { 487 RouteNode terminationNode = new RouteNode(); 488 terminationNode.setDocumentType(documentType); 489 terminationNode.setDocumentTypeId(documentType.getDocumentTypeId()); 490 terminationNode.setNodeType(NoOpNode.class.getName()); 491 terminationNode.setRouteNodeName("SIMULATION_TERMINATION_NODE"); 492 RouteNodeInstance terminationNodeInstance = helper.getNodeFactory().createRouteNodeInstance(lastNodeInstance.getDocumentId(), terminationNode); 493 terminationNodeInstance.setBranch(lastNodeInstance.getBranch()); 494 lastNodeInstance.addNextNodeInstance(terminationNodeInstance); 495 saveNode(context, lastNodeInstance); 496 } 497 498 // below is pretty much a copy of RouteDocumentAction... but actions have to be faked for now 499 private void simulateDocumentRoute(ActionTakenValue actionTaken, DocumentRouteHeaderValue document, Person user, RouteContext routeContext) throws InvalidActionTakenException { 500 if (document.isRouted()) { 501 throw new WorkflowRuntimeException("Document can not simulate a route if it has already been routed"); 502 } 503 ActionRequestService actionRequestService = KEWServiceLocator.getActionRequestService(); 504 // TODO delyea - deep copy below 505 List<ActionRequestValue> actionRequests = new ArrayList<ActionRequestValue>(); 506 for (Iterator iter = actionRequestService.findPendingByDoc(document.getDocumentId()).iterator(); iter.hasNext();) { 507 ActionRequestValue arv = (ActionRequestValue) deepCopy( (ActionRequestValue) iter.next() ); 508 for (ActionItem actionItem : arv.getActionItems()) { 509 arv.getSimulatedActionItems().add((ActionItem) deepCopy(actionItem)); 510 } 511 actionRequests.add(arv);//(ActionRequestValue)deepCopy(arv)); 512 } 513 // actionRequests.addAll(actionRequestService.findPendingByDoc(document.getDocumentId())); 514 LOG.debug("Simulate Deactivating all pending action requests"); 515 // deactivate any requests for the user that routed the document. 516 for (Iterator<ActionRequestValue> iter = actionRequests.iterator(); iter.hasNext();) { 517 ActionRequestValue actionRequest = (ActionRequestValue) iter.next(); 518 // requests generated to the user who is routing the document should be deactivated 519 if ( (user.getPrincipalId().equals(actionRequest.getPrincipalId())) && (actionRequest.isActive()) ) { 520 actionRequestService.deactivateRequest(actionTaken, actionRequest, routeContext.getActivationContext()); 521 } 522 // requests generated by a save action should be deactivated 523 else if (KewApiConstants.SAVED_REQUEST_RESPONSIBILITY_ID.equals(actionRequest.getResponsibilityId())) { 524 actionRequestService.deactivateRequest(actionTaken, actionRequest, routeContext.getActivationContext()); 525 } 526 } 527 528 // String oldStatus = document.getDocRouteStatus(); 529 document.markDocumentEnroute(); 530 // String newStatus = document.getDocRouteStatus(); 531 // notifyStatusChange(newStatus, oldStatus); 532 // getRouteHeaderService().saveRouteHeader(document); 533 } 534 535 private ActionTakenValue createDummyActionTaken(DocumentRouteHeaderValue routeHeader, Person userToPerformAction, String actionToPerform, Recipient delegator) { 536 ActionTakenValue val = new ActionTakenValue(); 537 val.setActionTaken(actionToPerform); 538 if (KewApiConstants.ACTION_TAKEN_ROUTED_CD.equals(actionToPerform)) { 539 val.setActionTaken(KewApiConstants.ACTION_TAKEN_COMPLETED_CD); 540 } 541 val.setAnnotation(""); 542 val.setDocVersion(routeHeader.getDocVersion()); 543 val.setDocumentId(routeHeader.getDocumentId()); 544 val.setPrincipalId(userToPerformAction.getPrincipalId()); 545 546 if (delegator != null) { 547 if (delegator instanceof KimPrincipalRecipient) { 548 val.setDelegatorPrincipalId(((KimPrincipalRecipient) delegator).getPrincipalId()); 549 } else if (delegator instanceof KimGroupRecipient) { 550 Group group = ((KimGroupRecipient) delegator).getGroup(); 551 val.setDelegatorGroupId(group.getId()); 552 } else{ 553 throw new IllegalArgumentException("Invalid Recipient type received: " + delegator.getClass().getName()); 554 } 555 } 556 557 //val.setRouteHeader(routeHeader); 558 val.setCurrentIndicator(Boolean.TRUE); 559 return val; 560 } 561 562 /** 563 * Used by actions taken 564 * 565 * Returns the highest priority delegator in the list of action requests. 566 */ 567 private Recipient findDelegatorForActionRequests(List<ActionRequestValue> actionRequests) { 568 return KEWServiceLocator.getActionRequestService().findDelegator(actionRequests); 569 } 570 571 /** 572 * Executes a "saveNode" for the simulation engine, this does not actually save the document, but rather 573 * assigns it some simulation ids. 574 * 575 * Resolves KULRICE-368 576 */ 577 @Override 578 protected void saveNode(RouteContext context, RouteNodeInstance nodeInstance) { 579 // we shold be in simulation mode here 580 581 if (nodeInstance.getRouteNodeInstanceId() == null) { 582 nodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId()); 583 } 584 585 // if we are in simulation mode, lets go ahead and assign some id 586 // values to our beans 587 for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) { 588 RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iterator.next(); 589 if (routeNodeInstance.getRouteNodeInstanceId() == null) { 590 routeNodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId()); 591 } 592 } 593 if (nodeInstance.getProcess() != null && nodeInstance.getProcess().getRouteNodeInstanceId() == null) { 594 nodeInstance.getProcess().setRouteNodeInstanceId(context.getEngineState().getNextSimulationId()); 595 } 596 if (nodeInstance.getBranch() != null && nodeInstance.getBranch().getBranchId() == null) { 597 nodeInstance.getBranch().setBranchId(context.getEngineState().getNextSimulationId()); 598 } 599 } 600 601 }