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 }