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 }