1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.engine.node.service.impl;
17
18 import org.apache.commons.collections.ComparatorUtils;
19 import org.kuali.rice.kew.doctype.bo.DocumentType;
20 import org.kuali.rice.kew.engine.RouteHelper;
21 import org.kuali.rice.kew.engine.node.Branch;
22 import org.kuali.rice.kew.engine.node.BranchState;
23 import org.kuali.rice.kew.engine.node.NodeGraphContext;
24 import org.kuali.rice.kew.engine.node.NodeGraphSearchCriteria;
25 import org.kuali.rice.kew.engine.node.NodeGraphSearchResult;
26 import org.kuali.rice.kew.engine.node.NodeMatcher;
27 import org.kuali.rice.kew.engine.node.NodeState;
28 import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
29 import org.kuali.rice.kew.engine.node.RouteNode;
30 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
31 import org.kuali.rice.kew.engine.node.RouteNodeUtils;
32 import org.kuali.rice.kew.engine.node.dao.RouteNodeDAO;
33 import org.kuali.rice.kew.engine.node.service.RouteNodeService;
34 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
35 import org.kuali.rice.kew.service.KEWServiceLocator;
36
37 import java.util.ArrayList;
38 import java.util.Arrays;
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 List<String> findPreviousNodeNames(String documentId) {
191 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
192 List<String> revokedIds = Collections.emptyList();
193
194 String revoked = document.getRootBranch().getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY) == null ? null : document.getRootBranch().getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY).getValue();
195 if (revoked != null) {
196 revokedIds = Arrays.asList(revoked.split(","));
197 }
198 List <RouteNodeInstance> currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
199 List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
200 for (RouteNodeInstance nodeInstance : currentNodeInstances) {
201 nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
202 }
203 List<String> nodeNames = new ArrayList<String>();
204 while (!nodeInstances.isEmpty()) {
205 RouteNodeInstance nodeInstance = nodeInstances.remove(0);
206 if (!revokedIds.contains(nodeInstance.getRouteNodeInstanceId())) {
207 nodeNames.add(nodeInstance.getName());
208 }
209 nodeInstances.addAll(nodeInstance.getPreviousNodeInstances());
210 }
211
212
213 Collections.reverse(nodeNames);
214
215 return nodeNames;
216 }
217
218 public List<String> findFutureNodeNames(String documentId) {
219 List currentNodeInstances = KEWServiceLocator.getRouteNodeService().getCurrentNodeInstances(documentId);
220 List<RouteNode> nodes = new ArrayList<RouteNode>();
221 for (Iterator iterator = currentNodeInstances.iterator(); iterator.hasNext();) {
222 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
223 nodes.addAll(nodeInstance.getRouteNode().getNextNodes());
224 }
225 List<String> nodeNames = new ArrayList<String>();
226 while (!nodes.isEmpty()) {
227 RouteNode node = nodes.remove(0);
228 if (!nodeNames.contains(node.getRouteNodeName())) {
229 nodeNames.add(node.getRouteNodeName());
230 }
231 nodes.addAll(node.getNextNodes());
232 }
233 return nodeNames;
234 }
235
236 public List<RouteNode> getFlattenedNodes(DocumentType documentType, boolean climbHierarchy) {
237 List<RouteNode> nodes = new ArrayList<RouteNode>();
238 if (!documentType.isRouteInherited() || climbHierarchy) {
239 for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) {
240 ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next();
241 nodes.addAll(getFlattenedNodes(process));
242 }
243 }
244 Collections.sort(nodes, new RouteNodeSorter());
245 return nodes;
246 }
247
248 public List<RouteNode> getFlattenedNodes(ProcessDefinitionBo process) {
249 Map<String, RouteNode> nodesMap = new HashMap<String, RouteNode>();
250 if (process.getInitialRouteNode() != null) {
251 flattenNodeGraph(nodesMap, process.getInitialRouteNode());
252 List<RouteNode> nodes = new ArrayList<RouteNode>(nodesMap.values());
253 Collections.sort(nodes, new RouteNodeSorter());
254 return nodes;
255 } else {
256 List<RouteNode> nodes = new ArrayList<RouteNode>();
257 nodes.add(new RouteNode());
258 return nodes;
259 }
260
261 }
262
263
264
265
266
267 private void flattenNodeGraph(Map<String, RouteNode> nodes, RouteNode node) {
268 if (node != null) {
269 if (nodes.containsKey(node.getRouteNodeName())) {
270 return;
271 }
272 nodes.put(node.getRouteNodeName(), node);
273 for (Iterator<RouteNode> iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
274 RouteNode nextNode = iterator.next();
275 flattenNodeGraph(nodes, nextNode);
276 }
277 } else {
278 return;
279 }
280 }
281
282 public List<RouteNodeInstance> getFlattenedNodeInstances(DocumentRouteHeaderValue document, boolean includeProcesses) {
283 List<RouteNodeInstance> nodeInstances = new ArrayList<RouteNodeInstance>();
284 Set<String> visitedNodeInstanceIds = new HashSet<String>();
285 for (Iterator<RouteNodeInstance> iterator = document.getInitialRouteNodeInstances().iterator(); iterator.hasNext();) {
286 RouteNodeInstance initialNodeInstance = iterator.next();
287 flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, initialNodeInstance, includeProcesses);
288 }
289 return nodeInstances;
290 }
291
292 private void flattenNodeInstanceGraph(List<RouteNodeInstance> nodeInstances, Set<String> visitedNodeInstanceIds, RouteNodeInstance nodeInstance, boolean includeProcesses) {
293
294 if (nodeInstance != null) {
295 if (visitedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
296 return;
297 }
298 if (includeProcesses && nodeInstance.getProcess() != null) {
299 flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nodeInstance.getProcess(), includeProcesses);
300 }
301 visitedNodeInstanceIds.add(nodeInstance.getRouteNodeInstanceId());
302 nodeInstances.add(nodeInstance);
303 for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
304 RouteNodeInstance nextNodeInstance = iterator.next();
305 flattenNodeInstanceGraph(nodeInstances, visitedNodeInstanceIds, nextNodeInstance, includeProcesses);
306 }
307
308 }
309
310 }
311
312 public NodeGraphSearchResult searchNodeGraph(NodeGraphSearchCriteria criteria) {
313 NodeGraphContext context = new NodeGraphContext();
314 if (criteria.getSearchDirection() == NodeGraphSearchCriteria.SEARCH_DIRECTION_BACKWARD) {
315 searchNodeGraphBackward(context, criteria.getMatcher(), null, criteria.getStartingNodeInstances());
316 } else {
317 throw new UnsupportedOperationException("Search feature can only search backward currently.");
318 }
319 List exactPath = determineExactPath(context, criteria.getSearchDirection(), criteria.getStartingNodeInstances());
320 return new NodeGraphSearchResult(context.getCurrentNodeInstance(), exactPath);
321 }
322
323 private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher, RouteNodeInstance previousNodeInstance, Collection nodeInstances) {
324 if (nodeInstances == null) {
325 return;
326 }
327 for (Iterator iterator = nodeInstances.iterator(); iterator.hasNext();) {
328 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
329 context.setPreviousNodeInstance(previousNodeInstance);
330 context.setCurrentNodeInstance(nodeInstance);
331 searchNodeGraphBackward(context, matcher);
332 if (context.getResultNodeInstance() != null) {
333
334 break;
335 }
336 }
337 }
338
339 private void searchNodeGraphBackward(NodeGraphContext context, NodeMatcher matcher) {
340 RouteNodeInstance current = context.getCurrentNodeInstance();
341 int numBranches = current.getNextNodeInstances().size();
342
343 if (numBranches > 1) {
344
345 Integer joinCount = (Integer)context.getSplitState().get(current.getRouteNodeInstanceId());
346 if (joinCount == null) {
347 joinCount = new Integer(0);
348 }
349
350 if (context.getPreviousNodeInstance() != null) {
351 joinCount = new Integer(joinCount.intValue()+1);
352 }
353 context.getSplitState().put(current.getRouteNodeInstanceId(), joinCount);
354
355 if (joinCount.intValue() != numBranches) {
356 return;
357 }
358 }
359 if (matcher.isMatch(context)) {
360 context.setResultNodeInstance(current);
361 } else {
362 context.getVisited().put(current.getRouteNodeInstanceId(), current);
363 searchNodeGraphBackward(context, matcher, current, current.getPreviousNodeInstances());
364 }
365 }
366
367 public List<RouteNodeInstance> getActiveNodeInstances(DocumentRouteHeaderValue document, String nodeName) {
368 Collection<RouteNodeInstance> activeNodes = getActiveNodeInstances(document.getDocumentId());
369 List<RouteNodeInstance> foundNodes = new ArrayList<RouteNodeInstance>();
370 for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
371 RouteNodeInstance nodeInstance = iterator.next();
372 if (nodeInstance.getName().equals(nodeName)) {
373 foundNodes.add(nodeInstance);
374 }
375 }
376 return foundNodes;
377 }
378
379 private List determineExactPath(NodeGraphContext context, int searchDirection, Collection<RouteNodeInstance> startingNodeInstances) {
380 List<RouteNodeInstance> exactPath = new ArrayList<RouteNodeInstance>();
381 if (context.getResultNodeInstance() == null) {
382 exactPath.addAll(context.getVisited().values());
383 } else {
384 determineExactPath(exactPath, new HashMap<String, RouteNodeInstance>(), startingNodeInstances, context.getResultNodeInstance());
385 }
386 if (NodeGraphSearchCriteria.SEARCH_DIRECTION_FORWARD == searchDirection) {
387 Collections.sort(exactPath, NODE_INSTANCE_BACKWARD_SORT);
388 } else {
389 Collections.sort(exactPath, NODE_INSTANCE_FORWARD_SORT);
390 }
391 return exactPath;
392 }
393
394 private void determineExactPath(List<RouteNodeInstance> exactPath, Map<String, RouteNodeInstance> visited, Collection<RouteNodeInstance> startingNodeInstances, RouteNodeInstance nodeInstance) {
395 if (nodeInstance == null) {
396 return;
397 }
398 if (visited.containsKey(nodeInstance.getRouteNodeInstanceId())) {
399 return;
400 }
401 visited.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
402 exactPath.add(nodeInstance);
403 for (RouteNodeInstance startingNode : startingNodeInstances) {
404 if (startingNode.getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())) {
405 return;
406 }
407 }
408 for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext(); ) {
409 RouteNodeInstance nextNodeInstance = iterator.next();
410 determineExactPath(exactPath, visited, startingNodeInstances, nextNodeInstance);
411 }
412 }
413
414
415
416
417
418
419
420
421 private static class RouteNodeSorter implements Comparator {
422 public int compare(Object arg0, Object arg1) {
423 RouteNode rn1 = (RouteNode)arg0;
424 RouteNode rn2 = (RouteNode)arg1;
425 return rn1.getRouteNodeId().compareTo(rn2.getRouteNodeId());
426 }
427 }
428
429 private static class NodeInstanceIdSorter implements Comparator {
430 public int compare(Object arg0, Object arg1) {
431 RouteNodeInstance nodeInstance1 = (RouteNodeInstance)arg0;
432 RouteNodeInstance nodeInstance2 = (RouteNodeInstance)arg1;
433 return nodeInstance1.getRouteNodeInstanceId().compareTo(nodeInstance2.getRouteNodeInstanceId());
434 }
435 }
436
437
438 public void deleteByRouteNodeInstance(RouteNodeInstance routeNodeInstance){
439
440 routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
441
442 routeNodeDAO.deleteRouteNodeInstancesHereAfter(routeNodeInstance);
443 }
444
445 public void deleteNodeStateById(Long nodeStateId){
446 routeNodeDAO.deleteNodeStateById(nodeStateId);
447 }
448
449 public void deleteNodeStates(List statesToBeDeleted){
450 routeNodeDAO.deleteNodeStates(statesToBeDeleted);
451 }
452
453
454
455
456 public void revokeNodeInstance(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
457 if (document == null) {
458 throw new IllegalArgumentException("Document must not be null.");
459 }
460 if (nodeInstance == null || nodeInstance.getRouteNodeInstanceId() == null) {
461 throw new IllegalArgumentException("In order to revoke a final approval node the node instance must be persisent and have an id.");
462 }
463
464 Branch rootBranch = document.getRootBranch();
465 BranchState state = null;
466 if (rootBranch != null) {
467 state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
468 }
469 if (state == null) {
470 state = new BranchState();
471 state.setKey(REVOKED_NODE_INSTANCES_STATE_KEY);
472 state.setValue("");
473 rootBranch.addBranchState(state);
474 }
475 if (state.getValue() == null) {
476 state.setValue("");
477 }
478 state.setValue(state.getValue() + nodeInstance.getRouteNodeInstanceId() + ",");
479 save(rootBranch);
480 }
481
482
483
484
485
486 public List getRevokedNodeInstances(DocumentRouteHeaderValue document) {
487 if (document == null) {
488 throw new IllegalArgumentException("Document must not be null.");
489 }
490 List<RouteNodeInstance> revokedNodeInstances = new ArrayList<RouteNodeInstance>();
491
492 Branch rootBranch = document.getRootBranch();
493 BranchState state = null;
494 if (rootBranch != null) {
495 state = rootBranch.getBranchState(REVOKED_NODE_INSTANCES_STATE_KEY);
496 }
497 if (state == null || org.apache.commons.lang.StringUtils.isEmpty(state.getValue())) {
498 return revokedNodeInstances;
499 }
500 String[] revokedNodes = state.getValue().split(",");
501 for (int index = 0; index < revokedNodes.length; index++) {
502 String revokedNodeInstanceId = revokedNodes[index];
503 RouteNodeInstance revokedNodeInstance = findRouteNodeInstanceById(revokedNodeInstanceId);
504 if (revokedNodeInstance == null) {
505 LOG.warn("Could not locate revoked RouteNodeInstance with the given id: " + revokedNodeInstanceId);
506 } else {
507 revokedNodeInstances.add(revokedNodeInstance);
508 }
509 }
510 return revokedNodeInstances;
511 }
512
513
514 }