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