View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    * 
4    * 
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    * http://www.opensource.org/licenses/ecl2.php
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.engine.node.service.impl;
18  
19  import org.apache.commons.collections.ComparatorUtils;
20  import org.kuali.rice.kew.doctype.bo.DocumentType;
21  import org.kuali.rice.kew.engine.RouteHelper;
22  import org.kuali.rice.kew.engine.node.Branch;
23  import org.kuali.rice.kew.engine.node.BranchState;
24  import org.kuali.rice.kew.engine.node.NodeGraphContext;
25  import org.kuali.rice.kew.engine.node.NodeGraphSearchCriteria;
26  import org.kuali.rice.kew.engine.node.NodeGraphSearchResult;
27  import org.kuali.rice.kew.engine.node.NodeMatcher;
28  import org.kuali.rice.kew.engine.node.NodeState;
29  import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
30  import org.kuali.rice.kew.engine.node.RouteNode;
31  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
32  import org.kuali.rice.kew.engine.node.RouteNodeUtils;
33  import org.kuali.rice.kew.engine.node.dao.RouteNodeDAO;
34  import org.kuali.rice.kew.engine.node.service.RouteNodeService;
35  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
36  import org.kuali.rice.kew.service.KEWServiceLocator;
37  
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.Comparator;
42  import java.util.HashMap;
43  import java.util.HashSet;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  
50  
51  public class RouteNodeServiceImpl implements RouteNodeService {
52  
53  	protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
54  	
55  	public static final String REVOKED_NODE_INSTANCES_STATE_KEY = "NodeInstances.Revoked";
56  
57  	private static final Comparator NODE_INSTANCE_FORWARD_SORT = new NodeInstanceIdSorter();
58  	private static final Comparator NODE_INSTANCE_BACKWARD_SORT = 
59  		ComparatorUtils.reversedComparator(NODE_INSTANCE_FORWARD_SORT);
60      private RouteHelper helper = new RouteHelper();
61  	private RouteNodeDAO routeNodeDAO;
62  	
63      public void save(RouteNode node) {
64      	routeNodeDAO.save(node);
65      }
66      
67      public void save(RouteNodeInstance nodeInstance) {
68      	routeNodeDAO.save(nodeInstance);
69      }
70      
71      public void save(NodeState nodeState) {
72          routeNodeDAO.save(nodeState);
73      }
74      
75      public void save(Branch branch) {
76          routeNodeDAO.save(branch);
77      }
78  
79      public RouteNode findRouteNodeById(String nodeId) {
80      	return routeNodeDAO.findRouteNodeById(nodeId);
81      }
82      
83      public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId) {
84      	return routeNodeDAO.findRouteNodeInstanceById(nodeInstanceId);
85      }
86  
87      public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId, DocumentRouteHeaderValue document) {
88      	return RouteNodeUtils.findRouteNodeInstanceById(nodeInstanceId, document);
89      }
90      
91      public List<RouteNodeInstance> getCurrentNodeInstances(String documentId) {
92          List<RouteNodeInstance> currentNodeInstances = getActiveNodeInstances(documentId);
93          if (currentNodeInstances.isEmpty()) {
94              currentNodeInstances = getTerminalNodeInstances(documentId);
95          }
96          return currentNodeInstances;
97      }
98      
99      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     public List<RouteNodeInstance> getTerminalNodeInstances(String documentId) {
115         return routeNodeDAO.getTerminalNodeInstances(documentId);
116     }
117     
118     public List getInitialNodeInstances(String documentId) {
119     	return routeNodeDAO.getInitialNodeInstances(documentId);
120     }
121     
122     public NodeState findNodeState(Long nodeInstanceId, String key) {
123         return routeNodeDAO.findNodeState(nodeInstanceId, key);
124     }
125     
126     public RouteNode findRouteNodeByName(String documentTypeId, String name) {
127         return routeNodeDAO.findRouteNodeByName(documentTypeId, name);
128     }
129     
130     public List<RouteNode> findFinalApprovalRouteNodes(String documentTypeId) {
131         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findById(documentTypeId);
132         documentType = documentType.getRouteDefiningDocumentType();
133         return routeNodeDAO.findFinalApprovalRouteNodes(documentType.getDocumentTypeId());
134     }
135     
136     public List findNextRouteNodesInPath(RouteNodeInstance nodeInstance, String nodeName) {
137         List<RouteNode> nodesInPath = new ArrayList<RouteNode>();
138         for (Iterator<RouteNode> iterator = nodeInstance.getRouteNode().getNextNodes().iterator(); iterator.hasNext();) {
139             RouteNode nextNode = iterator.next();
140             nodesInPath.addAll(findNextRouteNodesInPath(nodeName, nextNode, new HashSet<String>()));
141         }
142         return nodesInPath;
143     }
144     
145     private List<RouteNode> findNextRouteNodesInPath(String nodeName, RouteNode node, Set<String> inspected) {
146         List<RouteNode> nextNodesInPath = new ArrayList<RouteNode>();
147         if (inspected.contains(node.getRouteNodeId())) {
148             return nextNodesInPath;
149         }
150         inspected.add(node.getRouteNodeId());
151         if (node.getRouteNodeName().equals(nodeName)) {
152             nextNodesInPath.add(node);
153         } else {
154             if (helper.isSubProcessNode(node)) {
155                 ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
156                 RouteNode subNode = subProcess.getInitialRouteNode();
157                 nextNodesInPath.addAll(findNextRouteNodesInPath(nodeName, subNode, inspected));
158             }
159             for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
160                 RouteNode nextNode = iterator.next();
161                 nextNodesInPath.addAll(findNextRouteNodesInPath(nodeName, nextNode, inspected));
162             }
163         }
164         return nextNodesInPath;
165     }
166     
167     public boolean isNodeInPath(DocumentRouteHeaderValue document, String nodeName) {
168         boolean isInPath = false;
169         Collection<RouteNodeInstance> activeNodes = getActiveNodeInstances(document.getDocumentId());
170         for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
171             RouteNodeInstance nodeInstance = iterator.next();
172             List nextNodesInPath = findNextRouteNodesInPath(nodeInstance, nodeName);
173             isInPath = isInPath || !nextNodesInPath.isEmpty();
174         }
175         return isInPath;
176     }
177     
178     public List findRouteNodeInstances(String documentId) {
179         return this.routeNodeDAO.findRouteNodeInstances(documentId);
180     }
181     
182 	public void setRouteNodeDAO(RouteNodeDAO dao) {
183 		this.routeNodeDAO = dao;
184 	}
185     
186     public List findProcessNodeInstances(RouteNodeInstance process) {
187        return this.routeNodeDAO.findProcessNodeInstances(process);
188     }
189     
190     public Set findPreviousNodeNames(String documentId) {
191         List currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
192         List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
193         for (Iterator iterator = currentNodeInstances.iterator(); iterator.hasNext();) {
194             RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
195             nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
196         }
197         Set<String> nodeNames = new HashSet<String>();
198         while (!nodeInstances.isEmpty()) {
199             RouteNodeInstance nodeInstance = nodeInstances.remove(0);
200             nodeNames.add(nodeInstance.getName());
201             nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
202         }
203         return nodeNames;
204     }
205     
206     public List<String> findFutureNodeNames(String documentId) {
207         List currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
208         List<RouteNode> nodes = new ArrayList<RouteNode>();
209         for (Iterator iterator = currentNodeInstances.iterator(); iterator.hasNext();) {
210             RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
211             nodes.addAll(nodeInstance.getRouteNode().getNextNodes());
212         }
213         List<String> nodeNames = new ArrayList<String>();
214         while (!nodes.isEmpty()) {
215             RouteNode node = nodes.remove(0);
216             if (!nodeNames.contains(node.getRouteNodeName())) {
217         	nodeNames.add(node.getRouteNodeName());
218             }
219             nodes.addAll(node.getNextNodes());
220         }
221         return nodeNames;
222     }
223     
224     public List<RouteNode> getFlattenedNodes(DocumentType documentType, boolean climbHierarchy) {
225         List<RouteNode> nodes = new ArrayList<RouteNode>();
226         if (!documentType.isRouteInherited() || climbHierarchy) {
227             for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) {
228                 ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next();
229                 nodes.addAll(getFlattenedNodes(process));
230             }
231         }
232         Collections.sort(nodes, new RouteNodeSorter());
233         return nodes;
234     }
235     
236     public List<RouteNode> getFlattenedNodes(ProcessDefinitionBo process) {
237         Map<String, RouteNode> nodesMap = new HashMap<String, RouteNode>();
238         if (process.getInitialRouteNode() != null) {
239             flattenNodeGraph(nodesMap, process.getInitialRouteNode());
240             List<RouteNode> nodes = new ArrayList<RouteNode>(nodesMap.values());
241             Collections.sort(nodes, new RouteNodeSorter());
242             return nodes;
243         } else {
244             List<RouteNode> nodes = new ArrayList<RouteNode>();
245             nodes.add(new RouteNode());
246             return nodes;
247         }
248 
249     }
250     
251     /**
252      * Recursively walks the node graph and builds up the map.  Uses a map because we will
253      * end up walking through duplicates, as is the case with Join nodes.
254      */
255     private void flattenNodeGraph(Map<String, RouteNode> nodes, RouteNode node) {
256         if (node != null) {
257             if (nodes.containsKey(node.getRouteNodeName())) {
258                 return;
259             }
260             nodes.put(node.getRouteNodeName(), node);
261             for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
262                 RouteNode nextNode = iterator.next();
263                 flattenNodeGraph(nodes, nextNode);
264             }
265         } else {
266             return;
267         }
268     }        
269     
270     public List<RouteNodeInstance> getFlattenedNodeInstances(DocumentRouteHeaderValue document, boolean includeProcesses) {
271         List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
272         Set<String> visitedNodeInstanceIds = new HashSet<String>();
273         for (Iterator<RouteNodeInstance> iterator = document.getInitialRouteNodeInstances().iterator(); iterator.hasNext();) {
274             RouteNodeInstance initialNodeInstance = iterator.next();
275             flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, initialNodeInstance, includeProcesses);    
276         }
277         return nodeInstances;
278     }
279     
280 	private void flattenNodeInstanceGraph(List<RouteNodeInstance> nodeInstances, Set<String> visitedNodeInstanceIds, RouteNodeInstance nodeInstance, boolean includeProcesses) {
281 
282 		if (nodeInstance != null) {
283 			if (visitedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
284 				return;
285 			}
286 			if (includeProcesses && nodeInstance.getProcess() != null) {
287 				flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nodeInstance.getProcess(), includeProcesses);
288 			}
289 			visitedNodeInstanceIds.add(nodeInstance.getRouteNodeInstanceId());
290 			nodeInstances.add(nodeInstance);
291 			for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
292 				RouteNodeInstance nextNodeInstance = iterator.next();
293 				flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nextNodeInstance, includeProcesses);
294 			}
295 
296 		}
297 
298     }      
299     
300     public NodeGraphSearchResult searchNodeGraph(NodeGraphSearchCriteria criteria) {
301     	NodeGraphContext context = new NodeGraphContext();
302     	if (criteria.getSearchDirection() == NodeGraphSearchCriteria.SEARCH_DIRECTION_BACKWARD) {
303         	searchNodeGraphBackward(context, criteria.getMatcher(), null, criteria.getStartingNodeInstances());
304     	} else {
305     		throw new UnsupportedOperationException("Search feature can only search backward currently.");
306     	}
307     	List exactPath = determineExactPath(context, criteria.getSearchDirection(), criteria.getStartingNodeInstances());
308         return new NodeGraphSearchResult(context.getCurrentNodeInstance(), exactPath);
309     }
310     
311     private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher, RouteNodeInstance previousNodeInstance, Collection nodeInstances) {
312         if (nodeInstances == null) {
313             return;
314         }
315     	for (Iterator iterator = nodeInstances.iterator(); iterator.hasNext();) {
316             RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
317             context.setPreviousNodeInstance(previousNodeInstance);
318             context.setCurrentNodeInstance(nodeInstance);
319             searchNodeGraphBackward(context, matcher);
320             if (context.getResultNodeInstance() != null) {
321             	// we've located the node instance we're searching for, we're done
322             	break;
323             }
324         }
325     }
326     
327     private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher) {
328         RouteNodeInstance current = context.getCurrentNodeInstance();
329         int numBranches = current.getNextNodeInstances().size();
330         // if this is a split node, we want to wait here, until all branches join back to us
331         if (numBranches > 1) {
332         	// determine the number of branches that have joined back to the split thus far
333             Integer joinCount = (Integer)context.getSplitState().get(current.getRouteNodeInstanceId());
334             if (joinCount == null) {
335                 joinCount = new Integer(0);
336             }
337             // if this split is not a leaf node we increment the count
338             if (context.getPreviousNodeInstance() != null) {
339                 joinCount = new Integer(joinCount.intValue()+1);
340             }
341             context.getSplitState().put(current.getRouteNodeInstanceId(), joinCount);
342             // if not all branches have joined, stop and wait for other branches to join
343             if (joinCount.intValue() != numBranches) {
344                 return;
345             }
346         }
347         if (matcher.isMatch(context)) {
348             context.setResultNodeInstance(current);
349         } else {
350             context.getVisited().put(current.getRouteNodeInstanceId(), current);
351             searchNodeGraphBackward(context, matcher, current, current.getPreviousNodeInstances());
352         }
353     }
354     
355     public List<RouteNodeInstance> getActiveNodeInstances(DocumentRouteHeaderValue document, String nodeName) {
356 		Collection<RouteNodeInstance> activeNodes = getActiveNodeInstances(document.getDocumentId());
357 		List<RouteNodeInstance> foundNodes = new ArrayList<RouteNodeInstance>();
358         for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
359             RouteNodeInstance nodeInstance = iterator.next();
360             if (nodeInstance.getName().equals(nodeName)) {
361             	foundNodes.add(nodeInstance);
362             }
363         }
364         return foundNodes;
365     }
366     
367     private List determineExactPath(NodeGraphContext context, int searchDirection, Collection<RouteNodeInstance> startingNodeInstances) {
368     	List<RouteNodeInstance> exactPath = new ArrayList<RouteNodeInstance>();
369     	if (context.getResultNodeInstance() == null) {
370     		exactPath.addAll(context.getVisited().values());
371     	} else {
372     		determineExactPath(exactPath, new HashMap<String, RouteNodeInstance>(), startingNodeInstances, context.getResultNodeInstance());
373     	}
374     	if (NodeGraphSearchCriteria.SEARCH_DIRECTION_FORWARD == searchDirection) {
375     		Collections.sort(exactPath, NODE_INSTANCE_BACKWARD_SORT);
376     	} else {
377     		Collections.sort(exactPath, NODE_INSTANCE_FORWARD_SORT);
378     	}
379     	return exactPath;
380     }
381     
382     private void determineExactPath(List<RouteNodeInstance> exactPath, Map<String, RouteNodeInstance> visited, Collection<RouteNodeInstance> startingNodeInstances, RouteNodeInstance nodeInstance) {
383     	if (nodeInstance == null) {
384     		return;
385     	}
386     	if (visited.containsKey(nodeInstance.getRouteNodeInstanceId())) {
387     		return;
388     	}
389     	visited.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
390     	exactPath.add(nodeInstance);
391     	for (RouteNodeInstance startingNode : startingNodeInstances) {
392 			if (startingNode.getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())) {
393 				return;
394 			}
395 		}
396     	for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext(); ) {
397 			RouteNodeInstance nextNodeInstance = iterator.next();
398 			determineExactPath(exactPath, visited, startingNodeInstances, nextNodeInstance);
399 		}
400     }
401     
402        
403     /**
404      * Sorts by RouteNodeId or the order the nodes will be evaluated in *roughly*.  This is 
405      * for display purposes when rendering a flattened list of nodes.
406      * 
407  * @author Kuali Rice Team (rice.collab@kuali.org)
408      */
409     private static class RouteNodeSorter implements Comparator {
410         public int compare(Object arg0, Object arg1) {
411             RouteNode rn1 = (RouteNode)arg0;
412             RouteNode rn2 = (RouteNode)arg1;
413             return rn1.getRouteNodeId().compareTo(rn2.getRouteNodeId());
414         }
415     }
416     
417     private static class NodeInstanceIdSorter implements Comparator {
418         public int compare(Object arg0, Object arg1) {
419             RouteNodeInstance nodeInstance1 = (RouteNodeInstance)arg0;
420             RouteNodeInstance nodeInstance2 = (RouteNodeInstance)arg1;
421             return nodeInstance1.getRouteNodeInstanceId().compareTo(nodeInstance2.getRouteNodeInstanceId());
422         }
423     }
424     
425     
426     public void deleteByRouteNodeInstance(RouteNodeInstance routeNodeInstance){
427     	//update the route node instance link table to cancel the relationship between the to-be-deleted instance and the previous node instances
428     	routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
429     	//delete the routeNodeInstance and its next node instances
430     	routeNodeDAO.deleteRouteNodeInstancesHereAfter(routeNodeInstance);
431     }
432     
433     public void deleteNodeStateById(Long nodeStateId){
434     	routeNodeDAO.deleteNodeStateById(nodeStateId);
435     }
436     
437     public void deleteNodeStates(List statesToBeDeleted){
438     	routeNodeDAO.deleteNodeStates(statesToBeDeleted);
439     }
440     
441     /**
442      * Records the revocation in the root BranchState of the document.
443      */
444     public void revokeNodeInstance(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
445     	if (document == null) {
446     		throw new IllegalArgumentException("Document must not be null.");
447     	}
448 		if (nodeInstance == null || nodeInstance.getRouteNodeInstanceId() == null) {
449 			throw new IllegalArgumentException("In order to revoke a final approval node the node instance must be persisent and have an id.");
450 		}
451 		// get the initial node instance, the root branch is where we will store the state
452     	Branch rootBranch = document.getRootBranch();
453     	BranchState state = null;
454     	if (rootBranch != null) {
455     	    state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
456     	}
457     	if (state == null) {
458     		state = new BranchState();
459     		state.setKey(REVOKED_NODE_INSTANCES_STATE_KEY);
460     		state.setValue("");
461     		rootBranch.addBranchState(state);
462     	}
463     	if (state.getValue() == null) {
464     		state.setValue("");
465     	}
466     	state.setValue(state.getValue() + nodeInstance.getRouteNodeInstanceId() + ",");
467     	save(rootBranch);
468 	}
469 
470     /**
471      * Queries the list of revoked node instances from the root BranchState of the Document
472      * and returns a List of revoked RouteNodeInstances.
473      */
474 	public List getRevokedNodeInstances(DocumentRouteHeaderValue document) {
475 		if (document == null) {
476     		throw new IllegalArgumentException("Document must not be null.");
477     	}
478 		List<RouteNodeInstance> revokedNodeInstances = new ArrayList<RouteNodeInstance>();
479     	
480     	Branch rootBranch = document.getRootBranch();
481     	BranchState state = null;
482     	if (rootBranch != null) {
483     	    state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
484     	}
485     	if (state == null || org.apache.commons.lang.StringUtils.isEmpty(state.getValue())) {
486     		return revokedNodeInstances;
487     	}
488     	String[] revokedNodes = state.getValue().split(",");
489     	for (int index = 0; index < revokedNodes.length; index++) {
490 			String revokedNodeInstanceId = revokedNodes[index];
491 			RouteNodeInstance revokedNodeInstance = findRouteNodeInstanceById(revokedNodeInstanceId);
492 			if (revokedNodeInstance == null) {
493 				LOG.warn("Could not locate revoked RouteNodeInstance with the given id: " + revokedNodeInstanceId);
494 			} else {
495 				revokedNodeInstances.add(revokedNodeInstance);
496 			}
497 		}
498     	return revokedNodeInstances;
499 	}
500     
501     
502 }