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.xml.export; 017 018import org.apache.commons.lang.StringUtils; 019import org.jdom.Document; 020import org.jdom.Element; 021import org.jdom.JDOMException; 022import org.jdom.input.SAXBuilder; 023import org.kuali.rice.core.api.impex.ExportDataSet; 024import org.kuali.rice.core.api.util.xml.XmlException; 025import org.kuali.rice.core.api.util.xml.XmlHelper; 026import org.kuali.rice.core.api.util.xml.XmlRenderer; 027import org.kuali.rice.core.framework.impex.xml.XmlExporter; 028import org.kuali.rice.kew.api.WorkflowRuntimeException; 029import org.kuali.rice.kew.doctype.ApplicationDocumentStatus; 030import org.kuali.rice.kew.doctype.ApplicationDocumentStatusCategory; 031import org.kuali.rice.kew.doctype.DocumentTypeAttributeBo; 032import org.kuali.rice.kew.doctype.DocumentTypePolicy; 033import org.kuali.rice.kew.doctype.bo.DocumentType; 034import org.kuali.rice.kew.engine.node.BranchPrototype; 035import org.kuali.rice.kew.engine.node.NodeType; 036import org.kuali.rice.kew.engine.node.ProcessDefinitionBo; 037import org.kuali.rice.kew.engine.node.RouteNode; 038import org.kuali.rice.kew.api.exception.ResourceUnavailableException; 039import org.kuali.rice.kew.export.KewExportDataSet; 040import org.kuali.rice.kew.service.KEWServiceLocator; 041import org.kuali.rice.kew.api.KewApiConstants; 042import org.kuali.rice.kim.api.group.Group; 043 044import java.io.IOException; 045import java.io.StringReader; 046import java.util.Collection; 047import java.util.Collections; 048import java.util.Comparator; 049import java.util.Iterator; 050import java.util.List; 051 052import static org.kuali.rice.core.api.impex.xml.XmlConstants.*; 053 054/** 055 * Exports {@link DocumentType}s to XML. 056 * 057 * @see DocumentType 058 * 059 * @author Kuali Rice Team (rice.collab@kuali.org) 060 */ 061public class DocumentTypeXmlExporter implements XmlExporter { 062 063 protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass()); 064 065 private XmlRenderer renderer = new XmlRenderer(DOCUMENT_TYPE_NAMESPACE); 066 067 @Override 068 public boolean supportPrettyPrint() { 069 return true; 070 } 071 072 public Element export(ExportDataSet exportDataSet) { 073 KewExportDataSet dataSet = KewExportDataSet.fromExportDataSet(exportDataSet); 074 if (!dataSet.getDocumentTypes().isEmpty()) { 075 Collections.sort(dataSet.getDocumentTypes(), new DocumentTypeParentComparator()); 076 Element rootElement = renderer.renderElement(null, DOCUMENT_TYPES); 077 rootElement.setAttribute(SCHEMA_LOCATION_ATTR, DOCUMENT_TYPE_SCHEMA_LOCATION, SCHEMA_NAMESPACE); 078 for (Iterator iterator = dataSet.getDocumentTypes().iterator(); iterator.hasNext();) { 079 DocumentType documentType = (DocumentType) iterator.next(); 080 exportDocumentType(rootElement, documentType); 081 } 082 return rootElement; 083 } 084 return null; 085 } 086 087 private void exportDocumentType(Element parent, DocumentType documentType) { 088 Element docTypeElement = renderer.renderElement(parent, DOCUMENT_TYPE); 089 List flattenedNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(documentType, false); 090 // derive a default exception workgroup by looking at how the nodes are configured 091 boolean hasDefaultExceptionWorkgroup = hasDefaultExceptionWorkgroup(flattenedNodes); 092 renderer.renderTextElement(docTypeElement, NAME, documentType.getName()); 093 if (documentType.getParentDocType() != null) { 094 renderer.renderTextElement(docTypeElement, PARENT, documentType.getParentDocType().getName()); 095 } 096 renderer.renderTextElement(docTypeElement, DESCRIPTION, documentType.getDescription()); 097 renderer.renderTextElement(docTypeElement, LABEL, documentType.getLabel()); 098 if (!StringUtils.isBlank(documentType.getActualApplicationId())) { 099 renderer.renderTextElement(docTypeElement, APPLICATION_ID, documentType.getActualApplicationId()); 100 } 101 renderer.renderTextElement(docTypeElement, POST_PROCESSOR_NAME, documentType.getPostProcessorName()); 102 103 renderer.renderTextElement(docTypeElement, AUTHORIZER, documentType.getAuthorizer()); 104 105 Group superUserWorkgroup = documentType.getSuperUserWorkgroupNoInheritence(); 106 if (superUserWorkgroup != null) { 107 Element superUserGroupElement = renderer.renderTextElement(docTypeElement, SUPER_USER_GROUP_NAME, superUserWorkgroup.getName().trim()); 108 superUserGroupElement.setAttribute(NAMESPACE, superUserWorkgroup.getNamespaceCode().trim()); 109 } 110 Group blanketWorkgroup = documentType.getBlanketApproveWorkgroup(); 111 if (blanketWorkgroup != null){ 112 Element blanketGroupElement = renderer.renderTextElement(docTypeElement, BLANKET_APPROVE_GROUP_NAME, blanketWorkgroup.getName().trim()); 113 blanketGroupElement.setAttribute(NAMESPACE, blanketWorkgroup.getNamespaceCode().trim()); 114 } 115 if (documentType.getBlanketApprovePolicy() != null){ 116 renderer.renderTextElement(docTypeElement, BLANKET_APPROVE_POLICY, documentType.getBlanketApprovePolicy()); 117 } 118 Group reportingWorkgroup = documentType.getReportingWorkgroup(); 119 if (reportingWorkgroup != null) { 120 Element reportingGroupElement = renderer.renderTextElement(docTypeElement, REPORTING_GROUP_NAME, reportingWorkgroup.getName().trim()); 121 reportingGroupElement.setAttribute(NAMESPACE, reportingWorkgroup.getNamespaceCode().trim()); 122 } 123 if (!flattenedNodes.isEmpty() && hasDefaultExceptionWorkgroup) { 124 Group exceptionWorkgroup = ((RouteNode)flattenedNodes.get(0)).getExceptionWorkgroup(); 125 if (exceptionWorkgroup != null) { 126 Element exceptionGroupElement = renderer.renderTextElement(docTypeElement, DEFAULT_EXCEPTION_GROUP_NAME, exceptionWorkgroup.getName().trim()); 127 exceptionGroupElement.setAttribute(NAMESPACE, exceptionWorkgroup.getNamespaceCode().trim()); 128 } 129 } 130 if (StringUtils.isNotBlank(documentType.getUnresolvedDocHandlerUrl())) { 131 renderer.renderTextElement(docTypeElement, DOC_HANDLER, documentType.getUnresolvedDocHandlerUrl()); 132 } 133 if (!StringUtils.isBlank(documentType.getUnresolvedHelpDefinitionUrl())) { 134 renderer.renderTextElement(docTypeElement, HELP_DEFINITION_URL, documentType.getUnresolvedHelpDefinitionUrl()); 135 } 136 if (!StringUtils.isBlank(documentType.getUnresolvedDocSearchHelpUrl())) { 137 renderer.renderTextElement(docTypeElement, DOC_SEARCH_HELP_URL, documentType.getUnresolvedDocSearchHelpUrl()); 138 } 139 if (!StringUtils.isBlank(documentType.getActualNotificationFromAddress())) { 140 renderer.renderTextElement(docTypeElement, NOTIFICATION_FROM_ADDRESS, documentType.getActualNotificationFromAddress()); 141 } 142 renderer.renderBooleanElement(docTypeElement, ACTIVE, documentType.getActive(), true); 143 exportPolicies(docTypeElement, documentType.getDocumentTypePolicies()); 144 exportAttributes(docTypeElement, documentType.getDocumentTypeAttributes()); 145 exportSecurity(docTypeElement, documentType.getDocumentTypeSecurityXml()); 146 if (!StringUtils.isBlank(documentType.getRoutingVersion())) { 147 renderer.renderTextElement(docTypeElement, ROUTING_VERSION, documentType.getRoutingVersion()); 148 } 149 exportApplicationStatusCategories(docTypeElement, documentType); 150 ProcessDefinitionBo process = null; 151 if (documentType.getProcesses().size() > 0) { 152 process = (ProcessDefinitionBo)documentType.getProcesses().get(0); 153 } 154 if (process != null && process.getInitialRouteNode() != null) { 155 exportRouteData(docTypeElement, documentType, flattenedNodes, hasDefaultExceptionWorkgroup); 156 } else { 157 renderer.renderElement(docTypeElement, ROUTE_PATHS); 158 } 159 } 160 161 private void exportPolicies(Element parent, Collection policies) { 162 if (!policies.isEmpty()) { 163 Element policiesElement = renderer.renderElement(parent, POLICIES); 164 for (Iterator iterator = policies.iterator(); iterator.hasNext();) { 165 DocumentTypePolicy policy = (DocumentTypePolicy) iterator.next(); 166 Element policyElement = renderer.renderElement(policiesElement, POLICY); 167 renderer.renderTextElement(policyElement, NAME, policy.getPolicyName()); 168 if (StringUtils.isNotEmpty(policy.getPolicyStringValue())) { 169 renderer.renderTextElement(policyElement, STRING_VALUE, policy.getPolicyStringValue()); 170 } else { 171 renderer.renderBooleanElement(policyElement, VALUE, policy.getPolicyValue(), false); 172 } 173 } 174 } 175 } 176 177 private void exportAttributes(Element parent, List attributes) { 178 if (!attributes.isEmpty()) { 179 Element attributesElement = renderer.renderElement(parent, ATTRIBUTES); 180 for (Iterator iterator = attributes.iterator(); iterator.hasNext();) { 181 DocumentTypeAttributeBo attribute = (DocumentTypeAttributeBo) iterator.next(); 182 Element attributeElement = renderer.renderElement(attributesElement, ATTRIBUTE); 183 renderer.renderTextElement(attributeElement, NAME, attribute.getRuleAttribute().getName()); 184 } 185 } 186 } 187 188 private void exportSecurity(Element parent, String securityXML) { 189 if (!org.apache.commons.lang.StringUtils.isEmpty(securityXML)) { 190 try { 191 org.jdom.Document securityDoc = new SAXBuilder().build(new StringReader(securityXML)); 192 XmlHelper.propagateNamespace(securityDoc.getRootElement(), DOCUMENT_TYPE_NAMESPACE); 193 parent.addContent(securityDoc.getRootElement().detach()); 194 } catch (IOException e) { 195 throw new WorkflowRuntimeException("Error parsing doctype security XML."); 196 } catch (JDOMException e) { 197 throw new WorkflowRuntimeException("Error parsing doctype security XML."); 198 } 199 } 200 } 201 202 private void exportApplicationStatusCategories(Element parent, DocumentType documentType) { 203 List<ApplicationDocumentStatusCategory> appDocStatCategories = documentType.getApplicationStatusCategories(); 204 List<ApplicationDocumentStatus> appDocStats = documentType.getValidApplicationStatuses(); 205 206 if (appDocStatCategories != null && !appDocStatCategories.isEmpty()) { 207 Element appDocStatCategoriesElement = renderer.renderElement(parent, APP_DOC_STATUSES); 208 for (Iterator iterator = appDocStatCategories.iterator(); iterator.hasNext();) { 209 ApplicationDocumentStatusCategory appDocStatCategory = (ApplicationDocumentStatusCategory) iterator.next(); 210 Element appStatusCatElement = renderer.renderElement(appDocStatCategoriesElement, CATEGORY); 211 appStatusCatElement.setAttribute(NAME, appDocStatCategory.getCategoryName().trim()); 212 if(appDocStats != null) { 213 for (Iterator iterator2 = appDocStats.iterator(); iterator2.hasNext();) { 214 ApplicationDocumentStatus appDocStat = (ApplicationDocumentStatus) iterator2.next(); 215 if (StringUtils.equals(appDocStat.getCategoryName(), appDocStatCategory.getCategoryName())) { 216 renderer.renderTextElement(appStatusCatElement, STATUS, appDocStat.getStatusName()); 217 } 218 } 219 } 220 } 221 222 for (Iterator iterator = appDocStats.iterator(); iterator.hasNext();) { 223 ApplicationDocumentStatus appDocStat = (ApplicationDocumentStatus) iterator.next(); 224 if (StringUtils.isEmpty(appDocStat.getCategoryName())) { 225 renderer.renderTextElement(appDocStatCategoriesElement, STATUS, appDocStat.getStatusName()); 226 } 227 } 228 } 229 } 230 231 private void exportRouteData(Element parent, DocumentType documentType, List flattenedNodes, boolean hasDefaultExceptionWorkgroup) { 232 if (!flattenedNodes.isEmpty()) { 233 Element routePathsElement = renderer.renderElement(parent, ROUTE_PATHS); 234 for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) { 235 ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next(); 236 Element routePathElement = renderer.renderElement(routePathsElement, ROUTE_PATH); 237 if (!process.isInitial() && process.getInitialRouteNode() != null) { 238 renderer.renderAttribute(routePathElement, INITIAL_NODE, process.getInitialRouteNode().getRouteNodeName()); 239 renderer.renderAttribute(routePathElement, PROCESS_NAME, process.getName()); 240 } 241 exportProcess(routePathElement, process); 242 } 243 244 Element routeNodesElement = renderer.renderElement(parent, ROUTE_NODES); 245 for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) { 246 RouteNode node = (RouteNode) iterator.next(); 247 exportRouteNode(routeNodesElement, node, hasDefaultExceptionWorkgroup); 248 } 249 } 250 } 251 252 /* default exception workgroup is not stored independently in db, so derive 253 * one from the definition itself: if all nodes have the *same* exception workgroup name 254 * defined, then this is tantamount to a *default* exception workgroup and can be 255 * used as such. 256 */ 257 private boolean hasDefaultExceptionWorkgroup(List flattenedNodes) { 258 boolean hasDefaultExceptionWorkgroup = true; 259 String exceptionWorkgroupName = null; 260 for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) { 261 RouteNode node = (RouteNode) iterator.next(); 262 if (exceptionWorkgroupName == null) { 263 exceptionWorkgroupName = node.getExceptionWorkgroupName(); 264 } 265 if (exceptionWorkgroupName == null || !exceptionWorkgroupName.equals(node.getExceptionWorkgroupName())) { 266 hasDefaultExceptionWorkgroup = false; 267 break; 268 } 269 } 270 return hasDefaultExceptionWorkgroup; 271 } 272 273 private void exportProcess(Element parent, ProcessDefinitionBo process) { 274 exportNodeGraph(parent, process.getInitialRouteNode(), null); 275 } 276 277 private void exportNodeGraph(Element parent, RouteNode node, SplitJoinContext splitJoinContext) { 278 NodeType nodeType = null; 279 280 if (node != null) { 281 String contentFragment = node.getContentFragment(); 282 // some of the older versions of rice do not have content fragments 283 if(contentFragment == null || "".equals(contentFragment)){ 284 nodeType = getNodeTypeForNode(node); 285 }else{ 286 // I'm not sure if this should be the default implementation because 287 // it uses a string comparison instead of a classpath check. 288 nodeType = this.getNodeTypeForNodeFromFragment(node); 289 } 290 291 if (nodeType.isAssignableFrom(NodeType.SPLIT)) { 292 exportSplitNode(parent, node, nodeType, splitJoinContext); 293 } else if (nodeType.isAssignableFrom(NodeType.JOIN)) { 294 exportJoinNode(parent, node, nodeType, splitJoinContext); 295 } else { 296 exportSimpleNode(parent, node, nodeType, splitJoinContext); 297 } 298 } 299 } 300 301 private void exportSimpleNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 302 Element simpleElement = renderNodeElement(parent, node, nodeType); 303 if (node.getNextNodes().size() > 1) { 304 throw new WorkflowRuntimeException("Simple node cannot have more than one next node: " + node.getRouteNodeName()); 305 } 306 if (node.getNextNodes().size() == 1) { 307 RouteNode nextNode = (RouteNode)node.getNextNodes().get(0); 308 renderer.renderAttribute(simpleElement, NEXT_NODE, nextNode.getRouteNodeName()); 309 exportNodeGraph(parent, nextNode, splitJoinContext); 310 } 311 } 312 313 private void exportSplitNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 314 Element splitElement = renderNodeElement(parent, node, nodeType); 315 SplitJoinContext newSplitJoinContext = new SplitJoinContext(node); 316 for (Iterator iterator = node.getNextNodes().iterator(); iterator.hasNext();) { 317 RouteNode nextNode = (RouteNode) iterator.next(); 318 BranchPrototype branch = nextNode.getBranch(); 319 if (branch == null) { 320 throw new WorkflowRuntimeException("Found a split next node with no associated branch prototype: " + nextNode.getRouteNodeName()); 321 } 322 exportBranch(splitElement, nextNode, branch, newSplitJoinContext); 323 } 324 RouteNode joinNode = newSplitJoinContext.joinNode; 325 if (joinNode == null) { 326 if (node.getNextNodes().size() > 0) { 327 throw new WorkflowRuntimeException("Could not locate the join node for the given split node " + node.getRouteNodeName()); 328 } 329 } else { 330 renderNodeElement(splitElement, joinNode, newSplitJoinContext.joinNodeType); 331 if (joinNode.getNextNodes().size() > 1) { 332 throw new WorkflowRuntimeException("Join node cannot have more than one next node: " + joinNode.getRouteNodeName()); 333 } 334 if (joinNode.getNextNodes().size() == 1) { 335 RouteNode nextNode = (RouteNode)joinNode.getNextNodes().get(0); 336 renderer.renderAttribute(splitElement, NEXT_NODE, nextNode.getRouteNodeName()); 337 exportNodeGraph(parent, nextNode, splitJoinContext); 338 } 339 } 340 } 341 342 private void exportBranch(Element parent, RouteNode node, BranchPrototype branch, SplitJoinContext splitJoinContext) { 343 Element branchElement = renderer.renderElement(parent, BRANCH); 344 renderer.renderAttribute(branchElement, NAME, branch.getName()); 345 exportNodeGraph(branchElement, node, splitJoinContext); 346 } 347 348 private void exportJoinNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 349 if (splitJoinContext == null) { 350 // this is the case where a join node is defined as part of a sub process to be used by a dynamic node, in this case it is 351 // not associated with a proper split node. 352 if (!node.getNextNodes().isEmpty()) { 353 throw new WorkflowRuntimeException("Could not export join node with next nodes that is not contained within a split."); 354 } 355 renderNodeElement(parent, node, nodeType); 356 } else if (splitJoinContext.joinNode == null) { 357 // this is the case where we are "inside" the split node in the XML, by setting up this context, the calling code knows that 358 // when it renders all of the branches of the split node, it can then use this context info to render the join node before 359 // closing the split 360 splitJoinContext.joinNode = node; 361 splitJoinContext.joinNodeType = nodeType; 362 } 363 } 364 365 private Element renderNodeElement(Element parent, RouteNode node, NodeType nodeType) { 366 String nodeName = nodeType.getName(); 367 // if it's a request activation node, be sure to export it as a simple node 368 if (nodeType.equals(NodeType.REQUEST_ACTIVATION)) { 369 nodeName = NodeType.SIMPLE.getName(); 370 } 371 Element nodeElement = renderer.renderElement(parent, nodeName); 372 renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName()); 373 return nodeElement; 374 } 375 376 /** 377 * Exists for backward compatibility for nodes which don't have a content fragment. 378 */ 379 private void exportRouteNodeOld(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) { 380 NodeType nodeType = getNodeTypeForNode(node); 381 Element nodeElement = renderer.renderElement(parent, nodeType.getName()); 382 renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName()); 383 if (!hasDefaultExceptionWorkgroup) { 384 if (!StringUtils.isBlank(node.getExceptionWorkgroupName())) { 385 Element exceptionGroupElement = renderer.renderTextElement(nodeElement, EXCEPTION_GROUP_NAME, node.getExceptionWorkgroupName()); 386 exceptionGroupElement.setAttribute(NAMESPACE, node.getExceptionWorkgroup().getNamespaceCode()); 387 } 388 } 389 if (supportsActivationType(nodeType) && !StringUtils.isBlank(node.getActivationType())) { 390 renderer.renderTextElement(nodeElement, ACTIVATION_TYPE, node.getActivationType()); 391 } 392 if (supportsRouteMethod(nodeType)) { 393 exportRouteMethod(nodeElement, node); 394 renderer.renderBooleanElement(nodeElement, MANDATORY_ROUTE, node.getMandatoryRouteInd(), false); 395 renderer.renderBooleanElement(nodeElement, FINAL_APPROVAL, node.getFinalApprovalInd(), false); 396 } 397 if (nodeType.isCustomNode(node.getNodeType())) { 398 renderer.renderTextElement(nodeElement, TYPE, node.getNodeType()); 399 } 400 } 401 402 private void exportRouteNode(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) { 403 String contentFragment = node.getContentFragment(); 404 if (StringUtils.isBlank(contentFragment)) { 405 exportRouteNodeOld(parent, node, hasDefaultExceptionWorkgroup); 406 } else { 407 try { 408 Document document = XmlHelper.buildJDocument(new StringReader(contentFragment)); 409 Element rootElement = document.detachRootElement(); 410 XmlHelper.propagateNamespace(rootElement, parent.getNamespace()); 411 parent.addContent(rootElement); 412 } catch (XmlException e) { 413 throw new WorkflowRuntimeException("Failed to load the content fragment.", e); 414 } 415 } 416 } 417 418 419 private NodeType getNodeTypeForNode(RouteNode node) { 420 NodeType nodeType = null; 421 String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType(); 422 423 try { 424 nodeType = NodeType.fromClassName(node.getNodeType()); 425 } catch (ResourceUnavailableException e) { 426 throw new WorkflowRuntimeException(errorMessage, e); 427 } 428 if (nodeType == null) { 429 throw new WorkflowRuntimeException(errorMessage); 430 } 431 return nodeType; 432 } 433 434 /** 435 * 436 * This method will find the base node type via the content fragment of the node. 437 * Basically, it reads the node type, start, split, join, etc and then assigns 438 * the base type to it. This is necessary because there are cases where the 439 * passed in nodeType will no be in the classpath. It should, in theory do 440 * the same thing as getNodeTypeForNode. 441 * 442 * @param node 443 * @return 444 */ 445 private NodeType getNodeTypeForNodeFromFragment(RouteNode node) { 446 NodeType nodeType = null; 447 String contentFragment = node.getContentFragment(); 448 String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType(); 449 450 for (Iterator<NodeType> iterator = NodeType.getTypeList().iterator(); iterator.hasNext();) { 451 NodeType nType = iterator.next(); 452 // checks for something like <start 453 // or <split 454 // we may want to switch this out for something a little more robust. 455 if(contentFragment.startsWith("<" + nType.getName())){ 456 nodeType = nType; 457 } 458 } 459 460 if (nodeType == null) { 461 throw new WorkflowRuntimeException(errorMessage); 462 } 463 return nodeType; 464 } 465 466 /** 467 * Any node can support activation type, this use to not be the case but now it is. 468 */ 469 private boolean supportsActivationType(NodeType nodeType) { 470 return true; 471 } 472 473 /** 474 * Any node can support route methods, this use to not be the case but now it is. 475 */ 476 private boolean supportsRouteMethod(NodeType nodeType) { 477 return true; 478 } 479 480 private void exportRouteMethod(Element parent, RouteNode node) { 481 if (!StringUtils.isBlank(node.getRouteMethodName())) { 482 String routeMethodCode = node.getRouteMethodCode(); 483 String elementName = null; 484 if (KewApiConstants.ROUTE_LEVEL_FLEX_RM.equals(routeMethodCode)) { 485 elementName = RULE_TEMPLATE; 486 } else if (KewApiConstants.ROUTE_LEVEL_ROUTE_MODULE.equals(routeMethodCode)) { 487 elementName = ROUTE_MODULE; 488 } else { 489 throw new WorkflowRuntimeException("Invalid route method code '"+routeMethodCode+"' for node " + node.getRouteNodeName()); 490 } 491 renderer.renderTextElement(parent, elementName, node.getRouteMethodName()); 492 } 493 } 494 495 private class DocumentTypeParentComparator implements Comparator { 496 497 public int compare(Object object1, Object object2) { 498 DocumentType docType1 = (DocumentType)object1; 499 DocumentType docType2 = (DocumentType)object2; 500 Integer depth1 = getDepth(docType1); 501 Integer depth2 = getDepth(docType2); 502 return depth1.compareTo(depth2); 503 } 504 505 private Integer getDepth(DocumentType docType) { 506 int depth = 0; 507 while ((docType = docType.getParentDocType()) != null) { 508 depth++; 509 } 510 return Integer.valueOf(depth); 511 } 512 513 } 514 515 private class SplitJoinContext { 516 public RouteNode splitNode; 517 public RouteNode joinNode; 518 public NodeType joinNodeType; 519 public SplitJoinContext(RouteNode splitNode) { 520 this.splitNode = splitNode; 521 } 522 } 523 524 525}