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