1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.rice.kew.engine.node.hierarchyrouting;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26
27 import javax.xml.XMLConstants;
28 import javax.xml.namespace.QName;
29
30 import org.apache.commons.lang.StringUtils;
31 import org.apache.log4j.Logger;
32 import org.kuali.rice.core.api.reflect.ObjectDefinition;
33 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
34 import org.kuali.rice.core.api.reflect.ObjectDefinition;
35 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
36 import org.kuali.rice.kew.api.WorkflowRuntimeException;
37 import org.kuali.rice.kew.doctype.bo.DocumentType;
38 import org.kuali.rice.kew.engine.RouteContext;
39 import org.kuali.rice.kew.engine.RouteHelper;
40 import org.kuali.rice.kew.engine.node.Branch;
41 import org.kuali.rice.kew.engine.node.DynamicNode;
42 import org.kuali.rice.kew.engine.node.DynamicResult;
43 import org.kuali.rice.kew.engine.node.NoOpNode;
44 import org.kuali.rice.kew.engine.node.NodeState;
45 import org.kuali.rice.kew.engine.node.Process;
46 import org.kuali.rice.kew.engine.node.RequestsNode;
47 import org.kuali.rice.kew.engine.node.RouteNode;
48 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
49 import org.kuali.rice.kew.engine.node.SimpleJoinNode;
50 import org.kuali.rice.kew.engine.node.SimpleSplitNode;
51 import org.kuali.rice.kew.engine.node.hierarchyrouting.HierarchyProvider.Stop;
52 import org.kuali.rice.kew.engine.transition.SplitTransitionEngine;
53 import org.kuali.rice.kew.service.KEWServiceLocator;
54 import org.kuali.rice.kew.util.Utilities;
55
56
57
58
59
60
61 public class HierarchyRoutingNode implements DynamicNode {
62 protected final Logger LOG = Logger.getLogger(getClass());
63
64
65
66
67 public static final String HIERARCHY_PROVIDER = "hierarchyProvider";
68
69
70
71 public static final String STOP_ID = "stop_id";
72
73 protected static final String SPLIT_PROCESS_NAME = "Hierarchy Split";
74 protected static final String JOIN_PROCESS_NAME = "Hierarchy Join";
75 protected static final String REQUEST_PROCESS_NAME = "Hierarchy Request";
76 protected static final String NO_STOP_NAME = "No stop";
77
78
79 private static final String VISITED_STOPS = "visited_stops";
80 private static final String V_STOPS_DEL = ",";
81
82 private static final String INITIAL_SPLIT_NODE_MARKER = "InitialSplitNode";
83
84
85
86
87
88
89
90 protected HierarchyProvider getHierarchyProvider(RouteNodeInstance nodeInstance, RouteContext context) {
91 Map<String, String> cfgMap = Utilities.getKeyValueCollectionAsMap(nodeInstance.getRouteNode().getConfigParams());
92 String hierarchyProviderClass = cfgMap.get(HIERARCHY_PROVIDER);
93 if (StringUtils.isEmpty(hierarchyProviderClass)) {
94 throw new WorkflowRuntimeException("hierarchyProvider configuration parameter not set for HierarchyRoutingNode: " + nodeInstance.getName());
95 }
96 QName qn = QName.valueOf(hierarchyProviderClass);
97 ObjectDefinition od;
98 if (XMLConstants.NULL_NS_URI.equals(qn.getNamespaceURI())) {
99 od = new ObjectDefinition(qn.getLocalPart());
100 } else {
101 od = new ObjectDefinition(qn.getLocalPart(), qn.getNamespaceURI());
102 }
103 HierarchyProvider hp = (HierarchyProvider) GlobalResourceLoader.getObject(od);
104 hp.init(nodeInstance, context);
105 return hp;
106 }
107
108 public DynamicResult transitioningInto(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper) throws Exception {
109
110 HierarchyProvider provider = getHierarchyProvider(dynamicNodeInstance, context);
111 DocumentType documentType = setUpDocumentType(provider, context.getDocument().getDocumentType(), dynamicNodeInstance);
112 RouteNode splitNode = documentType.getNamedProcess(SPLIT_PROCESS_NAME).getInitialRouteNode();
113
114
115 RouteNodeInstance splitNodeInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), splitNode);
116 splitNodeInstance.setBranch(dynamicNodeInstance.getBranch());
117 markAsInitialSplitNode(splitNodeInstance);
118
119 int i = 0;
120 List<Stop> stops = provider.getLeafStops(context);
121 if (stops.isEmpty()) {
122
123 RouteNode noStopNode = documentType.getNamedProcess(NO_STOP_NAME).getInitialRouteNode();
124 RouteNodeInstance noChartOrgInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), noStopNode);
125 noChartOrgInstance.setBranch(dynamicNodeInstance.getBranch());
126
127 provider.setStop(noChartOrgInstance, null);
128
129 return new DynamicResult(true, noChartOrgInstance);
130 }
131 for (Stop stop: stops) {
132 RouteNode requestNode = getStopRequestNode(stop, documentType);
133 createInitialRequestNodeInstance(provider, stop, splitNodeInstance, dynamicNodeInstance, requestNode);
134 }
135
136 return new DynamicResult(false, splitNodeInstance);
137 }
138
139 public DynamicResult transitioningOutOf(RouteContext context, RouteHelper helper) throws Exception {
140
141
142 HierarchyProvider provider = getHierarchyProvider(context.getNodeInstance().getProcess(), context);
143
144 RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
145 RouteNodeInstance curStopNode = context.getNodeInstance();
146 Map<String, RouteNodeInstance> stopRequestNodeMap = new HashMap<String, RouteNodeInstance>();
147 findStopRequestNodes(provider, context, stopRequestNodeMap);
148
149 Stop stop = provider.getStop(curStopNode);
150
151 if (provider.isRoot(stop)) {
152 return new DynamicResult(true, null);
153 }
154
155
156
157 InnerTransitionResult transition = canTransitionFrom(provider, stop, stopRequestNodeMap.values(), helper);
158 DynamicResult result = null;
159 if (transition.isCanTransition()) {
160 DocumentType documentType = context.getDocument().getDocumentType();
161
162 RouteNodeInstance requestNode = createNextStopRequestNodeInstance(provider, context, stop, processInstance, helper);
163
164 if (transition.getSiblings().isEmpty()) {
165 result = new DynamicResult(false, requestNode);
166 } else {
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191 }
192
193 } else {
194 result = new DynamicResult(false, null);
195 }
196 result.getNextNodeInstances().addAll(getNewlyAddedOrgRouteInstances(provider, context, helper));
197 return result;
198 }
199
200 private void findStopRequestNodes(HierarchyProvider provider, RouteContext context, Map<String, RouteNodeInstance> stopRequestNodes) {
201 List<RouteNodeInstance> nodeInstances = KEWServiceLocator.getRouteNodeService().getFlattenedNodeInstances(context.getDocument(), true);
202 for (RouteNodeInstance nodeInstance: nodeInstances) {
203 if (provider.hasStop(nodeInstance)) {
204 LOG.debug("Stop node instance: " + nodeInstance);
205 stopRequestNodes.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
206 }
207 }
208
209 }
210
211 private RouteNodeInstance createNextStopRequestNodeInstance(HierarchyProvider provider, RouteContext context, Stop stop, RouteNodeInstance processInstance, RouteHelper helper) {
212 Stop futureStop = provider.getParent(stop);
213 LOG.debug("Creating next stop request node instance " + provider.getStopIdentifier(futureStop) + " as parent of " + provider.getStopIdentifier(stop));
214 RouteNode requestsPrototype = getStopRequestNode(futureStop, context.getDocument().getDocumentType());
215 RouteNodeInstance requestNode = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), requestsPrototype);
216 requestNode.setBranch(processInstance.getBranch());
217 NodeState ns = new NodeState();
218 ns.setKey(STOP_ID);
219 ns.setValue(provider.getStopIdentifier(futureStop));
220 requestNode.addNodeState(ns);
221 provider.setStop(requestNode, futureStop);
222 LOG.debug("Stop set on request node: " + provider.getStop(requestNode));
223 addStopToProcessState(provider, processInstance, futureStop);
224 return requestNode;
225 }
226
227
228
229
230
231
232
233
234 private InnerTransitionResult canTransitionFrom(HierarchyProvider provider, Stop currentStop, Collection<RouteNodeInstance> requestNodes, RouteHelper helper) {
235 LOG.debug("Testing whether we can transition from stop: " + currentStop);
236 Stop parent = provider.getParent(currentStop);
237 InnerTransitionResult result = new InnerTransitionResult();
238 result.setCanTransition(false);
239
240 for (RouteNodeInstance requestNode: requestNodes) {
241 if (!provider.hasStop(requestNode)) {
242 LOG.debug("request node " + requestNode.getName() + " does not have a stop associated with it");
243 continue;
244 }
245
246 Stop requestNodeStop = provider.getStop(requestNode);
247 LOG.debug("Request node: " + requestNode.getRouteNodeInstanceId() + " has stop " + requestNodeStop.toString());
248 if (requestNodeStop != null && provider.equals(currentStop, requestNodeStop)) {
249 LOG.debug("Skipping node " + requestNode.getName() + " because it is associated with the current stop");
250 continue;
251 }
252
253
254 Stop stop = provider.getStop(requestNode);
255
256 LOG.debug("Found an outstanding stop: " + stop);
257
258 boolean isChildOfMyParent = isDescendent(provider, parent, stop);
259
260 if (isChildOfMyParent) {
261 LOG.debug("Found stop node whose parent is my parent:");
262 LOG.debug("Stop: " + stop);
263 LOG.debug("Node: " + requestNode);
264
265
266 if (requestNode.isActive()) {
267
268 result.getSiblings().clear();
269 return result;
270 }
271
272
273
274
275
276
277
278
279 }
280 }
281 result.setCanTransition(true);
282 return result;
283 }
284
285 protected boolean isDescendent(HierarchyProvider provider, Stop parent, Stop otherStop) {
286 return provider.isRoot(parent) || hasAsParent(provider, parent, otherStop);
287 }
288
289 private static class InnerTransitionResult {
290 private boolean canTransition;
291 private List<RouteNodeInstance> siblings = new ArrayList<RouteNodeInstance>();
292
293 public boolean isCanTransition() {
294 return canTransition;
295 }
296
297 public void setCanTransition(boolean canTransition) {
298 this.canTransition = canTransition;
299 }
300
301 public List<RouteNodeInstance> getSiblings() {
302 return siblings;
303 }
304
305 public void setSiblings(List<RouteNodeInstance> siblings) {
306 this.siblings = siblings;
307 }
308 }
309
310 private static void markAsInitialSplitNode(RouteNodeInstance splitNode) {
311 NodeState ns = new NodeState();
312 ns.setKey(INITIAL_SPLIT_NODE_MARKER);
313 ns.setValue(INITIAL_SPLIT_NODE_MARKER);
314
315 splitNode.addNodeState(ns);
316 }
317
318
319
320
321
322 private static boolean isInitialSplitNode(RouteNodeInstance routeNodeInstance) {
323 return routeNodeInstance.getNodeState(INITIAL_SPLIT_NODE_MARKER) != null;
324 }
325
326
327
328
329
330
331 private void addStopToProcessState(HierarchyProvider provider, RouteNodeInstance processInstance, Stop stop) {
332 String stopStateName = provider.getStopIdentifier(stop);
333 NodeState visitedStopsState = processInstance.getNodeState(VISITED_STOPS);
334 if (visitedStopsState == null) {
335 NodeState ns = new NodeState();
336 ns.setKey(VISITED_STOPS);
337 ns.setValue(stopStateName + V_STOPS_DEL);
338
339 processInstance.addNodeState(ns);
340 } else if (! getVisitedStopsList(processInstance).contains(stopStateName)) {
341 visitedStopsState.setValue(visitedStopsState.getValue() + stopStateName + V_STOPS_DEL);
342 }
343 }
344
345
346
347
348
349 private static List<String> getVisitedStopsList(RouteNodeInstance process) {
350 return Arrays.asList(process.getNodeState(VISITED_STOPS).getValue().split(V_STOPS_DEL));
351 }
352
353
354
355
356
357
358
359 private boolean isNewStop(HierarchyProvider provider, Stop stop, RouteNodeInstance process) {
360
361 String orgStateName = provider.getStopIdentifier(stop);
362 List<String> visitedOrgs = getVisitedStopsList(process);
363 boolean isInVisitedList = visitedOrgs.contains(orgStateName);
364 if (isInVisitedList) {
365 return false;
366 }
367 boolean willEventualRouteThere = false;
368
369 for (Iterator<String> iter = visitedOrgs.iterator(); iter.hasNext() && willEventualRouteThere == false; ) {
370 String visitedStopStateName = iter.next();
371 Stop visitedStop = provider.getStopByIdentifier(visitedStopStateName);
372 willEventualRouteThere = hasAsParent(provider, stop, visitedStop) || willEventualRouteThere;
373 }
374 return ! willEventualRouteThere;
375 }
376
377
378
379
380
381
382
383
384
385
386 private RouteNodeInstance createInitialRequestNodeInstance(HierarchyProvider provider, Stop stop, RouteNodeInstance splitNodeInstance, RouteNodeInstance processInstance, RouteNode requestsNode) {
387 String branchName = "Branch " + provider.getStopIdentifier(stop);
388 RouteNodeInstance orgRequestInstance = SplitTransitionEngine.createSplitChild(branchName, requestsNode, splitNodeInstance);
389 splitNodeInstance.addNextNodeInstance(orgRequestInstance);
390 NodeState ns = new NodeState();
391 ns.setKey(STOP_ID);
392 ns.setValue(provider.getStopIdentifier(stop));
393
394 orgRequestInstance.addNodeState(ns);
395 provider.setStop(orgRequestInstance, stop);
396 addStopToProcessState(provider, processInstance, stop);
397 return orgRequestInstance;
398 }
399
400
401
402
403
404
405
406
407 private List<RouteNodeInstance> getNewlyAddedOrgRouteInstances(HierarchyProvider provider, RouteContext context, RouteHelper helper) throws Exception {
408 RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
409 RouteNodeInstance chartOrgNode = context.getNodeInstance();
410
411 List<Stop> stops = provider.getLeafStops(context);
412 List<RouteNodeInstance> newStopsRoutingTo = new ArrayList<RouteNodeInstance>();
413 for (Stop stop: stops) {
414 if (isNewStop(provider, stop, processInstance)) {
415
416 List<RouteNodeInstance> processNodes = chartOrgNode.getPreviousNodeInstances();
417 for (RouteNodeInstance splitNodeInstance: processNodes) {
418 if (isInitialSplitNode(splitNodeInstance)) {
419 RouteNode requestsNode = getStopRequestNode(stop, context.getDocument().getDocumentType());
420 RouteNodeInstance newOrgRequestNode = createInitialRequestNodeInstance(provider, stop, splitNodeInstance, processInstance, requestsNode);
421 newStopsRoutingTo.add(newOrgRequestNode);
422 }
423 }
424 }
425 }
426 return newStopsRoutingTo;
427 }
428
429
430
431
432
433
434 private boolean hasAsParent(HierarchyProvider provider, Stop parent, Stop child) {
435 if (provider.isRoot(child)) {
436 return false;
437 } else if (provider.equals(parent, child)) {
438 return true;
439 } else {
440 child = provider.getParent(child);
441 return hasAsParent(provider, parent, child);
442 }
443 }
444
445
446
447
448
449
450
451
452 private DocumentType setUpDocumentType(HierarchyProvider provider, DocumentType documentType, RouteNodeInstance dynamicNodeInstance) {
453 boolean altered = false;
454 if (documentType.getNamedProcess(SPLIT_PROCESS_NAME) == null) {
455 RouteNode splitNode = getSplitNode(dynamicNodeInstance);
456 documentType.addProcess(getPrototypeProcess(splitNode, documentType));
457 altered = true;
458 }
459 if (documentType.getNamedProcess(JOIN_PROCESS_NAME) == null) {
460 RouteNode joinNode = getJoinNode(dynamicNodeInstance);
461 documentType.addProcess(getPrototypeProcess(joinNode, documentType));
462 altered = true;
463 }
464 if (documentType.getNamedProcess(REQUEST_PROCESS_NAME) == null) {
465 RouteNode requestsNode = getRequestNode(provider, dynamicNodeInstance);
466 documentType.addProcess(getPrototypeProcess(requestsNode, documentType));
467 altered = true;
468 }
469 if (documentType.getNamedProcess(NO_STOP_NAME) == null) {
470 RouteNode noChartOrgNode = getNoChartOrgNode(dynamicNodeInstance);
471 documentType.addProcess(getPrototypeProcess(noChartOrgNode, documentType));
472 altered = true;
473 }
474 if (altered) {
475
476 KEWServiceLocator.getDocumentTypeService().save(documentType);
477 }
478 return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
479 }
480
481
482
483
484
485
486
487
488 protected Process getPrototypeProcess(RouteNode node, DocumentType documentType) {
489 Process process = new Process();
490 process.setDocumentType(documentType);
491 process.setInitial(false);
492 process.setInitialRouteNode(node);
493 process.setName(node.getRouteNodeName());
494 return process;
495 }
496
497
498
499
500
501 private static RouteNode getSplitNode(RouteNodeInstance process) {
502 RouteNode dynamicNode = process.getRouteNode();
503 RouteNode splitNode = new RouteNode();
504 splitNode.setActivationType(dynamicNode.getActivationType());
505 splitNode.setDocumentType(dynamicNode.getDocumentType());
506 splitNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
507 splitNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
508 splitNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
509 splitNode.setNodeType(SimpleSplitNode.class.getName());
510 splitNode.setRouteMethodCode("FR");
511 splitNode.setRouteMethodName(null);
512 splitNode.setRouteNodeName(SPLIT_PROCESS_NAME);
513 return splitNode;
514
515 }
516
517
518
519
520
521 private static RouteNode getJoinNode(RouteNodeInstance process) {
522 RouteNode dynamicNode = process.getRouteNode();
523 RouteNode joinNode = new RouteNode();
524 joinNode.setActivationType(dynamicNode.getActivationType());
525 joinNode.setDocumentType(dynamicNode.getDocumentType());
526 joinNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
527 joinNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
528 joinNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
529 joinNode.setNodeType(SimpleJoinNode.class.getName());
530 joinNode.setRouteMethodCode("FR");
531 joinNode.setRouteMethodName(null);
532 joinNode.setRouteNodeName(JOIN_PROCESS_NAME);
533 return joinNode;
534 }
535
536
537
538
539
540 private RouteNode getRequestNode(HierarchyProvider provider, RouteNodeInstance process) {
541 RouteNode dynamicNode = process.getRouteNode();
542 RouteNode requestsNode = new RouteNode();
543 requestsNode.setActivationType(dynamicNode.getActivationType());
544 requestsNode.setDocumentType(dynamicNode.getDocumentType());
545 requestsNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
546 requestsNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
547 requestsNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
548 requestsNode.setNodeType(RequestsNode.class.getName());
549 requestsNode.setRouteMethodCode("FR");
550 requestsNode.setRouteMethodName(process.getRouteNode().getRouteMethodName());
551 requestsNode.setRouteNodeName(REQUEST_PROCESS_NAME);
552 provider.configureRequestNode(process, requestsNode);
553 return requestsNode;
554 }
555
556
557
558
559
560 private static RouteNode getNoChartOrgNode(RouteNodeInstance process) {
561 RouteNode dynamicNode = process.getRouteNode();
562 RouteNode noChartOrgNOde = new RouteNode();
563 noChartOrgNOde.setActivationType(dynamicNode.getActivationType());
564 noChartOrgNOde.setDocumentType(dynamicNode.getDocumentType());
565 noChartOrgNOde.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
566 noChartOrgNOde.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
567 noChartOrgNOde.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
568 noChartOrgNOde.setNodeType(NoOpNode.class.getName());
569 noChartOrgNOde.setRouteMethodCode("FR");
570 noChartOrgNOde.setRouteMethodName(null);
571 noChartOrgNOde.setRouteNodeName(NO_STOP_NAME);
572 return noChartOrgNOde;
573 }
574
575
576
577
578
579 protected RouteNode getStopRequestNode(Stop stop, DocumentType documentType) {
580 return documentType.getNamedProcess(REQUEST_PROCESS_NAME).getInitialRouteNode();
581 }
582
583 }