View Javadoc

1   /**
2    * Copyright 2005-2011 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             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                 // if any sibling request node is active, then I can't transition
261                 if (requestNode.isActive()) {
262                     // can't transition
263                     result.getSiblings().clear();
264                     return result;
265                 }
266 
267                 /* join stuff not working
268                 // if it's a direct sibling
269                 if (provider.equals(parent, provider.getParent(stop))) {
270                     LOG.debug("Adding stop " + provider.getStopIdentifier(stop) + " as sibling to " + provider.getStopIdentifier(currentStop));
271                     result.getSiblings().add(requestNode);
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         NodeState ns = new NodeState();
307         ns.setKey(INITIAL_SPLIT_NODE_MARKER);
308         ns.setValue(INITIAL_SPLIT_NODE_MARKER);
309     	
310     	splitNode.addNodeState(ns);
311     }
312 
313     /**
314      * @param routeNodeInstance
315      * @return
316      */
317     private static boolean isInitialSplitNode(RouteNodeInstance routeNodeInstance) {
318         return routeNodeInstance.getNodeState(INITIAL_SPLIT_NODE_MARKER) != null;
319     }
320 
321     /**
322      * Adds the org to the process state 
323      * @param processInstance
324      * @param org
325      */
326     private void addStopToProcessState(HierarchyProvider provider, RouteNodeInstance processInstance, Stop stop) {
327         String stopStateName = provider.getStopIdentifier(stop);
328         NodeState visitedStopsState = processInstance.getNodeState(VISITED_STOPS);
329         if (visitedStopsState == null) {
330             NodeState ns = new NodeState();
331             ns.setKey(VISITED_STOPS);
332             ns.setValue(stopStateName + V_STOPS_DEL);
333         	
334         	processInstance.addNodeState(ns);
335         } else if (! getVisitedStopsList(processInstance).contains(stopStateName)) {
336             visitedStopsState.setValue(visitedStopsState.getValue() + stopStateName + V_STOPS_DEL);
337         }
338     }
339     
340     /**
341      * @param process
342      * @return List of stop strings on the process state
343      */
344     private static List<String> getVisitedStopsList(RouteNodeInstance process) {
345         return Arrays.asList(process.getNodeState(VISITED_STOPS).getValue().split(V_STOPS_DEL));
346     }
347     
348     /**
349      * Determines if the org has been routed to or will be.
350      * @param stop
351      * @param process
352      * @return boolean if this is an org we would not hit in routing
353      */
354     private boolean isNewStop(HierarchyProvider provider, Stop stop, RouteNodeInstance process) {
355         
356         String orgStateName = provider.getStopIdentifier(stop);
357         List<String> visitedOrgs = getVisitedStopsList(process);
358         boolean isInVisitedList = visitedOrgs.contains(orgStateName);
359         if (isInVisitedList) {
360             return false;
361         }
362         boolean willEventualRouteThere = false;
363         //determine if we will eventually route to this chart anyway
364         for (Iterator<String> iter = visitedOrgs.iterator(); iter.hasNext() && willEventualRouteThere == false; ) {
365             String visitedStopStateName = iter.next();
366             Stop visitedStop = provider.getStopByIdentifier(visitedStopStateName);
367             willEventualRouteThere = hasAsParent(provider, stop, visitedStop) || willEventualRouteThere;
368         }
369         return ! willEventualRouteThere;
370     }
371 
372     /**
373      * Creates a Org Request RouteNodeInstance that is a child of the passed in split.  This is used to create the initial 
374      * request RouteNodeInstances off the begining split.
375      * @param org
376      * @param splitNodeInstance
377      * @param processInstance
378      * @param requestsNode
379      * @return Request RouteNodeInstance bound to the passed in split as a 'nextNodeInstance'
380      */
381     private RouteNodeInstance createInitialRequestNodeInstance(HierarchyProvider provider, Stop stop, RouteNodeInstance splitNodeInstance, RouteNodeInstance processInstance, RouteNode requestsNode) {
382         String branchName = "Branch " + provider.getStopIdentifier(stop);
383         RouteNodeInstance orgRequestInstance = SplitTransitionEngine.createSplitChild(branchName, requestsNode, splitNodeInstance);
384         splitNodeInstance.addNextNodeInstance(orgRequestInstance);
385         NodeState ns = new NodeState();
386         ns.setKey(STOP_ID);
387         ns.setValue(provider.getStopIdentifier(stop));
388         
389         orgRequestInstance.addNodeState(ns);
390         provider.setStop(orgRequestInstance, stop);
391         addStopToProcessState(provider, processInstance, stop);
392         return orgRequestInstance;
393     }
394     
395     /**
396      * Check the xml and determine there are any orgs declared that we will not travel through on our current trajectory.
397      * @param context
398      * @param helper
399      * @return RouteNodeInstances for any orgs we would not have traveled through that are now in the xml.
400      * @throws Exception
401      */
402     private List<RouteNodeInstance> getNewlyAddedOrgRouteInstances(HierarchyProvider provider, RouteContext context, RouteHelper helper) throws Exception {
403         RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
404         RouteNodeInstance chartOrgNode = context.getNodeInstance();
405         //check for new stops in the xml
406         List<Stop> stops = provider.getLeafStops(context);
407         List<RouteNodeInstance> newStopsRoutingTo = new ArrayList<RouteNodeInstance>();
408         for (Stop stop: stops) {
409             if (isNewStop(provider, stop, processInstance)) {
410                 //the idea here is to always use the object referenced by the engine so simulation can occur
411                 List<RouteNodeInstance> processNodes = chartOrgNode.getPreviousNodeInstances();
412                 for (RouteNodeInstance splitNodeInstance: processNodes) {
413                     if (isInitialSplitNode(splitNodeInstance)) {                        
414                         RouteNode requestsNode = getStopRequestNode(stop, context.getDocument().getDocumentType());
415                         RouteNodeInstance newOrgRequestNode = createInitialRequestNodeInstance(provider, stop, splitNodeInstance, processInstance, requestsNode);
416                         newStopsRoutingTo.add(newOrgRequestNode);
417                     }
418                 }
419             }
420         }
421         return newStopsRoutingTo;
422     }    
423     
424     /**
425      * @param parent
426      * @param child
427      * @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
428      */
429     private boolean hasAsParent(HierarchyProvider provider, Stop parent, Stop child) {
430         if (provider.isRoot(child)) {
431             return false;
432         } else if (provider.equals(parent, child)) {
433             return true;
434         } else {
435             child = provider.getParent(child);
436             return hasAsParent(provider, parent, child);
437         }
438     }
439 
440 
441     /**
442      * Make the 'floating' split, join and request RouteNodes that will be independent processes. These are the prototypes from which our RouteNodeInstance will belong
443      * 
444      * @param documentType
445      * @param dynamicNodeInstance
446      */
447     private DocumentType setUpDocumentType(HierarchyProvider provider, DocumentType documentType, RouteNodeInstance dynamicNodeInstance) {
448         boolean altered = false;
449         if (documentType.getNamedProcess(SPLIT_PROCESS_NAME) == null) {
450             RouteNode splitNode = getSplitNode(dynamicNodeInstance);
451             documentType.addProcess(getPrototypeProcess(splitNode, documentType));
452             altered = true;
453         }
454         if (documentType.getNamedProcess(JOIN_PROCESS_NAME) == null) {
455             RouteNode joinNode = getJoinNode(dynamicNodeInstance);
456             documentType.addProcess(getPrototypeProcess(joinNode, documentType));
457             altered = true;
458         }
459         if (documentType.getNamedProcess(REQUEST_PROCESS_NAME) == null) {
460             RouteNode requestsNode = getRequestNode(provider, dynamicNodeInstance);
461             documentType.addProcess(getPrototypeProcess(requestsNode, documentType));
462             altered = true;
463         }
464         if (documentType.getNamedProcess(NO_STOP_NAME) == null) {
465             RouteNode noChartOrgNode = getNoChartOrgNode(dynamicNodeInstance);
466             documentType.addProcess(getPrototypeProcess(noChartOrgNode, documentType));
467             altered = true;
468         }
469         if (altered) {
470                 //side step normal version etc. because it's a pain.
471             KEWServiceLocator.getDocumentTypeService().save(documentType);
472         }
473         return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
474     }
475 
476     /**
477      * Places a ProcessDefinition on the documentType wrapping the node and setting the node as the process's initalRouteNode
478      * 
479      * @param node
480      * @param documentType
481      * @return Process wrapping the node passed in
482      */
483     protected ProcessDefinitionBo getPrototypeProcess(RouteNode node, DocumentType documentType) {
484         ProcessDefinitionBo process = new ProcessDefinitionBo();
485         process.setDocumentType(documentType);
486         process.setInitial(false);
487         process.setInitialRouteNode(node);
488         process.setName(node.getRouteNodeName());
489         return process;
490     }
491 
492     /**
493      * @param process
494      * @return Route Node of the JoinNode that will be prototype for the split RouteNodeInstances generated by this component
495      */
496     private static RouteNode getSplitNode(RouteNodeInstance process) {
497         RouteNode dynamicNode = process.getRouteNode();
498         RouteNode splitNode = new RouteNode();
499         splitNode.setActivationType(dynamicNode.getActivationType());
500         splitNode.setDocumentType(dynamicNode.getDocumentType());
501         splitNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
502         splitNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
503         splitNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
504         splitNode.setNodeType(SimpleSplitNode.class.getName());
505         splitNode.setRouteMethodCode("FR");
506         splitNode.setRouteMethodName(null);
507         splitNode.setRouteNodeName(SPLIT_PROCESS_NAME);
508         return splitNode;
509         //SubRequests
510     }
511 
512     /**
513      * @param process
514      * @return Route Node of the JoinNode that will be prototype for the join RouteNodeInstances generated by this component
515      */
516     private static RouteNode getJoinNode(RouteNodeInstance process) {
517         RouteNode dynamicNode = process.getRouteNode();
518         RouteNode joinNode = new RouteNode();
519         joinNode.setActivationType(dynamicNode.getActivationType());
520         joinNode.setDocumentType(dynamicNode.getDocumentType());
521         joinNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
522         joinNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
523         joinNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
524         joinNode.setNodeType(SimpleJoinNode.class.getName());
525         joinNode.setRouteMethodCode("FR");
526         joinNode.setRouteMethodName(null);
527         joinNode.setRouteNodeName(JOIN_PROCESS_NAME);
528         return joinNode;
529     }
530 
531     /**
532      * @param process
533      * @return RouteNode of RequestsNode that will be prototype for RouteNodeInstances having requets that are generated by this component
534      */
535     private RouteNode getRequestNode(HierarchyProvider provider, RouteNodeInstance process) {
536         RouteNode dynamicNode = process.getRouteNode();
537         RouteNode requestsNode = new RouteNode();
538         requestsNode.setActivationType(dynamicNode.getActivationType());
539         requestsNode.setDocumentType(dynamicNode.getDocumentType());
540         requestsNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
541         requestsNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
542         requestsNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
543         requestsNode.setNodeType(RequestsNode.class.getName());
544         requestsNode.setRouteMethodCode("FR");
545         requestsNode.setRouteMethodName(process.getRouteNode().getRouteMethodName());
546         requestsNode.setRouteNodeName(REQUEST_PROCESS_NAME);
547         provider.configureRequestNode(process, requestsNode);
548         return requestsNode;
549     }
550 
551     /**
552      * @param process
553      * @return RouteNode of a no-op node which will be used if the user sends no Chart+Org XML to this routing component.
554      */
555     private static RouteNode getNoChartOrgNode(RouteNodeInstance process) {
556         RouteNode dynamicNode = process.getRouteNode();
557         RouteNode noChartOrgNOde = new RouteNode();
558         noChartOrgNOde.setActivationType(dynamicNode.getActivationType());
559         noChartOrgNOde.setDocumentType(dynamicNode.getDocumentType());
560         noChartOrgNOde.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
561         noChartOrgNOde.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
562         noChartOrgNOde.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
563         noChartOrgNOde.setNodeType(NoOpNode.class.getName());
564         noChartOrgNOde.setRouteMethodCode("FR");
565         noChartOrgNOde.setRouteMethodName(null);
566         noChartOrgNOde.setRouteNodeName(NO_STOP_NAME);
567         return noChartOrgNOde;
568     }
569 
570  
571     
572     // methods which can be overridden to change the chart org routing node behavior
573     
574     protected RouteNode getStopRequestNode(Stop stop, DocumentType documentType) {
575         return documentType.getNamedProcess(REQUEST_PROCESS_NAME).getInitialRouteNode();
576     }
577     
578 }