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