001    /**
002     * Copyright 2005-2014 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.node.service.impl;
017    
018    import org.apache.commons.collections.ComparatorUtils;
019    import org.kuali.rice.kew.doctype.bo.DocumentType;
020    import org.kuali.rice.kew.engine.RouteHelper;
021    import org.kuali.rice.kew.engine.node.Branch;
022    import org.kuali.rice.kew.engine.node.BranchState;
023    import org.kuali.rice.kew.engine.node.NodeGraphContext;
024    import org.kuali.rice.kew.engine.node.NodeGraphSearchCriteria;
025    import org.kuali.rice.kew.engine.node.NodeGraphSearchResult;
026    import org.kuali.rice.kew.engine.node.NodeMatcher;
027    import org.kuali.rice.kew.engine.node.NodeState;
028    import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
029    import org.kuali.rice.kew.engine.node.RouteNode;
030    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
031    import org.kuali.rice.kew.engine.node.RouteNodeUtils;
032    import org.kuali.rice.kew.engine.node.dao.RouteNodeDAO;
033    import org.kuali.rice.kew.engine.node.service.RouteNodeService;
034    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
035    import org.kuali.rice.kew.service.KEWServiceLocator;
036    
037    import java.util.ArrayList;
038    import java.util.Arrays;
039    import java.util.Collection;
040    import java.util.Collections;
041    import java.util.Comparator;
042    import java.util.HashMap;
043    import java.util.HashSet;
044    import java.util.Iterator;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    
049    
050    
051    public class RouteNodeServiceImpl implements RouteNodeService {
052    
053            protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
054            
055            public static final String REVOKED_NODE_INSTANCES_STATE_KEY = "NodeInstances.Revoked";
056    
057            private static final Comparator NODE_INSTANCE_FORWARD_SORT = new NodeInstanceIdSorter();
058            private static final Comparator NODE_INSTANCE_BACKWARD_SORT = 
059                    ComparatorUtils.reversedComparator(NODE_INSTANCE_FORWARD_SORT);
060        private RouteHelper helper = new RouteHelper();
061            private RouteNodeDAO routeNodeDAO;
062            
063        public void save(RouteNode node) {
064            routeNodeDAO.save(node);
065        }
066        
067        public void save(RouteNodeInstance nodeInstance) {
068            routeNodeDAO.save(nodeInstance);
069        }
070        
071        public void save(NodeState nodeState) {
072            routeNodeDAO.save(nodeState);
073        }
074        
075        public void save(Branch branch) {
076            routeNodeDAO.save(branch);
077        }
078    
079        public RouteNode findRouteNodeById(String nodeId) {
080            return routeNodeDAO.findRouteNodeById(nodeId);
081        }
082        
083        public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId) {
084            return routeNodeDAO.findRouteNodeInstanceById(nodeInstanceId);
085        }
086    
087        public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId, DocumentRouteHeaderValue document) {
088            return RouteNodeUtils.findRouteNodeInstanceById(nodeInstanceId, document);
089        }
090        
091        public List<RouteNodeInstance> getCurrentNodeInstances(String documentId) {
092            List<RouteNodeInstance> currentNodeInstances = getActiveNodeInstances(documentId);
093            if (currentNodeInstances.isEmpty()) {
094                currentNodeInstances = getTerminalNodeInstances(documentId);
095            }
096            return currentNodeInstances;
097        }
098        
099        public List<RouteNodeInstance> getActiveNodeInstances(String documentId) {
100            return routeNodeDAO.getActiveNodeInstances(documentId);
101        }
102        
103        public List<RouteNodeInstance> getActiveNodeInstances(DocumentRouteHeaderValue document) {
104           List<RouteNodeInstance> flattenedNodeInstances = getFlattenedNodeInstances(document, true);
105            List<RouteNodeInstance> activeNodeInstances = new ArrayList<RouteNodeInstance>();
106            for (RouteNodeInstance nodeInstance : flattenedNodeInstances) {
107                if (nodeInstance.isActive()) {
108                    activeNodeInstances.add(nodeInstance);
109                }
110            }
111            return activeNodeInstances;
112        }
113    
114        @Override
115        public List<String> getCurrentRouteNodeNames(String documentId) {
116            return routeNodeDAO.getCurrentRouteNodeNames(documentId);
117        }
118    
119        @Override
120            public List<String> getActiveRouteNodeNames(String documentId) {
121            return routeNodeDAO.getActiveRouteNodeNames(documentId);
122        }
123        
124        public List<RouteNodeInstance> getTerminalNodeInstances(String documentId) {
125            return routeNodeDAO.getTerminalNodeInstances(documentId);
126        }
127        
128        @Override
129            public List<String> getTerminalRouteNodeNames(String documentId) {
130            return routeNodeDAO.getTerminalRouteNodeNames(documentId);
131        }
132    
133        public List getInitialNodeInstances(String documentId) {
134            return routeNodeDAO.getInitialNodeInstances(documentId);
135        }
136        
137        public NodeState findNodeState(Long nodeInstanceId, String key) {
138            return routeNodeDAO.findNodeState(nodeInstanceId, key);
139        }
140        
141        public RouteNode findRouteNodeByName(String documentTypeId, String name) {
142            return routeNodeDAO.findRouteNodeByName(documentTypeId, name);
143        }
144        
145        public List<RouteNode> findFinalApprovalRouteNodes(String documentTypeId) {
146            DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findById(documentTypeId);
147            documentType = documentType.getRouteDefiningDocumentType();
148            return routeNodeDAO.findFinalApprovalRouteNodes(documentType.getDocumentTypeId());
149        }
150        
151        public List findNextRouteNodesInPath(RouteNodeInstance nodeInstance, String nodeName) {
152            List<RouteNode> nodesInPath = new ArrayList<RouteNode>();
153            for (Iterator<RouteNode> iterator = nodeInstance.getRouteNode().getNextNodes().iterator(); iterator.hasNext();) {
154                RouteNode nextNode = iterator.next();
155                nodesInPath.addAll(findNextRouteNodesInPath(nodeName, nextNode, new HashSet<String>()));
156            }
157            return nodesInPath;
158        }
159        
160        private List<RouteNode> findNextRouteNodesInPath(String nodeName, RouteNode node, Set<String> inspected) {
161            List<RouteNode> nextNodesInPath = new ArrayList<RouteNode>();
162            if (inspected.contains(node.getRouteNodeId())) {
163                return nextNodesInPath;
164            }
165            inspected.add(node.getRouteNodeId());
166            if (node.getRouteNodeName().equals(nodeName)) {
167                nextNodesInPath.add(node);
168            } else {
169                if (helper.isSubProcessNode(node)) {
170                    ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
171                    RouteNode subNode = subProcess.getInitialRouteNode();
172                    if (subNode != null) {
173                        nextNodesInPath.addAll(findNextRouteNodesInPath(nodeName, subNode, inspected));
174                    }
175                }
176                for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
177                    RouteNode nextNode = iterator.next();
178                    nextNodesInPath.addAll(findNextRouteNodesInPath(nodeName, nextNode, inspected));
179                }
180            }
181            return nextNodesInPath;
182        }
183        
184        public boolean isNodeInPath(DocumentRouteHeaderValue document, String nodeName) {
185            boolean isInPath = false;
186            Collection<RouteNodeInstance> activeNodes = getActiveNodeInstances(document.getDocumentId());
187            for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
188                RouteNodeInstance nodeInstance = iterator.next();
189                List nextNodesInPath = findNextRouteNodesInPath(nodeInstance, nodeName);
190                isInPath = isInPath || !nextNodesInPath.isEmpty();
191            }
192            return isInPath;
193        }
194        
195        public List findRouteNodeInstances(String documentId) {
196            return this.routeNodeDAO.findRouteNodeInstances(documentId);
197        }
198        
199            public void setRouteNodeDAO(RouteNodeDAO dao) {
200                    this.routeNodeDAO = dao;
201            }
202        
203        public List findProcessNodeInstances(RouteNodeInstance process) {
204           return this.routeNodeDAO.findProcessNodeInstances(process);
205        }
206        
207        public List<String> findPreviousNodeNames(String documentId) {
208            DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
209            List<String> revokedIds = Collections.emptyList();
210    
211            String revoked = document.getRootBranch().getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY) == null ? null : document.getRootBranch().getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY).getValue();
212            if (revoked != null) {
213                revokedIds = Arrays.asList(revoked.split(","));
214            }
215            List <RouteNodeInstance> currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
216            List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
217            for (RouteNodeInstance nodeInstance : currentNodeInstances) {
218                nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
219            }
220            List<String> nodeNames = new ArrayList<String>();
221            while (!nodeInstances.isEmpty()) {
222                RouteNodeInstance nodeInstance = nodeInstances.remove(0);
223                if (!revokedIds.contains(nodeInstance.getRouteNodeInstanceId())) {
224                    nodeNames.add(nodeInstance.getName());
225                }
226                nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
227            }
228    
229            //reverse the order, because it was built last to first
230            Collections.reverse(nodeNames);
231    
232            return nodeNames;
233        }
234        
235        public List<String> findFutureNodeNames(String documentId) {
236            List currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
237            List<RouteNode> nodes = new ArrayList<RouteNode>();
238            for (Iterator iterator = currentNodeInstances.iterator(); iterator.hasNext();) {
239                RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
240                nodes.addAll(nodeInstance.getRouteNode().getNextNodes());
241            }
242            List<String> nodeNames = new ArrayList<String>();
243            while (!nodes.isEmpty()) {
244                RouteNode node = nodes.remove(0);
245                if (!nodeNames.contains(node.getRouteNodeName())) {
246                    nodeNames.add(node.getRouteNodeName());
247                }
248                nodes.addAll(node.getNextNodes());
249            }
250            return nodeNames;
251        }
252        
253        public List<RouteNode> getFlattenedNodes(DocumentType documentType, boolean climbHierarchy) {
254            List<RouteNode> nodes = new ArrayList<RouteNode>();
255            if (!documentType.isRouteInherited() || climbHierarchy) {
256                for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) {
257                    ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next();
258                    nodes.addAll(getFlattenedNodes(process));
259                }
260            }
261            Collections.sort(nodes, new RouteNodeSorter());
262            return nodes;
263        }
264        
265        public List<RouteNode> getFlattenedNodes(ProcessDefinitionBo process) {
266            Map<String, RouteNode> nodesMap = new HashMap<String, RouteNode>();
267            if (process.getInitialRouteNode() != null) {
268                flattenNodeGraph(nodesMap, process.getInitialRouteNode());
269                List<RouteNode> nodes = new ArrayList<RouteNode>(nodesMap.values());
270                Collections.sort(nodes, new RouteNodeSorter());
271                return nodes;
272            } else {
273                List<RouteNode> nodes = new ArrayList<RouteNode>();
274                nodes.add(new RouteNode());
275                return nodes;
276            }
277    
278        }
279        
280        /**
281         * Recursively walks the node graph and builds up the map.  Uses a map because we will
282         * end up walking through duplicates, as is the case with Join nodes.
283         */
284        private void flattenNodeGraph(Map<String, RouteNode> nodes, RouteNode node) {
285            if (node != null) {
286                if (nodes.containsKey(node.getRouteNodeName())) {
287                    return;
288                }
289                nodes.put(node.getRouteNodeName(), node);
290                for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
291                    RouteNode nextNode = iterator.next();
292                    flattenNodeGraph(nodes, nextNode);
293                }
294            } else {
295                return;
296            }
297        }        
298        
299        public List<RouteNodeInstance> getFlattenedNodeInstances(DocumentRouteHeaderValue document, boolean includeProcesses) {
300            List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
301            Set<String> visitedNodeInstanceIds = new HashSet<String>();
302            for (Iterator<RouteNodeInstance> iterator = document.getInitialRouteNodeInstances().iterator(); iterator.hasNext();) {
303                RouteNodeInstance initialNodeInstance = iterator.next();
304                flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, initialNodeInstance, includeProcesses);    
305            }
306            return nodeInstances;
307        }
308        
309            private void flattenNodeInstanceGraph(List<RouteNodeInstance> nodeInstances, Set<String> visitedNodeInstanceIds, RouteNodeInstance nodeInstance, boolean includeProcesses) {
310    
311                    if (nodeInstance != null) {
312                            if (visitedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
313                                    return;
314                            }
315                            if (includeProcesses && nodeInstance.getProcess() != null) {
316                                    flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nodeInstance.getProcess(), includeProcesses);
317                            }
318                            visitedNodeInstanceIds.add(nodeInstance.getRouteNodeInstanceId());
319                            nodeInstances.add(nodeInstance);
320                            for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
321                                    RouteNodeInstance nextNodeInstance = iterator.next();
322                                    flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nextNodeInstance, includeProcesses);
323                            }
324    
325                    }
326    
327        }      
328        
329        public NodeGraphSearchResult searchNodeGraph(NodeGraphSearchCriteria criteria) {
330            NodeGraphContext context = new NodeGraphContext();
331            if (criteria.getSearchDirection() == NodeGraphSearchCriteria.SEARCH_DIRECTION_BACKWARD) {
332                    searchNodeGraphBackward(context, criteria.getMatcher(), null, criteria.getStartingNodeInstances());
333            } else {
334                    throw new UnsupportedOperationException("Search feature can only search backward currently.");
335            }
336            List exactPath = determineExactPath(context, criteria.getSearchDirection(), criteria.getStartingNodeInstances());
337            return new NodeGraphSearchResult(context.getCurrentNodeInstance(), exactPath);
338        }
339        
340        private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher, RouteNodeInstance previousNodeInstance, Collection nodeInstances) {
341            if (nodeInstances == null) {
342                return;
343            }
344            for (Iterator iterator = nodeInstances.iterator(); iterator.hasNext();) {
345                RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
346                context.setPreviousNodeInstance(previousNodeInstance);
347                context.setCurrentNodeInstance(nodeInstance);
348                searchNodeGraphBackward(context, matcher);
349                if (context.getResultNodeInstance() != null) {
350                    // we've located the node instance we're searching for, we're done
351                    break;
352                }
353            }
354        }
355        
356        private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher) {
357            RouteNodeInstance current = context.getCurrentNodeInstance();
358            int numBranches = current.getNextNodeInstances().size();
359            // if this is a split node, we want to wait here, until all branches join back to us
360            if (numBranches > 1) {
361                    // determine the number of branches that have joined back to the split thus far
362                Integer joinCount = (Integer)context.getSplitState().get(current.getRouteNodeInstanceId());
363                if (joinCount == null) {
364                    joinCount = new Integer(0);
365                }
366                // if this split is not a leaf node we increment the count
367                if (context.getPreviousNodeInstance() != null) {
368                    joinCount = new Integer(joinCount.intValue()+1);
369                }
370                context.getSplitState().put(current.getRouteNodeInstanceId(), joinCount);
371                // if not all branches have joined, stop and wait for other branches to join
372                if (joinCount.intValue() != numBranches) {
373                    return;
374                }
375            }
376            if (matcher.isMatch(context)) {
377                context.setResultNodeInstance(current);
378            } else {
379                context.getVisited().put(current.getRouteNodeInstanceId(), current);
380                searchNodeGraphBackward(context, matcher, current, current.getPreviousNodeInstances());
381            }
382        }
383        
384        public List<RouteNodeInstance> getActiveNodeInstances(DocumentRouteHeaderValue document, String nodeName) {
385                    Collection<RouteNodeInstance> activeNodes = getActiveNodeInstances(document.getDocumentId());
386                    List<RouteNodeInstance> foundNodes = new ArrayList<RouteNodeInstance>();
387            for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
388                RouteNodeInstance nodeInstance = iterator.next();
389                if (nodeInstance.getName().equals(nodeName)) {
390                    foundNodes.add(nodeInstance);
391                }
392            }
393            return foundNodes;
394        }
395        
396        private List determineExactPath(NodeGraphContext context, int searchDirection, Collection<RouteNodeInstance> startingNodeInstances) {
397            List<RouteNodeInstance> exactPath = new ArrayList<RouteNodeInstance>();
398            if (context.getResultNodeInstance() == null) {
399                    exactPath.addAll(context.getVisited().values());
400            } else {
401                    determineExactPath(exactPath, new HashMap<String, RouteNodeInstance>(), startingNodeInstances, context.getResultNodeInstance());
402            }
403            if (NodeGraphSearchCriteria.SEARCH_DIRECTION_FORWARD == searchDirection) {
404                    Collections.sort(exactPath, NODE_INSTANCE_BACKWARD_SORT);
405            } else {
406                    Collections.sort(exactPath, NODE_INSTANCE_FORWARD_SORT);
407            }
408            return exactPath;
409        }
410        
411        private void determineExactPath(List<RouteNodeInstance> exactPath, Map<String, RouteNodeInstance> visited, Collection<RouteNodeInstance> startingNodeInstances, RouteNodeInstance nodeInstance) {
412            if (nodeInstance == null) {
413                    return;
414            }
415            if (visited.containsKey(nodeInstance.getRouteNodeInstanceId())) {
416                    return;
417            }
418            visited.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
419            exactPath.add(nodeInstance);
420            for (RouteNodeInstance startingNode : startingNodeInstances) {
421                            if (startingNode.getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())) {
422                                    return;
423                            }
424                    }
425            for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext(); ) {
426                            RouteNodeInstance nextNodeInstance = iterator.next();
427                            determineExactPath(exactPath, visited, startingNodeInstances, nextNodeInstance);
428                    }
429        }
430        
431           
432        /**
433         * Sorts by RouteNodeId or the order the nodes will be evaluated in *roughly*.  This is 
434         * for display purposes when rendering a flattened list of nodes.
435         * 
436     * @author Kuali Rice Team (rice.collab@kuali.org)
437         */
438        private static class RouteNodeSorter implements Comparator {
439            public int compare(Object arg0, Object arg1) {
440                RouteNode rn1 = (RouteNode)arg0;
441                RouteNode rn2 = (RouteNode)arg1;
442                return rn1.getRouteNodeId().compareTo(rn2.getRouteNodeId());
443            }
444        }
445        
446        private static class NodeInstanceIdSorter implements Comparator {
447            public int compare(Object arg0, Object arg1) {
448                RouteNodeInstance nodeInstance1 = (RouteNodeInstance)arg0;
449                RouteNodeInstance nodeInstance2 = (RouteNodeInstance)arg1;
450                return nodeInstance1.getRouteNodeInstanceId().compareTo(nodeInstance2.getRouteNodeInstanceId());
451            }
452        }
453        
454        
455        public void deleteByRouteNodeInstance(RouteNodeInstance routeNodeInstance){
456            //update the route node instance link table to cancel the relationship between the to-be-deleted instance and the previous node instances
457            routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
458            //delete the routeNodeInstance and its next node instances
459            routeNodeDAO.deleteRouteNodeInstancesHereAfter(routeNodeInstance);
460        }
461        
462        public void deleteNodeStateById(Long nodeStateId){
463            routeNodeDAO.deleteNodeStateById(nodeStateId);
464        }
465        
466        public void deleteNodeStates(List statesToBeDeleted){
467            routeNodeDAO.deleteNodeStates(statesToBeDeleted);
468        }
469        
470        /**
471         * Records the revocation in the root BranchState of the document.
472         */
473        public void revokeNodeInstance(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
474            if (document == null) {
475                    throw new IllegalArgumentException("Document must not be null.");
476            }
477                    if (nodeInstance == null || nodeInstance.getRouteNodeInstanceId() == null) {
478                            throw new IllegalArgumentException("In order to revoke a final approval node the node instance must be persisent and have an id.");
479                    }
480                    // get the initial node instance, the root branch is where we will store the state
481            Branch rootBranch = document.getRootBranch();
482            BranchState state = null;
483            if (rootBranch != null) {
484                state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
485            }
486            if (state == null) {
487                    state = new BranchState();
488                    state.setKey(REVOKED_NODE_INSTANCES_STATE_KEY);
489                    state.setValue("");
490                    rootBranch.addBranchState(state);
491            }
492            if (state.getValue() == null) {
493                    state.setValue("");
494            }
495            state.setValue(state.getValue() + nodeInstance.getRouteNodeInstanceId() + ",");
496            save(rootBranch);
497            }
498    
499        /**
500         * Queries the list of revoked node instances from the root BranchState of the Document
501         * and returns a List of revoked RouteNodeInstances.
502         */
503            public List getRevokedNodeInstances(DocumentRouteHeaderValue document) {
504                    if (document == null) {
505                    throw new IllegalArgumentException("Document must not be null.");
506            }
507                    List<RouteNodeInstance> revokedNodeInstances = new ArrayList<RouteNodeInstance>();
508            
509            Branch rootBranch = document.getRootBranch();
510            BranchState state = null;
511            if (rootBranch != null) {
512                state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
513            }
514            if (state == null || org.apache.commons.lang.StringUtils.isEmpty(state.getValue())) {
515                    return revokedNodeInstances;
516            }
517            String[] revokedNodes = state.getValue().split(",");
518            for (int index = 0; index < revokedNodes.length; index++) {
519                            String revokedNodeInstanceId = revokedNodes[index];
520                            RouteNodeInstance revokedNodeInstance = findRouteNodeInstanceById(revokedNodeInstanceId);
521                            if (revokedNodeInstance == null) {
522                                    LOG.warn("Could not locate revoked RouteNodeInstance with the given id: " + revokedNodeInstanceId);
523                            } else {
524                                    revokedNodeInstances.add(revokedNodeInstance);
525                            }
526                    }
527            return revokedNodeInstances;
528            }
529        
530        
531    }