View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * Generic hierarchy routing node
54   * @author Kuali Rice Team (rice.collab@kuali.org)
55   */
56  public class HierarchyRoutingNode implements DynamicNode {
57      protected final Logger LOG = Logger.getLogger(getClass());
58  
59      /**
60       * The RouteNode configuration parameter that specifies the hierarchy provider implementation
61       */
62      public static final String HIERARCHY_PROVIDER = "hierarchyProvider";
63      /**
64       * RouteNodeInstance NodeState key for id of stop
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      // constants for the process state in tracking stops we've traveled
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       * Loads hierarchy provider class via the GlobalResourceLoader
81       * @param nodeInstance the current RouteNodeInstance
82       * @param context the current RouteContext
83       * @return the HierarchyProvider implementation, as specified by the HIERARCHY_PROVIDER config parameter
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         //set up initial SplitNodeInstance
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             // if we have no stops, then just return a no-op node with IU-UNIV attached, this will terminate the process
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         // process initial nodes govern transitioning within the process
136         // the process node will be the hierarchy node, so that's what we need to grab
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);//SpringServiceLocator.getRouteNodeService().findProcessNodeInstances(processInstance);
143         
144         Stop stop = provider.getStop(curStopNode);
145 
146         if (provider.isRoot(stop)) {
147             return new DynamicResult(true, null);
148         }        
149         
150         //create a join node for the next node and attach any sibling orgs to the join
151         //if no join node is necessary i.e. no siblings create a requests node
152         InnerTransitionResult transition = canTransitionFrom(provider, stop, stopRequestNodeMap.values(), helper);
153         DynamicResult result = null;
154         if (transition.isCanTransition()) {
155             DocumentType documentType = context.getDocument().getDocumentType();
156             // make a simple requests node
157             RouteNodeInstance requestNode = createNextStopRequestNodeInstance(provider, context, stop, processInstance, helper);
158 
159             if (transition.getSiblings().isEmpty()) {
160                 result = new DynamicResult(false, requestNode);
161             } else {
162                 /* join stuff not working
163 
164                 //create a join to transition us to the next org
165                 RouteNode joinPrototype = documentType.getNamedProcess(JOIN_PROCESS_NAME).getInitialRouteNode();
166                 RouteNodeInstance joinNode = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), joinPrototype);
167                 LOG.debug("Created join node: " + joinNode);
168                 String branchName = "Branch for join " + provider.getStopIdentifier(stop);
169                 Branch joinBranch = helper.getNodeFactory().createBranch(branchName, null, joinNode);
170                 LOG.debug("Created branch for join node: " + joinBranch);
171                 joinNode.setBranch(joinBranch);
172 
173                 for (RouteNodeInstance sibling: transition.getSiblings()) {
174                     LOG.debug("Adding expected joiner: " + sibling.getRouteNodeInstanceId() + " " + provider.getStop(sibling));
175                     helper.getJoinEngine().addExpectedJoiner(joinNode, sibling.getBranch());
176                 }
177 
178                 ///XXX: can't get stop from node that hasn't been saved yet maybe...need to follow up on this...comes back as 'root'
179                 LOG.debug("Adding as stop after join: " + requestNode.getRouteNodeInstanceId() + " " + provider.getStop(requestNode));
180                 //set the next org after the join
181                 joinNode.addNextNodeInstance(requestNode);
182 
183                 result = new DynamicResult(false, joinNode);
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      * i can only transition from this if all the nodes left are completed immediate siblings
224      * 
225      * @param org
226      * @param requestNodes
227      * @return List of Nodes that are siblings to the org passed in
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                 // if any sibling request node is active, then I can't transition
263                 if (requestNode.isActive()) {
264                     // can't transition
265                     result.getSiblings().clear();
266                     return result;
267                 }
268 
269                 /* join stuff not working
270                 // if it's a direct sibling
271                 if (provider.equals(parent, provider.getParent(stop))) {
272                     LOG.debug("Adding stop " + provider.getStopIdentifier(stop) + " as sibling to " + provider.getStopIdentifier(currentStop));
273                     result.getSiblings().add(requestNode);
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      * @param routeNodeInstance
317      * @return
318      */
319     private static boolean isInitialSplitNode(RouteNodeInstance routeNodeInstance) {
320         return routeNodeInstance.getNodeState(INITIAL_SPLIT_NODE_MARKER) != null;
321     }
322 
323     /**
324      * Adds the org to the process state 
325      * @param processInstance
326      * @param org
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      * @param process
344      * @return List of stop strings on the process state
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      * Determines if the org has been routed to or will be.
352      * @param stop
353      * @param process
354      * @return boolean if this is an org we would not hit in routing
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         //determine if we will eventually route to this chart anyway
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      * Creates a Org Request RouteNodeInstance that is a child of the passed in split.  This is used to create the initial 
376      * request RouteNodeInstances off the begining split.
377      * @param org
378      * @param splitNodeInstance
379      * @param processInstance
380      * @param requestsNode
381      * @return Request RouteNodeInstance bound to the passed in split as a 'nextNodeInstance'
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      * Check the xml and determine there are any orgs declared that we will not travel through on our current trajectory.
399      * @param context
400      * @param helper
401      * @return RouteNodeInstances for any orgs we would not have traveled through that are now in the xml.
402      * @throws Exception
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         //check for new stops in the xml
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                 //the idea here is to always use the object referenced by the engine so simulation can occur
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      * @param parent
428      * @param child
429      * @return true - if child or one of it's eventual parents reports to parent false - if child or one of it's eventual parents does not report to parent
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      * Make the 'floating' split, join and request RouteNodes that will be independent processes. These are the prototypes from which our RouteNodeInstance will belong
445      * 
446      * @param documentType
447      * @param dynamicNodeInstance
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                 //side step normal version etc. because it's a pain.
473             KEWServiceLocator.getDocumentTypeService().save(documentType);
474         }
475         return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
476     }
477 
478     /**
479      * Places a ProcessDefinition on the documentType wrapping the node and setting the node as the process's initalRouteNode
480      * 
481      * @param node
482      * @param documentType
483      * @return Process wrapping the node passed in
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      * @param process
496      * @return Route Node of the JoinNode that will be prototype for the split RouteNodeInstances generated by this component
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         //SubRequests
512     }
513 
514     /**
515      * @param process
516      * @return Route Node of the JoinNode that will be prototype for the join RouteNodeInstances generated by this component
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      * @param process
535      * @return RouteNode of RequestsNode that will be prototype for RouteNodeInstances having requets that are generated by this component
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      * @param process
555      * @return RouteNode of a no-op node which will be used if the user sends no Chart+Org XML to this routing component.
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     // methods which can be overridden to change the chart org routing node behavior
575     
576     protected RouteNode getStopRequestNode(Stop stop, DocumentType documentType) {
577         return documentType.getNamedProcess(REQUEST_PROCESS_NAME).getInitialRouteNode();
578     }
579     
580 }