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.node.service.impl;
017
018import org.apache.commons.collections.ComparatorUtils;
019import org.kuali.rice.kew.doctype.bo.DocumentType;
020import org.kuali.rice.kew.engine.RouteHelper;
021import org.kuali.rice.kew.engine.node.Branch;
022import org.kuali.rice.kew.engine.node.BranchState;
023import org.kuali.rice.kew.engine.node.NodeGraphContext;
024import org.kuali.rice.kew.engine.node.NodeGraphSearchCriteria;
025import org.kuali.rice.kew.engine.node.NodeGraphSearchResult;
026import org.kuali.rice.kew.engine.node.NodeMatcher;
027import org.kuali.rice.kew.engine.node.NodeState;
028import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
029import org.kuali.rice.kew.engine.node.RouteNode;
030import org.kuali.rice.kew.engine.node.RouteNodeInstance;
031import org.kuali.rice.kew.engine.node.RouteNodeUtils;
032import org.kuali.rice.kew.engine.node.dao.RouteNodeDAO;
033import org.kuali.rice.kew.engine.node.service.RouteNodeService;
034import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
035import org.kuali.rice.kew.service.KEWServiceLocator;
036
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.Comparator;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.Iterator;
045import java.util.List;
046import java.util.Map;
047import java.util.Set;
048
049
050
051public 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}