View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    * 
4    * 
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    * http://www.opensource.org/licenses/ecl2.php
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.api.reflect.ObjectDefinition;
33  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
34  import org.kuali.rice.core.api.reflect.ObjectDefinition;
35  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
36  import org.kuali.rice.kew.api.WorkflowRuntimeException;
37  import org.kuali.rice.kew.doctype.bo.DocumentType;
38  import org.kuali.rice.kew.engine.RouteContext;
39  import org.kuali.rice.kew.engine.RouteHelper;
40  import org.kuali.rice.kew.engine.node.Branch;
41  import org.kuali.rice.kew.engine.node.DynamicNode;
42  import org.kuali.rice.kew.engine.node.DynamicResult;
43  import org.kuali.rice.kew.engine.node.NoOpNode;
44  import org.kuali.rice.kew.engine.node.NodeState;
45  import org.kuali.rice.kew.engine.node.Process;
46  import org.kuali.rice.kew.engine.node.RequestsNode;
47  import org.kuali.rice.kew.engine.node.RouteNode;
48  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
49  import org.kuali.rice.kew.engine.node.SimpleJoinNode;
50  import org.kuali.rice.kew.engine.node.SimpleSplitNode;
51  import org.kuali.rice.kew.engine.node.hierarchyrouting.HierarchyProvider.Stop;
52  import org.kuali.rice.kew.engine.transition.SplitTransitionEngine;
53  import org.kuali.rice.kew.service.KEWServiceLocator;
54  import org.kuali.rice.kew.util.Utilities;
55  
56  
57  /**
58   * Generic hierarchy routing node
59   * @author Kuali Rice Team (rice.collab@kuali.org)
60   */
61  public class HierarchyRoutingNode implements DynamicNode {
62      protected final Logger LOG = Logger.getLogger(getClass());
63  
64      /**
65       * The RouteNode configuration parameter that specifies the hierarchy provider implementation
66       */
67      public static final String HIERARCHY_PROVIDER = "hierarchyProvider";
68      /**
69       * RouteNodeInstance NodeState key for id of stop
70       */
71      public static final String STOP_ID = "stop_id";
72  
73      protected static final String SPLIT_PROCESS_NAME = "Hierarchy Split";
74      protected static final String JOIN_PROCESS_NAME = "Hierarchy Join";
75      protected static final String REQUEST_PROCESS_NAME = "Hierarchy Request";
76      protected static final String NO_STOP_NAME = "No stop";
77  
78      // constants for the process state in tracking stops we've traveled
79      private static final String VISITED_STOPS = "visited_stops";
80      private static final String V_STOPS_DEL = ",";
81      
82      private static final String INITIAL_SPLIT_NODE_MARKER = "InitialSplitNode";
83  
84      /**
85       * Loads hierarchy provider class via the GlobalResourceLoader
86       * @param nodeInstance the current RouteNodeInstance
87       * @param context the current RouteContext
88       * @return the HierarchyProvider implementation, as specified by the HIERARCHY_PROVIDER config parameter
89       */
90      protected HierarchyProvider getHierarchyProvider(RouteNodeInstance nodeInstance, RouteContext context) {
91          Map<String, String> cfgMap = Utilities.getKeyValueCollectionAsMap(nodeInstance.getRouteNode().getConfigParams());
92          String hierarchyProviderClass = cfgMap.get(HIERARCHY_PROVIDER); 
93          if (StringUtils.isEmpty(hierarchyProviderClass)) {
94              throw new WorkflowRuntimeException("hierarchyProvider configuration parameter not set for HierarchyRoutingNode: " + nodeInstance.getName());
95          }
96          QName qn = QName.valueOf(hierarchyProviderClass);
97          ObjectDefinition od;
98          if (XMLConstants.NULL_NS_URI.equals(qn.getNamespaceURI())) {
99              od = new ObjectDefinition(qn.getLocalPart());
100         } else {
101             od = new ObjectDefinition(qn.getLocalPart(), qn.getNamespaceURI());
102         }
103         HierarchyProvider hp = (HierarchyProvider) GlobalResourceLoader.getObject(od);
104         hp.init(nodeInstance, context);
105         return hp;
106     }
107 
108     public DynamicResult transitioningInto(RouteContext context, RouteNodeInstance dynamicNodeInstance, RouteHelper helper) throws Exception {
109 
110         HierarchyProvider provider = getHierarchyProvider(dynamicNodeInstance, context);
111         DocumentType documentType = setUpDocumentType(provider, context.getDocument().getDocumentType(), dynamicNodeInstance);
112         RouteNode splitNode = documentType.getNamedProcess(SPLIT_PROCESS_NAME).getInitialRouteNode();
113 
114         //set up initial SplitNodeInstance
115         RouteNodeInstance splitNodeInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), splitNode);
116         splitNodeInstance.setBranch(dynamicNodeInstance.getBranch());
117         markAsInitialSplitNode(splitNodeInstance);
118         
119         int i = 0;
120         List<Stop> stops = provider.getLeafStops(context);
121         if (stops.isEmpty()) {
122             // if we have no stops, then just return a no-op node with IU-UNIV attached, this will terminate the process
123             RouteNode noStopNode = documentType.getNamedProcess(NO_STOP_NAME).getInitialRouteNode();
124             RouteNodeInstance noChartOrgInstance = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), noStopNode);
125             noChartOrgInstance.setBranch(dynamicNodeInstance.getBranch());
126 
127             provider.setStop(noChartOrgInstance, null);
128 
129             return new DynamicResult(true, noChartOrgInstance);
130         }
131         for (Stop stop: stops) {
132             RouteNode requestNode = getStopRequestNode(stop, documentType);
133             createInitialRequestNodeInstance(provider, stop, splitNodeInstance, dynamicNodeInstance, requestNode);
134         }
135 
136         return new DynamicResult(false, splitNodeInstance);
137     }
138 
139     public DynamicResult transitioningOutOf(RouteContext context, RouteHelper helper) throws Exception {
140         // process initial nodes govern transitioning within the process
141         // the process node will be the hierarchy node, so that's what we need to grab
142         HierarchyProvider provider = getHierarchyProvider(context.getNodeInstance().getProcess(), context);
143         
144         RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
145         RouteNodeInstance curStopNode = context.getNodeInstance();
146         Map<String, RouteNodeInstance> stopRequestNodeMap = new HashMap<String, RouteNodeInstance>();
147         findStopRequestNodes(provider, context, stopRequestNodeMap);//SpringServiceLocator.getRouteNodeService().findProcessNodeInstances(processInstance);
148         
149         Stop stop = provider.getStop(curStopNode);
150 
151         if (provider.isRoot(stop)) {
152             return new DynamicResult(true, null);
153         }        
154         
155         //create a join node for the next node and attach any sibling orgs to the join
156         //if no join node is necessary i.e. no siblings create a requests node
157         InnerTransitionResult transition = canTransitionFrom(provider, stop, stopRequestNodeMap.values(), helper);
158         DynamicResult result = null;
159         if (transition.isCanTransition()) {
160             DocumentType documentType = context.getDocument().getDocumentType();
161             // make a simple requests node
162             RouteNodeInstance requestNode = createNextStopRequestNodeInstance(provider, context, stop, processInstance, helper);
163 
164             if (transition.getSiblings().isEmpty()) {
165                 result = new DynamicResult(false, requestNode);
166             } else {
167                 /* join stuff not working
168 
169                 //create a join to transition us to the next org
170                 RouteNode joinPrototype = documentType.getNamedProcess(JOIN_PROCESS_NAME).getInitialRouteNode();
171                 RouteNodeInstance joinNode = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), joinPrototype);
172                 LOG.debug("Created join node: " + joinNode);
173                 String branchName = "Branch for join " + provider.getStopIdentifier(stop);
174                 Branch joinBranch = helper.getNodeFactory().createBranch(branchName, null, joinNode);
175                 LOG.debug("Created branch for join node: " + joinBranch);
176                 joinNode.setBranch(joinBranch);
177 
178                 for (RouteNodeInstance sibling: transition.getSiblings()) {
179                     LOG.debug("Adding expected joiner: " + sibling.getRouteNodeInstanceId() + " " + provider.getStop(sibling));
180                     helper.getJoinEngine().addExpectedJoiner(joinNode, sibling.getBranch());
181                 }
182 
183                 ///XXX: can't get stop from node that hasn't been saved yet maybe...need to follow up on this...comes back as 'root'
184                 LOG.debug("Adding as stop after join: " + requestNode.getRouteNodeInstanceId() + " " + provider.getStop(requestNode));
185                 //set the next org after the join
186                 joinNode.addNextNodeInstance(requestNode);
187 
188                 result = new DynamicResult(false, joinNode);
189                 
190                 */
191             }
192 
193         } else {
194             result = new DynamicResult(false, null);
195         }
196         result.getNextNodeInstances().addAll(getNewlyAddedOrgRouteInstances(provider, context, helper));
197         return result;
198     }
199     
200     private void findStopRequestNodes(HierarchyProvider provider, RouteContext context, Map<String, RouteNodeInstance> stopRequestNodes) {
201         List<RouteNodeInstance> nodeInstances = KEWServiceLocator.getRouteNodeService().getFlattenedNodeInstances(context.getDocument(), true);
202         for (RouteNodeInstance nodeInstance: nodeInstances) {
203             if (provider.hasStop(nodeInstance)) {
204                 LOG.debug("Stop node instance: " + nodeInstance);
205                 stopRequestNodes.put(nodeInstance.getRouteNodeInstanceId(), nodeInstance);
206             }
207         }
208         
209     }
210 
211     private RouteNodeInstance createNextStopRequestNodeInstance(HierarchyProvider provider, RouteContext context, Stop stop, RouteNodeInstance processInstance, RouteHelper helper) {
212         Stop futureStop = provider.getParent(stop);
213         LOG.debug("Creating next stop request node instance " + provider.getStopIdentifier(futureStop) + " as parent of " + provider.getStopIdentifier(stop));
214         RouteNode requestsPrototype = getStopRequestNode(futureStop, context.getDocument().getDocumentType());
215         RouteNodeInstance requestNode = helper.getNodeFactory().createRouteNodeInstance(context.getDocument().getDocumentId(), requestsPrototype);
216         requestNode.setBranch(processInstance.getBranch());
217         NodeState ns = new NodeState();
218         ns.setKey(STOP_ID);
219         ns.setValue(provider.getStopIdentifier(futureStop));
220         requestNode.addNodeState(ns);
221         provider.setStop(requestNode, futureStop);
222         LOG.debug("Stop set on request node: " + provider.getStop(requestNode));
223         addStopToProcessState(provider, processInstance, futureStop);
224         return requestNode;
225     }
226 
227     /**
228      * i can only transition from this if all the nodes left are completed immediate siblings
229      * 
230      * @param org
231      * @param requestNodes
232      * @return List of Nodes that are siblings to the org passed in
233      */
234     private InnerTransitionResult canTransitionFrom(HierarchyProvider provider, Stop currentStop, Collection<RouteNodeInstance> requestNodes, RouteHelper helper) {
235         LOG.debug("Testing whether we can transition from stop: " + currentStop);
236         Stop parent = provider.getParent(currentStop);
237         InnerTransitionResult result = new InnerTransitionResult();
238         result.setCanTransition(false);
239 
240         for (RouteNodeInstance requestNode: requestNodes) {
241             if (!provider.hasStop(requestNode)) {
242                 LOG.debug("request node " + requestNode.getName() + " does not have a stop associated with it");
243                 continue;
244             }
245 
246             Stop requestNodeStop = provider.getStop(requestNode);
247             LOG.debug("Request node: " + requestNode.getRouteNodeInstanceId() + " has stop " + requestNodeStop.toString());
248             if (requestNodeStop != null && provider.equals(currentStop, requestNodeStop)) {
249                 LOG.debug("Skipping node " + requestNode.getName() + " because it is associated with the current stop");
250                 continue;
251             }
252 
253 
254             Stop stop = provider.getStop(requestNode);
255 
256             LOG.debug("Found an outstanding stop: " + stop);
257 
258             boolean isChildOfMyParent = isDescendent(provider, parent, stop);
259 
260             if (isChildOfMyParent) {
261                 LOG.debug("Found stop node whose parent is my parent:");
262                 LOG.debug("Stop: " + stop);
263                 LOG.debug("Node: " + requestNode);
264 
265                 // if any sibling request node is active, then I can't transition
266                 if (requestNode.isActive()) {
267                     // can't transition
268                     result.getSiblings().clear();
269                     return result;
270                 }
271 
272                 /* join stuff not working
273                 // if it's a direct sibling
274                 if (provider.equals(parent, provider.getParent(stop))) {
275                     LOG.debug("Adding stop " + provider.getStopIdentifier(stop) + " as sibling to " + provider.getStopIdentifier(currentStop));
276                     result.getSiblings().add(requestNode);
277                 }
278                 */
279             }
280         }
281         result.setCanTransition(true);
282         return result;
283     }
284 
285     protected boolean isDescendent(HierarchyProvider provider, Stop parent, Stop otherStop) {
286         return provider.isRoot(parent) || hasAsParent(provider, parent, otherStop);
287     }
288 
289     private static class InnerTransitionResult {
290         private boolean canTransition;
291         private List<RouteNodeInstance> siblings = new ArrayList<RouteNodeInstance>();
292 
293         public boolean isCanTransition() {
294             return canTransition;
295         }
296 
297         public void setCanTransition(boolean canTransition) {
298             this.canTransition = canTransition;
299         }
300 
301         public List<RouteNodeInstance> getSiblings() {
302             return siblings;
303         }
304 
305         public void setSiblings(List<RouteNodeInstance> siblings) {
306             this.siblings = siblings;
307         }
308     }
309 
310     private static void markAsInitialSplitNode(RouteNodeInstance splitNode) {
311         NodeState ns = new NodeState();
312         ns.setKey(INITIAL_SPLIT_NODE_MARKER);
313         ns.setValue(INITIAL_SPLIT_NODE_MARKER);
314     	
315     	splitNode.addNodeState(ns);
316     }
317 
318     /**
319      * @param routeNodeInstance
320      * @return
321      */
322     private static boolean isInitialSplitNode(RouteNodeInstance routeNodeInstance) {
323         return routeNodeInstance.getNodeState(INITIAL_SPLIT_NODE_MARKER) != null;
324     }
325 
326     /**
327      * Adds the org to the process state 
328      * @param processInstance
329      * @param org
330      */
331     private void addStopToProcessState(HierarchyProvider provider, RouteNodeInstance processInstance, Stop stop) {
332         String stopStateName = provider.getStopIdentifier(stop);
333         NodeState visitedStopsState = processInstance.getNodeState(VISITED_STOPS);
334         if (visitedStopsState == null) {
335             NodeState ns = new NodeState();
336             ns.setKey(VISITED_STOPS);
337             ns.setValue(stopStateName + V_STOPS_DEL);
338         	
339         	processInstance.addNodeState(ns);
340         } else if (! getVisitedStopsList(processInstance).contains(stopStateName)) {
341             visitedStopsState.setValue(visitedStopsState.getValue() + stopStateName + V_STOPS_DEL);
342         }
343     }
344     
345     /**
346      * @param process
347      * @return List of stop strings on the process state
348      */
349     private static List<String> getVisitedStopsList(RouteNodeInstance process) {
350         return Arrays.asList(process.getNodeState(VISITED_STOPS).getValue().split(V_STOPS_DEL));
351     }
352     
353     /**
354      * Determines if the org has been routed to or will be.
355      * @param stop
356      * @param process
357      * @return boolean if this is an org we would not hit in routing
358      */
359     private boolean isNewStop(HierarchyProvider provider, Stop stop, RouteNodeInstance process) {
360         
361         String orgStateName = provider.getStopIdentifier(stop);
362         List<String> visitedOrgs = getVisitedStopsList(process);
363         boolean isInVisitedList = visitedOrgs.contains(orgStateName);
364         if (isInVisitedList) {
365             return false;
366         }
367         boolean willEventualRouteThere = false;
368         //determine if we will eventually route to this chart anyway
369         for (Iterator<String> iter = visitedOrgs.iterator(); iter.hasNext() && willEventualRouteThere == false; ) {
370             String visitedStopStateName = iter.next();
371             Stop visitedStop = provider.getStopByIdentifier(visitedStopStateName);
372             willEventualRouteThere = hasAsParent(provider, stop, visitedStop) || willEventualRouteThere;
373         }
374         return ! willEventualRouteThere;
375     }
376 
377     /**
378      * Creates a Org Request RouteNodeInstance that is a child of the passed in split.  This is used to create the initial 
379      * request RouteNodeInstances off the begining split.
380      * @param org
381      * @param splitNodeInstance
382      * @param processInstance
383      * @param requestsNode
384      * @return Request RouteNodeInstance bound to the passed in split as a 'nextNodeInstance'
385      */
386     private RouteNodeInstance createInitialRequestNodeInstance(HierarchyProvider provider, Stop stop, RouteNodeInstance splitNodeInstance, RouteNodeInstance processInstance, RouteNode requestsNode) {
387         String branchName = "Branch " + provider.getStopIdentifier(stop);
388         RouteNodeInstance orgRequestInstance = SplitTransitionEngine.createSplitChild(branchName, requestsNode, splitNodeInstance);
389         splitNodeInstance.addNextNodeInstance(orgRequestInstance);
390         NodeState ns = new NodeState();
391         ns.setKey(STOP_ID);
392         ns.setValue(provider.getStopIdentifier(stop));
393         
394         orgRequestInstance.addNodeState(ns);
395         provider.setStop(orgRequestInstance, stop);
396         addStopToProcessState(provider, processInstance, stop);
397         return orgRequestInstance;
398     }
399     
400     /**
401      * Check the xml and determine there are any orgs declared that we will not travel through on our current trajectory.
402      * @param context
403      * @param helper
404      * @return RouteNodeInstances for any orgs we would not have traveled through that are now in the xml.
405      * @throws Exception
406      */
407     private List<RouteNodeInstance> getNewlyAddedOrgRouteInstances(HierarchyProvider provider, RouteContext context, RouteHelper helper) throws Exception {
408         RouteNodeInstance processInstance = context.getNodeInstance().getProcess();
409         RouteNodeInstance chartOrgNode = context.getNodeInstance();
410         //check for new stops in the xml
411         List<Stop> stops = provider.getLeafStops(context);
412         List<RouteNodeInstance> newStopsRoutingTo = new ArrayList<RouteNodeInstance>();
413         for (Stop stop: stops) {
414             if (isNewStop(provider, stop, processInstance)) {
415                 //the idea here is to always use the object referenced by the engine so simulation can occur
416                 List<RouteNodeInstance> processNodes = chartOrgNode.getPreviousNodeInstances();
417                 for (RouteNodeInstance splitNodeInstance: processNodes) {
418                     if (isInitialSplitNode(splitNodeInstance)) {                        
419                         RouteNode requestsNode = getStopRequestNode(stop, context.getDocument().getDocumentType());
420                         RouteNodeInstance newOrgRequestNode = createInitialRequestNodeInstance(provider, stop, splitNodeInstance, processInstance, requestsNode);
421                         newStopsRoutingTo.add(newOrgRequestNode);
422                     }
423                 }
424             }
425         }
426         return newStopsRoutingTo;
427     }    
428     
429     /**
430      * @param parent
431      * @param child
432      * @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
433      */
434     private boolean hasAsParent(HierarchyProvider provider, Stop parent, Stop child) {
435         if (provider.isRoot(child)) {
436             return false;
437         } else if (provider.equals(parent, child)) {
438             return true;
439         } else {
440             child = provider.getParent(child);
441             return hasAsParent(provider, parent, child);
442         }
443     }
444 
445 
446     /**
447      * Make the 'floating' split, join and request RouteNodes that will be independent processes. These are the prototypes from which our RouteNodeInstance will belong
448      * 
449      * @param documentType
450      * @param dynamicNodeInstance
451      */
452     private DocumentType setUpDocumentType(HierarchyProvider provider, DocumentType documentType, RouteNodeInstance dynamicNodeInstance) {
453         boolean altered = false;
454         if (documentType.getNamedProcess(SPLIT_PROCESS_NAME) == null) {
455             RouteNode splitNode = getSplitNode(dynamicNodeInstance);
456             documentType.addProcess(getPrototypeProcess(splitNode, documentType));
457             altered = true;
458         }
459         if (documentType.getNamedProcess(JOIN_PROCESS_NAME) == null) {
460             RouteNode joinNode = getJoinNode(dynamicNodeInstance);
461             documentType.addProcess(getPrototypeProcess(joinNode, documentType));
462             altered = true;
463         }
464         if (documentType.getNamedProcess(REQUEST_PROCESS_NAME) == null) {
465             RouteNode requestsNode = getRequestNode(provider, dynamicNodeInstance);
466             documentType.addProcess(getPrototypeProcess(requestsNode, documentType));
467             altered = true;
468         }
469         if (documentType.getNamedProcess(NO_STOP_NAME) == null) {
470             RouteNode noChartOrgNode = getNoChartOrgNode(dynamicNodeInstance);
471             documentType.addProcess(getPrototypeProcess(noChartOrgNode, documentType));
472             altered = true;
473         }
474         if (altered) {
475                 //side step normal version etc. because it's a pain.
476             KEWServiceLocator.getDocumentTypeService().save(documentType);
477         }
478         return KEWServiceLocator.getDocumentTypeService().findByName(documentType.getName());
479     }
480 
481     /**
482      * Places a Process on the documentType wrapping the node and setting the node as the process's initalRouteNode
483      * 
484      * @param node
485      * @param documentType
486      * @return Process wrapping the node passed in
487      */
488     protected Process getPrototypeProcess(RouteNode node, DocumentType documentType) {
489         Process process = new Process();
490         process.setDocumentType(documentType);
491         process.setInitial(false);
492         process.setInitialRouteNode(node);
493         process.setName(node.getRouteNodeName());
494         return process;
495     }
496 
497     /**
498      * @param process
499      * @return Route Node of the JoinNode that will be prototype for the split RouteNodeInstances generated by this component
500      */
501     private static RouteNode getSplitNode(RouteNodeInstance process) {
502         RouteNode dynamicNode = process.getRouteNode();
503         RouteNode splitNode = new RouteNode();
504         splitNode.setActivationType(dynamicNode.getActivationType());
505         splitNode.setDocumentType(dynamicNode.getDocumentType());
506         splitNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
507         splitNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
508         splitNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
509         splitNode.setNodeType(SimpleSplitNode.class.getName());
510         splitNode.setRouteMethodCode("FR");
511         splitNode.setRouteMethodName(null);
512         splitNode.setRouteNodeName(SPLIT_PROCESS_NAME);
513         return splitNode;
514         //SubRequests
515     }
516 
517     /**
518      * @param process
519      * @return Route Node of the JoinNode that will be prototype for the join RouteNodeInstances generated by this component
520      */
521     private static RouteNode getJoinNode(RouteNodeInstance process) {
522         RouteNode dynamicNode = process.getRouteNode();
523         RouteNode joinNode = new RouteNode();
524         joinNode.setActivationType(dynamicNode.getActivationType());
525         joinNode.setDocumentType(dynamicNode.getDocumentType());
526         joinNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
527         joinNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
528         joinNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
529         joinNode.setNodeType(SimpleJoinNode.class.getName());
530         joinNode.setRouteMethodCode("FR");
531         joinNode.setRouteMethodName(null);
532         joinNode.setRouteNodeName(JOIN_PROCESS_NAME);
533         return joinNode;
534     }
535 
536     /**
537      * @param process
538      * @return RouteNode of RequestsNode that will be prototype for RouteNodeInstances having requets that are generated by this component
539      */
540     private RouteNode getRequestNode(HierarchyProvider provider, RouteNodeInstance process) {
541         RouteNode dynamicNode = process.getRouteNode();
542         RouteNode requestsNode = new RouteNode();
543         requestsNode.setActivationType(dynamicNode.getActivationType());
544         requestsNode.setDocumentType(dynamicNode.getDocumentType());
545         requestsNode.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
546         requestsNode.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
547         requestsNode.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
548         requestsNode.setNodeType(RequestsNode.class.getName());
549         requestsNode.setRouteMethodCode("FR");
550         requestsNode.setRouteMethodName(process.getRouteNode().getRouteMethodName());
551         requestsNode.setRouteNodeName(REQUEST_PROCESS_NAME);
552         provider.configureRequestNode(process, requestsNode);
553         return requestsNode;
554     }
555 
556     /**
557      * @param process
558      * @return RouteNode of a no-op node which will be used if the user sends no Chart+Org XML to this routing component.
559      */
560     private static RouteNode getNoChartOrgNode(RouteNodeInstance process) {
561         RouteNode dynamicNode = process.getRouteNode();
562         RouteNode noChartOrgNOde = new RouteNode();
563         noChartOrgNOde.setActivationType(dynamicNode.getActivationType());
564         noChartOrgNOde.setDocumentType(dynamicNode.getDocumentType());
565         noChartOrgNOde.setFinalApprovalInd(dynamicNode.getFinalApprovalInd());
566         noChartOrgNOde.setExceptionWorkgroupId(dynamicNode.getExceptionWorkgroupId());
567         noChartOrgNOde.setMandatoryRouteInd(dynamicNode.getMandatoryRouteInd());
568         noChartOrgNOde.setNodeType(NoOpNode.class.getName());
569         noChartOrgNOde.setRouteMethodCode("FR");
570         noChartOrgNOde.setRouteMethodName(null);
571         noChartOrgNOde.setRouteNodeName(NO_STOP_NAME);
572         return noChartOrgNOde;
573     }
574 
575  
576     
577     // methods which can be overridden to change the chart org routing node behavior
578     
579     protected RouteNode getStopRequestNode(Stop stop, DocumentType documentType) {
580         return documentType.getNamedProcess(REQUEST_PROCESS_NAME).getInitialRouteNode();
581     }
582     
583 }