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 import org.kuali.rice.krad.data.DataObjectService;
37 import org.kuali.rice.krad.data.PersistenceOption;
38 import org.springframework.beans.factory.annotation.Required;
39
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.Comparator;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51
52
53
54 public class RouteNodeServiceImpl implements RouteNodeService {
55
56 protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
57
58 public static final String REVOKED_NODE_INSTANCES_STATE_KEY = "NodeInstances.Revoked";
59
60 private static final Comparator NODE_INSTANCE_FORWARD_SORT = new NodeInstanceIdSorter();
61 private static final Comparator NODE_INSTANCE_BACKWARD_SORT =
62 ComparatorUtils.reversedComparator(NODE_INSTANCE_FORWARD_SORT);
63 private RouteHelper helper = new RouteHelper();
64 private RouteNodeDAO routeNodeDAO;
65
66 private DataObjectService dataObjectService;
67
68 public RouteNode save(RouteNode node) {
69 return dataObjectService.save(node);
70 }
71
72 public RouteNodeInstance save(RouteNodeInstance nodeInstance) {
73 return dataObjectService.save(nodeInstance);
74 }
75
76 public void save(NodeState nodeState) {
77 dataObjectService.save(nodeState);
78 }
79
80 public Branch save(Branch branch) {
81 return dataObjectService.save(branch);
82 }
83
84 public RouteNode findRouteNodeById(String nodeId) {
85 return dataObjectService.find(RouteNode.class,nodeId);
86 }
87
88 public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId) {
89 return routeNodeDAO.findRouteNodeInstanceById(nodeInstanceId);
90 }
91
92 public RouteNodeInstance findRouteNodeInstanceById(String nodeInstanceId, DocumentRouteHeaderValue document) {
93 return RouteNodeUtils.findRouteNodeInstanceById(nodeInstanceId, document);
94 }
95
96 public List<RouteNodeInstance> getCurrentNodeInstances(String documentId) {
97 List<RouteNodeInstance> currentNodeInstances = getActiveNodeInstances(documentId);
98 if (currentNodeInstances.isEmpty()) {
99 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
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
290
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
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
368 if (numBranches > 1) {
369
370 Integer joinCount = (Integer)context.getSplitState().get(current.getRouteNodeInstanceId());
371 if (joinCount == null) {
372 joinCount = new Integer(0);
373 }
374
375 if (context.getPreviousNodeInstance() != null) {
376 joinCount = new Integer(joinCount.intValue()+1);
377 }
378 context.getSplitState().put(current.getRouteNodeInstanceId(), joinCount);
379
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
442
443
444
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
465 routeNodeDAO.deleteLinksToPreNodeInstances(routeNodeInstance);
466
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
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
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
509
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 }