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