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 exportApplicationStatuses(docTypeElement, documentType); 144 exportPolicies(docTypeElement, documentType.getDocumentTypePolicies()); 145 exportAttributes(docTypeElement, documentType.getDocumentTypeAttributes()); 146 exportSecurity(docTypeElement, documentType.getDocumentTypeSecurityXml()); 147 if (!StringUtils.isBlank(documentType.getRoutingVersion())) { 148 renderer.renderTextElement(docTypeElement, ROUTING_VERSION, documentType.getRoutingVersion()); 149 } 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 exportApplicationStatuses(Element parent, DocumentType documentType) { 162 List<ApplicationDocumentStatusCategory> appDocStatCategories = documentType.getApplicationStatusCategories(); 163 List<ApplicationDocumentStatus> appDocStats = documentType.getValidApplicationStatuses(); 164 165 if (appDocStatCategories != null && !appDocStatCategories.isEmpty()) { 166 Element appDocStatCategoriesElement = renderer.renderElement(parent, APP_DOC_STATUSES); 167 168 for (ApplicationDocumentStatusCategory appDocStatCategory : appDocStatCategories) { 169 Element appStatusCatElement = renderer.renderElement(appDocStatCategoriesElement, CATEGORY); 170 appStatusCatElement.setAttribute(NAME, appDocStatCategory.getCategoryName().trim()); 171 172 if (appDocStats != null) { 173 for (ApplicationDocumentStatus appDocStat : appDocStats) { 174 if (StringUtils.equals(appDocStat.getCategoryName(), appDocStatCategory.getCategoryName())) { 175 renderer.renderTextElement(appStatusCatElement, STATUS, appDocStat.getStatusName()); 176 } 177 } 178 } 179 } 180 181 for (ApplicationDocumentStatus appDocStat : appDocStats) { 182 if (StringUtils.isEmpty(appDocStat.getCategoryName())) { 183 renderer.renderTextElement(appDocStatCategoriesElement, STATUS, appDocStat.getStatusName()); 184 } 185 } 186 } else { 187 if (!appDocStats.isEmpty()) { 188 Element validApplicationStatusesElement = renderer.renderElement(parent, APP_DOC_STATUSES); 189 190 for (ApplicationDocumentStatus appDocStat : appDocStats) { 191 renderer.renderTextElement(validApplicationStatusesElement, STATUS, appDocStat.getStatusName()); 192 } 193 } 194 } 195 } 196 197 private void exportPolicies(Element parent, Collection policies) { 198 if (!policies.isEmpty()) { 199 Element policiesElement = renderer.renderElement(parent, POLICIES); 200 for (Iterator iterator = policies.iterator(); iterator.hasNext();) { 201 DocumentTypePolicy policy = (DocumentTypePolicy) iterator.next(); 202 Element policyElement = renderer.renderElement(policiesElement, POLICY); 203 renderer.renderTextElement(policyElement, NAME, policy.getPolicyName()); 204 if (StringUtils.isNotEmpty(policy.getPolicyStringValue())) { 205 renderer.renderTextElement(policyElement, STRING_VALUE, policy.getPolicyStringValue()); 206 } else { 207 renderer.renderBooleanElement(policyElement, VALUE, policy.getPolicyValue(), false); 208 } 209 } 210 } 211 } 212 213 private void exportAttributes(Element parent, List attributes) { 214 if (!attributes.isEmpty()) { 215 Element attributesElement = renderer.renderElement(parent, ATTRIBUTES); 216 for (Iterator iterator = attributes.iterator(); iterator.hasNext();) { 217 DocumentTypeAttributeBo attribute = (DocumentTypeAttributeBo) iterator.next(); 218 Element attributeElement = renderer.renderElement(attributesElement, ATTRIBUTE); 219 renderer.renderTextElement(attributeElement, NAME, attribute.getRuleAttribute().getName()); 220 } 221 } 222 } 223 224 private void exportSecurity(Element parent, String securityXML) { 225 if (!org.apache.commons.lang.StringUtils.isEmpty(securityXML)) { 226 try { 227 org.jdom.Document securityDoc = new SAXBuilder().build(new StringReader(securityXML)); 228 XmlHelper.propagateNamespace(securityDoc.getRootElement(), DOCUMENT_TYPE_NAMESPACE); 229 parent.addContent(securityDoc.getRootElement().detach()); 230 } catch (IOException e) { 231 throw new WorkflowRuntimeException("Error parsing doctype security XML."); 232 } catch (JDOMException e) { 233 throw new WorkflowRuntimeException("Error parsing doctype security XML."); 234 } 235 } 236 } 237 238 private void exportRouteData(Element parent, DocumentType documentType, List flattenedNodes, boolean hasDefaultExceptionWorkgroup) { 239 if (!flattenedNodes.isEmpty()) { 240 Element routePathsElement = renderer.renderElement(parent, ROUTE_PATHS); 241 for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) { 242 ProcessDefinitionBo process = (ProcessDefinitionBo) iterator.next(); 243 Element routePathElement = renderer.renderElement(routePathsElement, ROUTE_PATH); 244 if (!process.isInitial() && process.getInitialRouteNode() != null) { 245 renderer.renderAttribute(routePathElement, INITIAL_NODE, process.getInitialRouteNode().getRouteNodeName()); 246 renderer.renderAttribute(routePathElement, PROCESS_NAME, process.getName()); 247 } 248 exportProcess(routePathElement, process); 249 } 250 251 Element routeNodesElement = renderer.renderElement(parent, ROUTE_NODES); 252 for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) { 253 RouteNode node = (RouteNode) iterator.next(); 254 exportRouteNode(routeNodesElement, node, hasDefaultExceptionWorkgroup); 255 } 256 } 257 } 258 259 /* default exception workgroup is not stored independently in db, so derive 260 * one from the definition itself: if all nodes have the *same* exception workgroup name 261 * defined, then this is tantamount to a *default* exception workgroup and can be 262 * used as such. 263 */ 264 private boolean hasDefaultExceptionWorkgroup(List flattenedNodes) { 265 boolean hasDefaultExceptionWorkgroup = true; 266 String exceptionWorkgroupName = null; 267 for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) { 268 RouteNode node = (RouteNode) iterator.next(); 269 if (exceptionWorkgroupName == null) { 270 exceptionWorkgroupName = node.getExceptionWorkgroupName(); 271 } 272 if (exceptionWorkgroupName == null || !exceptionWorkgroupName.equals(node.getExceptionWorkgroupName())) { 273 hasDefaultExceptionWorkgroup = false; 274 break; 275 } 276 } 277 return hasDefaultExceptionWorkgroup; 278 } 279 280 private void exportProcess(Element parent, ProcessDefinitionBo process) { 281 exportNodeGraph(parent, process.getInitialRouteNode(), null); 282 } 283 284 private void exportNodeGraph(Element parent, RouteNode node, SplitJoinContext splitJoinContext) { 285 NodeType nodeType = null; 286 287 if (node != null) { 288 String contentFragment = node.getContentFragment(); 289 // some of the older versions of rice do not have content fragments 290 if(contentFragment == null || "".equals(contentFragment)){ 291 nodeType = getNodeTypeForNode(node); 292 }else{ 293 // I'm not sure if this should be the default implementation because 294 // it uses a string comparison instead of a classpath check. 295 nodeType = this.getNodeTypeForNodeFromFragment(node); 296 } 297 298 if (nodeType.isAssignableFrom(NodeType.SPLIT)) { 299 exportSplitNode(parent, node, nodeType, splitJoinContext); 300 } else if (nodeType.isAssignableFrom(NodeType.JOIN)) { 301 exportJoinNode(parent, node, nodeType, splitJoinContext); 302 } else { 303 exportSimpleNode(parent, node, nodeType, splitJoinContext); 304 } 305 } 306 } 307 308 private void exportSimpleNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 309 Element simpleElement = renderNodeElement(parent, node, nodeType); 310 if (node.getNextNodes().size() > 1) { 311 throw new WorkflowRuntimeException("Simple node cannot have more than one next node: " + node.getRouteNodeName()); 312 } 313 if (node.getNextNodes().size() == 1) { 314 RouteNode nextNode = (RouteNode)node.getNextNodes().get(0); 315 renderer.renderAttribute(simpleElement, NEXT_NODE, nextNode.getRouteNodeName()); 316 317 if (node.getNextDocumentStatus() != null) { 318 renderer.renderAttribute(simpleElement, NEXT_APP_DOC_STATUS, node.getNextDocumentStatus()); 319 } 320 321 exportNodeGraph(parent, nextNode, splitJoinContext); 322 } 323 } 324 325 private void exportSplitNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 326 Element splitElement = renderNodeElement(parent, node, nodeType); 327 SplitJoinContext newSplitJoinContext = new SplitJoinContext(node); 328 for (Iterator iterator = node.getNextNodes().iterator(); iterator.hasNext();) { 329 RouteNode nextNode = (RouteNode) iterator.next(); 330 BranchPrototype branch = nextNode.getBranch(); 331 if (branch == null) { 332 throw new WorkflowRuntimeException("Found a split next node with no associated branch prototype: " + nextNode.getRouteNodeName()); 333 } 334 exportBranch(splitElement, nextNode, branch, newSplitJoinContext); 335 } 336 RouteNode joinNode = newSplitJoinContext.joinNode; 337 if (joinNode == null) { 338 if (node.getNextNodes().size() > 0) { 339 throw new WorkflowRuntimeException("Could not locate the join node for the given split node " + node.getRouteNodeName()); 340 } 341 } else { 342 renderNodeElement(splitElement, joinNode, newSplitJoinContext.joinNodeType); 343 if (joinNode.getNextNodes().size() > 1) { 344 throw new WorkflowRuntimeException("Join node cannot have more than one next node: " + joinNode.getRouteNodeName()); 345 } 346 if (joinNode.getNextNodes().size() == 1) { 347 RouteNode nextNode = (RouteNode)joinNode.getNextNodes().get(0); 348 renderer.renderAttribute(splitElement, NEXT_NODE, nextNode.getRouteNodeName()); 349 350 if (node.getNextDocumentStatus() != null) { 351 renderer.renderAttribute(splitElement, NEXT_APP_DOC_STATUS, node.getNextDocumentStatus()); 352 } 353 354 exportNodeGraph(parent, nextNode, splitJoinContext); 355 } 356 } 357 } 358 359 private void exportBranch(Element parent, RouteNode node, BranchPrototype branch, SplitJoinContext splitJoinContext) { 360 Element branchElement = renderer.renderElement(parent, BRANCH); 361 renderer.renderAttribute(branchElement, NAME, branch.getName()); 362 exportNodeGraph(branchElement, node, splitJoinContext); 363 } 364 365 private void exportJoinNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) { 366 if (splitJoinContext == null) { 367 // 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 368 // not associated with a proper split node. 369 if (!node.getNextNodes().isEmpty()) { 370 throw new WorkflowRuntimeException("Could not export join node with next nodes that is not contained within a split."); 371 } 372 renderNodeElement(parent, node, nodeType); 373 } else if (splitJoinContext.joinNode == null) { 374 // this is the case where we are "inside" the split node in the XML, by setting up this context, the calling code knows that 375 // when it renders all of the branches of the split node, it can then use this context info to render the join node before 376 // closing the split 377 splitJoinContext.joinNode = node; 378 splitJoinContext.joinNodeType = nodeType; 379 } 380 } 381 382 private Element renderNodeElement(Element parent, RouteNode node, NodeType nodeType) { 383 String nodeName = nodeType.getName(); 384 // if it's a request activation node, be sure to export it as a simple node 385 if (nodeType.equals(NodeType.REQUEST_ACTIVATION)) { 386 nodeName = NodeType.SIMPLE.getName(); 387 } 388 Element nodeElement = renderer.renderElement(parent, nodeName); 389 renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName()); 390 return nodeElement; 391 } 392 393 /** 394 * Exists for backward compatibility for nodes which don't have a content fragment. 395 */ 396 private void exportRouteNodeOld(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) { 397 NodeType nodeType = getNodeTypeForNode(node); 398 Element nodeElement = renderer.renderElement(parent, nodeType.getName()); 399 renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName()); 400 if (!hasDefaultExceptionWorkgroup) { 401 if (!StringUtils.isBlank(node.getExceptionWorkgroupName())) { 402 Element exceptionGroupElement = renderer.renderTextElement(nodeElement, EXCEPTION_GROUP_NAME, node.getExceptionWorkgroupName()); 403 exceptionGroupElement.setAttribute(NAMESPACE, node.getExceptionWorkgroup().getNamespaceCode()); 404 } 405 } 406 if (supportsActivationType(nodeType) && !StringUtils.isBlank(node.getActivationType())) { 407 renderer.renderTextElement(nodeElement, ACTIVATION_TYPE, node.getActivationType()); 408 } 409 if (supportsRouteMethod(nodeType)) { 410 exportRouteMethod(nodeElement, node); 411 renderer.renderBooleanElement(nodeElement, MANDATORY_ROUTE, node.getMandatoryRouteInd(), false); 412 renderer.renderBooleanElement(nodeElement, FINAL_APPROVAL, node.getFinalApprovalInd(), false); 413 } 414 if (nodeType.isCustomNode(node.getNodeType())) { 415 renderer.renderTextElement(nodeElement, TYPE, node.getNodeType()); 416 } 417 } 418 419 private void exportRouteNode(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) { 420 String contentFragment = node.getContentFragment(); 421 if (StringUtils.isBlank(contentFragment)) { 422 exportRouteNodeOld(parent, node, hasDefaultExceptionWorkgroup); 423 } else { 424 try { 425 Document document = XmlHelper.buildJDocument(new StringReader(contentFragment)); 426 Element rootElement = document.detachRootElement(); 427 XmlHelper.propagateNamespace(rootElement, parent.getNamespace()); 428 parent.addContent(rootElement); 429 } catch (XmlException e) { 430 throw new WorkflowRuntimeException("Failed to load the content fragment.", e); 431 } 432 } 433 } 434 435 436 private NodeType getNodeTypeForNode(RouteNode node) { 437 NodeType nodeType = null; 438 String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType(); 439 440 try { 441 nodeType = NodeType.fromClassName(node.getNodeType()); 442 } catch (ResourceUnavailableException e) { 443 throw new WorkflowRuntimeException(errorMessage, e); 444 } 445 if (nodeType == null) { 446 throw new WorkflowRuntimeException(errorMessage); 447 } 448 return nodeType; 449 } 450 451 /** 452 * 453 * This method will find the base node type via the content fragment of the node. 454 * Basically, it reads the node type, start, split, join, etc and then assigns 455 * the base type to it. This is necessary because there are cases where the 456 * passed in nodeType will no be in the classpath. It should, in theory do 457 * the same thing as getNodeTypeForNode. 458 * 459 * @param node 460 * @return 461 */ 462 private NodeType getNodeTypeForNodeFromFragment(RouteNode node) { 463 NodeType nodeType = null; 464 String contentFragment = node.getContentFragment(); 465 String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType(); 466 467 for (Iterator<NodeType> iterator = NodeType.getTypeList().iterator(); iterator.hasNext();) { 468 NodeType nType = iterator.next(); 469 // checks for something like <start 470 // or <split 471 // we may want to switch this out for something a little more robust. 472 if(contentFragment.startsWith("<" + nType.getName())){ 473 nodeType = nType; 474 } 475 } 476 477 if (nodeType == null) { 478 throw new WorkflowRuntimeException(errorMessage); 479 } 480 return nodeType; 481 } 482 483 /** 484 * Any node can support activation type, this use to not be the case but now it is. 485 */ 486 private boolean supportsActivationType(NodeType nodeType) { 487 return true; 488 } 489 490 /** 491 * Any node can support route methods, this use to not be the case but now it is. 492 */ 493 private boolean supportsRouteMethod(NodeType nodeType) { 494 return true; 495 } 496 497 private void exportRouteMethod(Element parent, RouteNode node) { 498 if (!StringUtils.isBlank(node.getRouteMethodName())) { 499 String routeMethodCode = node.getRouteMethodCode(); 500 String elementName = null; 501 if (KewApiConstants.ROUTE_LEVEL_FLEX_RM.equals(routeMethodCode)) { 502 elementName = RULE_TEMPLATE; 503 } else if (KewApiConstants.ROUTE_LEVEL_ROUTE_MODULE.equals(routeMethodCode)) { 504 elementName = ROUTE_MODULE; 505 } else { 506 throw new WorkflowRuntimeException("Invalid route method code '"+routeMethodCode+"' for node " + node.getRouteNodeName()); 507 } 508 renderer.renderTextElement(parent, elementName, node.getRouteMethodName()); 509 } 510 } 511 512 private class DocumentTypeParentComparator implements Comparator { 513 514 public int compare(Object object1, Object object2) { 515 DocumentType docType1 = (DocumentType)object1; 516 DocumentType docType2 = (DocumentType)object2; 517 Integer depth1 = getDepth(docType1); 518 Integer depth2 = getDepth(docType2); 519 return depth1.compareTo(depth2); 520 } 521 522 private Integer getDepth(DocumentType docType) { 523 int depth = 0; 524 while ((docType = docType.getParentDocType()) != null) { 525 depth++; 526 } 527 return Integer.valueOf(depth); 528 } 529 530 } 531 532 private class SplitJoinContext { 533 public RouteNode splitNode; 534 public RouteNode joinNode; 535 public NodeType joinNodeType; 536 public SplitJoinContext(RouteNode splitNode) { 537 this.splitNode = splitNode; 538 } 539 } 540 541 542}