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