001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.engine.node.hierarchyrouting;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.reflect.ObjectDefinition;
021import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
022import org.kuali.rice.kew.api.WorkflowRuntimeException;
023import org.kuali.rice.kew.doctype.bo.DocumentType;
024import org.kuali.rice.kew.engine.RouteContext;
025import org.kuali.rice.kew.engine.RouteHelper;
026import org.kuali.rice.kew.engine.node.DynamicNode;
027import org.kuali.rice.kew.engine.node.DynamicResult;
028import org.kuali.rice.kew.engine.node.NoOpNode;
029import org.kuali.rice.kew.engine.node.NodeState;
030import org.kuali.rice.kew.engine.node.ProcessDefinitionBo;
031import org.kuali.rice.kew.engine.node.RequestsNode;
032import org.kuali.rice.kew.engine.node.RouteNode;
033import org.kuali.rice.kew.engine.node.RouteNodeInstance;
034import org.kuali.rice.kew.engine.node.SimpleJoinNode;
035import org.kuali.rice.kew.engine.node.SimpleSplitNode;
036import org.kuali.rice.kew.engine.node.hierarchyrouting.HierarchyProvider.Stop;
037import org.kuali.rice.kew.engine.transition.SplitTransitionEngine;
038import org.kuali.rice.kew.service.KEWServiceLocator;
039import org.kuali.rice.kew.util.Utilities;
040
041import javax.xml.XMLConstants;
042import javax.xml.namespace.QName;
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.Collection;
046import java.util.HashMap;
047import java.util.Iterator;
048import java.util.List;
049import java.util.Map;
050
051
052/**
053 * Generic hierarchy routing node
054 * @author Kuali Rice Team (rice.collab@kuali.org)
055 */
056public class HierarchyRoutingNode implements DynamicNode {
057    protected final Logger LOG = Logger.getLogger(getClass());
058
059    /**
060     * The RouteNode configuration parameter that specifies the hierarchy provider implementation
061     */
062    public static final String HIERARCHY_PROVIDER = "hierarchyProvider";
063    /**
064     * RouteNodeInstance NodeState key for id of stop
065     */
066    public static final String STOP_ID = "stop_id";
067
068    protected static final String SPLIT_PROCESS_NAME = "Hierarchy Split";
069    protected static final String JOIN_PROCESS_NAME = "Hierarchy Join";
070    protected static final String REQUEST_PROCESS_NAME = "Hierarchy Request";
071    protected static final String NO_STOP_NAME = "No stop";
072
073    // constants for the process state in tracking stops we've traveled
074    private static final String VISITED_STOPS = "visited_stops";
075    private static final String V_STOPS_DEL = ",";
076    
077    private static final String INITIAL_SPLIT_NODE_MARKER = "InitialSplitNode";
078
079    /**
080     * Loads hierarchy provider class via the GlobalResourceLoader
081     * @param nodeInstance the current RouteNodeInstance
082     * @param context the current RouteContext
083     * @return the HierarchyProvider implementation, as specified by the HIERARCHY_PROVIDER config parameter
084     */
085    protected HierarchyProvider getHierarchyProvider(RouteNodeInstance nodeInstance, RouteContext context) {
086        Map<String, String> cfgMap = Utilities.getKeyValueCollectionAsMap(nodeInstance.getRouteNode().getConfigParams());
087        String hierarchyProviderClass = cfgMap.get(HIERARCHY_PROVIDER); 
088        if (StringUtils.isEmpty(hierarchyProviderClass)) {
089            throw new WorkflowRuntimeException("hierarchyProvider configuration parameter not set for HierarchyRoutingNode: " + nodeInstance.getName());
090        }
091        QName qn = QName.valueOf(hierarchyProviderClass);
092        ObjectDefinition od;
093        if (XMLConstants.NULL_NS_URI.equals(qn.getNamespaceURI())) {
094            od = new ObjectDefinition(qn.getLocalPart());
095        } else {
096            od = new ObjectDefinition(qn.getLocalPart(), qn.getNamespaceURI());
097        }
098        HierarchyProvider hp = (HierarchyProvider) GlobalResourceLoader.getObject(od);
099        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}