1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
253
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
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
331 if (numBranches > 1) {
332
333 Integer joinCount = (Integer)context.getSplitState().get(current.getRouteNodeInstanceId());
334 if (joinCount == null) {
335 joinCount = new Integer(0);
336 }
337
338 if (context.getPreviousNodeInstance() != null) {
339 joinCount = new Integer(joinCount.intValue()+1);
340 }
341 context.getSplitState().put(current.getRouteNodeInstanceId(), joinCount);
342
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
405
406
407
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
428 routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
429
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
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
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
472
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 }