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