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