001/**
002 * Copyright 2005-2016 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}