001/** 002 * Copyright 2005-2017 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}