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