Coverage Report - org.kuali.rice.kew.xml.export.DocumentTypeXmlExporter
 
Classes in this File Line Coverage Branch Coverage Complexity
DocumentTypeXmlExporter
0%
0/240
0%
0/124
4.2
DocumentTypeXmlExporter$1
N/A
N/A
4.2
DocumentTypeXmlExporter$DocumentTypeParentComparator
0%
0/10
0%
0/2
4.2
DocumentTypeXmlExporter$SplitJoinContext
0%
0/3
N/A
4.2
 
 1  
 /*
 2  
  * Copyright 2005-2007 The Kuali Foundation
 3  
  *
 4  
  *
 5  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  * http://www.opensource.org/licenses/ecl2.php
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.kuali.rice.kew.xml.export;
 18  
 
 19  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ACTIVATION_TYPE;
 20  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ACTIVE;
 21  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ATTRIBUTE;
 22  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ATTRIBUTES;
 23  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.BLANKET_APPROVE_GROUP_NAME;
 24  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.BLANKET_APPROVE_POLICY;
 25  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.BRANCH;
 26  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DEFAULT_EXCEPTION_GROUP_NAME;
 27  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DESCRIPTION;
 28  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOCUMENT_TYPE;
 29  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOCUMENT_TYPES;
 30  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOCUMENT_TYPE_NAMESPACE;
 31  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOCUMENT_TYPE_SCHEMA_LOCATION;
 32  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOC_HANDLER;
 33  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.DOC_SEARCH_HELP_URL;
 34  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.EXCEPTION_GROUP_NAME;
 35  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.FINAL_APPROVAL;
 36  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.HELP_DEFINITION_URL;
 37  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.INITIAL_NODE;
 38  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.LABEL;
 39  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.MANDATORY_ROUTE;
 40  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.NAME;
 41  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.NAMESPACE;
 42  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.NEXT_NODE;
 43  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.NOTIFICATION_FROM_ADDRESS;
 44  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.PARENT;
 45  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.POLICIES;
 46  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.POLICY;
 47  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.POST_PROCESSOR_NAME;
 48  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.PROCESS_NAME;
 49  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.REPORTING_GROUP_NAME;
 50  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ROUTE_MODULE;
 51  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ROUTE_NODES;
 52  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ROUTE_PATH;
 53  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ROUTE_PATHS;
 54  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.ROUTING_VERSION;
 55  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.RULE_TEMPLATE;
 56  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.SCHEMA_LOCATION_ATTR;
 57  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.SCHEMA_NAMESPACE;
 58  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.APPLICATION_ID;
 59  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.SUPER_USER_GROUP_NAME;
 60  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.TYPE;
 61  
 import static org.kuali.rice.core.api.impex.xml.XmlConstants.VALUE;
 62  
 
 63  
 import java.io.IOException;
 64  
 import java.io.StringReader;
 65  
 import java.util.Collection;
 66  
 import java.util.Collections;
 67  
 import java.util.Comparator;
 68  
 import java.util.Iterator;
 69  
 import java.util.List;
 70  
 
 71  
 import org.apache.commons.lang.StringUtils;
 72  
 import org.jdom.Document;
 73  
 import org.jdom.Element;
 74  
 import org.jdom.JDOMException;
 75  
 import org.jdom.input.SAXBuilder;
 76  
 import org.kuali.rice.core.api.impex.ExportDataSet;
 77  
 import org.kuali.rice.core.framework.impex.xml.XmlExporter;
 78  
 import org.kuali.rice.core.util.xml.XmlException;
 79  
 import org.kuali.rice.core.util.xml.XmlHelper;
 80  
 import org.kuali.rice.core.util.xml.XmlRenderer;
 81  
 import org.kuali.rice.kew.api.WorkflowRuntimeException;
 82  
 import org.kuali.rice.kew.doctype.DocumentTypeAttribute;
 83  
 import org.kuali.rice.kew.doctype.DocumentTypePolicy;
 84  
 import org.kuali.rice.kew.doctype.bo.DocumentType;
 85  
 import org.kuali.rice.kew.engine.node.BranchPrototype;
 86  
 import org.kuali.rice.kew.engine.node.NodeType;
 87  
 import org.kuali.rice.kew.engine.node.Process;
 88  
 import org.kuali.rice.kew.engine.node.RouteNode;
 89  
 import org.kuali.rice.kew.exception.ResourceUnavailableException;
 90  
 import org.kuali.rice.kew.export.KewExportDataSet;
 91  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 92  
 import org.kuali.rice.kew.util.KEWConstants;
 93  
 import org.kuali.rice.kim.api.group.Group;
 94  
 
 95  
 /**
 96  
  * Exports {@link DocumentType}s to XML.
 97  
  *
 98  
  * @see DocumentType
 99  
  *
 100  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 101  
  */
 102  0
 public class DocumentTypeXmlExporter implements XmlExporter {
 103  
 
 104  0
     protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
 105  
 
 106  0
     private XmlRenderer renderer = new XmlRenderer(DOCUMENT_TYPE_NAMESPACE);
 107  
 
 108  
         @Override
 109  
         public boolean supportPrettyPrint() {
 110  0
                 return true;
 111  
         }
 112  
 
 113  
         public Element export(ExportDataSet exportDataSet) {
 114  0
             KewExportDataSet dataSet = KewExportDataSet.fromExportDataSet(exportDataSet);
 115  0
         if (!dataSet.getDocumentTypes().isEmpty()) {
 116  0
             Collections.sort(dataSet.getDocumentTypes(), new DocumentTypeParentComparator());
 117  0
             Element rootElement = renderer.renderElement(null, DOCUMENT_TYPES);
 118  0
             rootElement.setAttribute(SCHEMA_LOCATION_ATTR, DOCUMENT_TYPE_SCHEMA_LOCATION, SCHEMA_NAMESPACE);
 119  0
             for (Iterator iterator = dataSet.getDocumentTypes().iterator(); iterator.hasNext();) {
 120  0
                 DocumentType documentType = (DocumentType) iterator.next();
 121  0
                 exportDocumentType(rootElement, documentType);
 122  0
             }
 123  0
             return rootElement;
 124  
         }
 125  0
         return null;
 126  
     }
 127  
 
 128  
     private void exportDocumentType(Element parent, DocumentType documentType) {
 129  0
         Element docTypeElement = renderer.renderElement(parent, DOCUMENT_TYPE);
 130  0
         List flattenedNodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(documentType, false);
 131  
         // derive a default exception workgroup by looking at how the nodes are configured
 132  0
         boolean hasDefaultExceptionWorkgroup = hasDefaultExceptionWorkgroup(flattenedNodes);
 133  0
         renderer.renderTextElement(docTypeElement, NAME, documentType.getName());
 134  0
         if (documentType.getParentDocType() != null) {
 135  0
             renderer.renderTextElement(docTypeElement, PARENT, documentType.getParentDocType().getName());
 136  
         }
 137  0
         renderer.renderTextElement(docTypeElement, DESCRIPTION, documentType.getDescription());
 138  0
         renderer.renderTextElement(docTypeElement, LABEL, documentType.getLabel());
 139  0
         if (!StringUtils.isBlank(documentType.getActualApplicationId())) {
 140  0
             renderer.renderTextElement(docTypeElement, APPLICATION_ID, documentType.getActualApplicationId());
 141  
         }
 142  0
         renderer.renderTextElement(docTypeElement, POST_PROCESSOR_NAME, documentType.getPostProcessorName());
 143  0
         Group superUserWorkgroup = documentType.getSuperUserWorkgroupNoInheritence();
 144  0
         if (superUserWorkgroup != null) {
 145  0
                 Element superUserGroupElement = renderer.renderTextElement(docTypeElement, SUPER_USER_GROUP_NAME, superUserWorkgroup.getName().trim());
 146  0
                 superUserGroupElement.setAttribute(NAMESPACE, superUserWorkgroup.getNamespaceCode().trim());
 147  
         }
 148  0
         Group blanketWorkgroup = documentType.getBlanketApproveWorkgroup();
 149  0
         if (blanketWorkgroup != null){
 150  0
                 Element blanketGroupElement = renderer.renderTextElement(docTypeElement, BLANKET_APPROVE_GROUP_NAME, blanketWorkgroup.getName().trim());
 151  0
                 blanketGroupElement.setAttribute(NAMESPACE, blanketWorkgroup.getNamespaceCode().trim());
 152  
         }
 153  0
         if (documentType.getBlanketApprovePolicy() != null){
 154  0
                 renderer.renderTextElement(docTypeElement, BLANKET_APPROVE_POLICY, documentType.getBlanketApprovePolicy());
 155  
         }
 156  0
         Group reportingWorkgroup = documentType.getReportingWorkgroup();
 157  0
         if (reportingWorkgroup != null) {
 158  0
                 Element reportingGroupElement = renderer.renderTextElement(docTypeElement, REPORTING_GROUP_NAME, reportingWorkgroup.getName().trim());
 159  0
                 reportingGroupElement.setAttribute(NAMESPACE, reportingWorkgroup.getNamespaceCode().trim());
 160  
         }
 161  0
         if (!flattenedNodes.isEmpty() && hasDefaultExceptionWorkgroup) {
 162  0
                 Group exceptionWorkgroup = ((RouteNode)flattenedNodes.get(0)).getExceptionWorkgroup();
 163  0
                 if (exceptionWorkgroup != null) {
 164  0
                         Element exceptionGroupElement = renderer.renderTextElement(docTypeElement, DEFAULT_EXCEPTION_GROUP_NAME, exceptionWorkgroup.getName().trim());
 165  0
                         exceptionGroupElement.setAttribute(NAMESPACE, exceptionWorkgroup.getNamespaceCode().trim());
 166  
                 }
 167  
         }
 168  0
         if (StringUtils.isNotBlank(documentType.getUnresolvedDocHandlerUrl())) {
 169  0
             renderer.renderTextElement(docTypeElement, DOC_HANDLER, documentType.getUnresolvedDocHandlerUrl());
 170  
         }
 171  0
         if (!StringUtils.isBlank(documentType.getUnresolvedHelpDefinitionUrl())) {
 172  0
             renderer.renderTextElement(docTypeElement, HELP_DEFINITION_URL, documentType.getUnresolvedHelpDefinitionUrl());
 173  
         }
 174  0
         if (!StringUtils.isBlank(documentType.getUnresolvedDocSearchHelpUrl())) {
 175  0
             renderer.renderTextElement(docTypeElement, DOC_SEARCH_HELP_URL, documentType.getUnresolvedDocSearchHelpUrl());
 176  
         }
 177  0
         if (!StringUtils.isBlank(documentType.getActualNotificationFromAddress())) {
 178  0
                 renderer.renderTextElement(docTypeElement, NOTIFICATION_FROM_ADDRESS, documentType.getActualNotificationFromAddress());
 179  
         }
 180  0
         renderer.renderBooleanElement(docTypeElement, ACTIVE, documentType.getActive(), true);
 181  0
         exportPolicies(docTypeElement, documentType.getDocumentTypePolicies());
 182  0
       exportAttributes(docTypeElement, documentType.getDocumentTypeAttributes());
 183  0
       exportSecurity(docTypeElement, documentType.getDocumentTypeSecurityXml());
 184  0
               if (!StringUtils.isBlank(documentType.getRoutingVersion())) {
 185  0
                       renderer.renderTextElement(docTypeElement, ROUTING_VERSION, documentType.getRoutingVersion());
 186  
               }
 187  0
               Process process = null;
 188  0
               if (documentType.getProcesses().size() > 0) {
 189  0
                   process = (Process)documentType.getProcesses().get(0);
 190  
               }
 191  0
               if (process != null && process.getInitialRouteNode() != null) {
 192  0
                   exportRouteData(docTypeElement, documentType, flattenedNodes, hasDefaultExceptionWorkgroup);
 193  
               } else {
 194  0
                   renderer.renderElement(docTypeElement, ROUTE_PATHS);
 195  
               }
 196  0
     }
 197  
 
 198  
     private void exportPolicies(Element parent, Collection policies) {
 199  0
         if (!policies.isEmpty()) {
 200  0
             Element policiesElement = renderer.renderElement(parent, POLICIES);
 201  0
             for (Iterator iterator = policies.iterator(); iterator.hasNext();) {
 202  0
                 DocumentTypePolicy policy = (DocumentTypePolicy) iterator.next();
 203  0
                 Element policyElement = renderer.renderElement(policiesElement, POLICY);
 204  0
                 renderer.renderTextElement(policyElement, NAME, policy.getPolicyName());
 205  0
                 renderer.renderBooleanElement(policyElement, VALUE, policy.getPolicyValue(), false);
 206  0
             }
 207  
         }
 208  0
     }
 209  
 
 210  
     private void exportAttributes(Element parent, List attributes) {
 211  0
         if (!attributes.isEmpty()) {
 212  0
             Element attributesElement = renderer.renderElement(parent, ATTRIBUTES);
 213  0
             for (Iterator iterator = attributes.iterator(); iterator.hasNext();) {
 214  0
                 DocumentTypeAttribute attribute = (DocumentTypeAttribute) iterator.next();
 215  0
                 Element attributeElement = renderer.renderElement(attributesElement, ATTRIBUTE);
 216  0
                 renderer.renderTextElement(attributeElement, NAME, attribute.getRuleAttribute().getName());
 217  0
             }
 218  
         }
 219  0
     }
 220  
 
 221  
     private void exportSecurity(Element parent, String securityXML) {
 222  0
       if (!org.apache.commons.lang.StringUtils.isEmpty(securityXML)) {
 223  
         try {
 224  0
           org.jdom.Document securityDoc = new SAXBuilder().build(new StringReader(securityXML));
 225  0
           XmlHelper.propagateNamespace(securityDoc.getRootElement(), DOCUMENT_TYPE_NAMESPACE);
 226  0
           parent.addContent(securityDoc.getRootElement().detach());
 227  0
         } catch (IOException e) {
 228  0
           throw new WorkflowRuntimeException("Error parsing doctype security XML.");
 229  0
         } catch (JDOMException e) {
 230  0
           throw new WorkflowRuntimeException("Error parsing doctype security XML.");
 231  0
         }
 232  
       }
 233  0
     }
 234  
 
 235  
     private void exportRouteData(Element parent, DocumentType documentType, List flattenedNodes, boolean hasDefaultExceptionWorkgroup) {
 236  0
         if (!flattenedNodes.isEmpty()) {
 237  0
             Element routePathsElement = renderer.renderElement(parent, ROUTE_PATHS);
 238  0
             for (Iterator iterator = documentType.getProcesses().iterator(); iterator.hasNext();) {
 239  0
                 Process process = (Process) iterator.next();
 240  0
                 Element routePathElement = renderer.renderElement(routePathsElement, ROUTE_PATH);
 241  0
                 if (!process.isInitial()) {
 242  0
                     renderer.renderAttribute(routePathElement, INITIAL_NODE, process.getInitialRouteNode().getRouteNodeName());
 243  0
                     renderer.renderAttribute(routePathElement, PROCESS_NAME, process.getName());
 244  
                 }
 245  0
                 exportProcess(routePathElement, process);
 246  0
             }
 247  
 
 248  0
             Element routeNodesElement = renderer.renderElement(parent, ROUTE_NODES);
 249  0
             for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) {
 250  0
                 RouteNode node = (RouteNode) iterator.next();
 251  0
                 exportRouteNode(routeNodesElement, node, hasDefaultExceptionWorkgroup);
 252  0
             }
 253  
         }
 254  0
     }
 255  
 
 256  
     /* default exception workgroup is not stored independently in db, so derive
 257  
      * one from the definition itself: if all nodes have the *same* exception workgroup name
 258  
      * defined, then this is tantamount to a *default* exception workgroup and can be
 259  
      * used as such.
 260  
      */
 261  
     private boolean hasDefaultExceptionWorkgroup(List flattenedNodes) {
 262  0
         boolean hasDefaultExceptionWorkgroup = true;
 263  0
         String exceptionWorkgroupName = null;
 264  0
         for (Iterator iterator = flattenedNodes.iterator(); iterator.hasNext();) {
 265  0
             RouteNode node = (RouteNode) iterator.next();
 266  0
             if (exceptionWorkgroupName == null) {
 267  0
                 exceptionWorkgroupName = node.getExceptionWorkgroupName();
 268  
             }
 269  0
             if (exceptionWorkgroupName == null || !exceptionWorkgroupName.equals(node.getExceptionWorkgroupName())) {
 270  0
                 hasDefaultExceptionWorkgroup = false;
 271  0
                 break;
 272  
             }
 273  0
         }
 274  0
         return hasDefaultExceptionWorkgroup;
 275  
     }
 276  
 
 277  
     private void exportProcess(Element parent, Process process) {
 278  0
             exportNodeGraph(parent, process.getInitialRouteNode(), null);
 279  0
     }
 280  
 
 281  
     private void exportNodeGraph(Element parent, RouteNode node, SplitJoinContext splitJoinContext) {
 282  0
         NodeType nodeType = null;
 283  
 
 284  0
         String contentFragment = node.getContentFragment();
 285  
         // some of the older versions of rice do not have content fragments
 286  0
         if(contentFragment == null || "".equals(contentFragment)){
 287  0
                 nodeType = getNodeTypeForNode(node);
 288  
         }else{
 289  
                 // I'm not sure if this should be the default implementation because
 290  
                 // it uses a string comparison instead of a classpath check.
 291  0
                 nodeType = this.getNodeTypeForNodeFromFragment(node);
 292  
         }
 293  
 
 294  0
         if (nodeType.isAssignableFrom(NodeType.SPLIT)) {
 295  0
             exportSplitNode(parent, node, nodeType, splitJoinContext);
 296  0
         } else if (nodeType.isAssignableFrom(NodeType.JOIN)) {
 297  0
             exportJoinNode(parent, node, nodeType, splitJoinContext);
 298  
         } else {
 299  0
             exportSimpleNode(parent, node, nodeType, splitJoinContext);
 300  
         }
 301  0
     }
 302  
 
 303  
     private void exportSimpleNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) {
 304  0
         Element simpleElement = renderNodeElement(parent, node, nodeType);
 305  0
         if (node.getNextNodes().size() > 1) {
 306  0
             throw new WorkflowRuntimeException("Simple node cannot have more than one next node: " + node.getRouteNodeName());
 307  
         }
 308  0
         if (node.getNextNodes().size() == 1) {
 309  0
             RouteNode nextNode = (RouteNode)node.getNextNodes().get(0);
 310  0
             renderer.renderAttribute(simpleElement, NEXT_NODE, nextNode.getRouteNodeName());
 311  0
             exportNodeGraph(parent, nextNode, splitJoinContext);
 312  
         }
 313  0
     }
 314  
 
 315  
     private void exportSplitNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) {
 316  0
         Element splitElement = renderNodeElement(parent, node, nodeType);
 317  0
         SplitJoinContext newSplitJoinContext = new SplitJoinContext(node);
 318  0
         for (Iterator iterator = node.getNextNodes().iterator(); iterator.hasNext();) {
 319  0
             RouteNode nextNode = (RouteNode) iterator.next();
 320  0
             BranchPrototype branch = nextNode.getBranch();
 321  0
             if (branch == null) {
 322  0
                 throw new WorkflowRuntimeException("Found a split next node with no associated branch prototype: " + nextNode.getRouteNodeName());
 323  
             }
 324  0
             exportBranch(splitElement, nextNode, branch, newSplitJoinContext);
 325  0
         }
 326  0
         RouteNode joinNode = newSplitJoinContext.joinNode;
 327  0
         if (joinNode == null) {
 328  0
             if (node.getNextNodes().size() > 0) {
 329  0
                 throw new WorkflowRuntimeException("Could not locate the join node for the given split node " + node.getRouteNodeName());
 330  
             }
 331  
         } else {
 332  0
             renderNodeElement(splitElement, joinNode, newSplitJoinContext.joinNodeType);
 333  0
             if (joinNode.getNextNodes().size() > 1) {
 334  0
                 throw new WorkflowRuntimeException("Join node cannot have more than one next node: " + joinNode.getRouteNodeName());
 335  
             }
 336  0
             if (joinNode.getNextNodes().size() == 1) {
 337  0
                 RouteNode nextNode = (RouteNode)joinNode.getNextNodes().get(0);
 338  0
                 renderer.renderAttribute(splitElement, NEXT_NODE, nextNode.getRouteNodeName());
 339  0
                 exportNodeGraph(parent, nextNode, splitJoinContext);
 340  
             }
 341  
         }
 342  0
     }
 343  
 
 344  
     private void exportBranch(Element parent, RouteNode node, BranchPrototype branch, SplitJoinContext splitJoinContext) {
 345  0
         Element branchElement = renderer.renderElement(parent, BRANCH);
 346  0
         renderer.renderAttribute(branchElement, NAME, branch.getName());
 347  0
         exportNodeGraph(branchElement, node, splitJoinContext);
 348  0
     }
 349  
 
 350  
     private void exportJoinNode(Element parent, RouteNode node, NodeType nodeType, SplitJoinContext splitJoinContext) {
 351  0
         if (splitJoinContext == null) {
 352  
             // 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
 353  
             // not associated with a proper split node.
 354  0
             if (!node.getNextNodes().isEmpty()) {
 355  0
                 throw new WorkflowRuntimeException("Could not export join node with next nodes that is not contained within a split.");
 356  
             }
 357  0
             renderNodeElement(parent, node, nodeType);
 358  0
         } else if (splitJoinContext.joinNode == null) {
 359  
             // this is the case where we are "inside" the split node in the XML, by setting up this context, the calling code knows that
 360  
             // when it renders all of the branches of the split node, it can then use this context info to render the join node before
 361  
             // closing the split
 362  0
             splitJoinContext.joinNode = node;
 363  0
             splitJoinContext.joinNodeType = nodeType;
 364  
         }
 365  0
     }
 366  
 
 367  
     private Element renderNodeElement(Element parent, RouteNode node, NodeType nodeType) {
 368  0
         String nodeName = nodeType.getName();
 369  
         // if it's a request activation node, be sure to export it as a simple node
 370  0
         if (nodeType.equals(NodeType.REQUEST_ACTIVATION)) {
 371  0
             nodeName = NodeType.SIMPLE.getName();
 372  
         }
 373  0
         Element nodeElement = renderer.renderElement(parent, nodeName);
 374  0
         renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName());
 375  0
         return nodeElement;
 376  
     }
 377  
 
 378  
     /**
 379  
      * Exists for backward compatibility for nodes which don't have a content fragment.
 380  
      */
 381  
     private void exportRouteNodeOld(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) {
 382  0
         NodeType nodeType = getNodeTypeForNode(node);
 383  0
         Element nodeElement = renderer.renderElement(parent, nodeType.getName());
 384  0
         renderer.renderAttribute(nodeElement, NAME, node.getRouteNodeName());
 385  0
         if (!hasDefaultExceptionWorkgroup) {
 386  0
                 if (!StringUtils.isBlank(node.getExceptionWorkgroupName())) {
 387  0
                         Element exceptionGroupElement = renderer.renderTextElement(nodeElement, EXCEPTION_GROUP_NAME, node.getExceptionWorkgroupName());
 388  0
                         exceptionGroupElement.setAttribute(NAMESPACE, node.getExceptionWorkgroup().getNamespaceCode());
 389  
                 }
 390  
         }
 391  0
         if (supportsActivationType(nodeType) && !StringUtils.isBlank(node.getActivationType())) {
 392  0
             renderer.renderTextElement(nodeElement, ACTIVATION_TYPE, node.getActivationType());
 393  
         }
 394  0
         if (supportsRouteMethod(nodeType)) {
 395  0
             exportRouteMethod(nodeElement, node);
 396  0
             renderer.renderBooleanElement(nodeElement, MANDATORY_ROUTE, node.getMandatoryRouteInd(), false);
 397  0
             renderer.renderBooleanElement(nodeElement, FINAL_APPROVAL, node.getFinalApprovalInd(), false);
 398  
         }
 399  0
         if (nodeType.isCustomNode(node.getNodeType())) {
 400  0
             renderer.renderTextElement(nodeElement, TYPE, node.getNodeType());
 401  
         }
 402  0
     }
 403  
 
 404  
     private void exportRouteNode(Element parent, RouteNode node, boolean hasDefaultExceptionWorkgroup) {
 405  0
         String contentFragment = node.getContentFragment();
 406  0
         if (StringUtils.isBlank(contentFragment)) {
 407  0
             exportRouteNodeOld(parent, node, hasDefaultExceptionWorkgroup);
 408  
         } else {
 409  
             try {
 410  0
                 Document document = XmlHelper.buildJDocument(new StringReader(contentFragment));
 411  0
                 Element rootElement = document.detachRootElement();
 412  0
                 XmlHelper.propagateNamespace(rootElement, parent.getNamespace());
 413  0
                 parent.addContent(rootElement);
 414  0
             } catch (XmlException e) {
 415  0
                 throw new WorkflowRuntimeException("Failed to load the content fragment.", e);
 416  0
             }
 417  
         }
 418  0
     }
 419  
 
 420  
 
 421  
     private NodeType getNodeTypeForNode(RouteNode node) {
 422  0
         NodeType nodeType = null;
 423  0
         String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType();
 424  
 
 425  
         try {
 426  0
             nodeType = NodeType.fromClassName(node.getNodeType());
 427  0
         } catch (ResourceUnavailableException e) {
 428  0
             throw new WorkflowRuntimeException(errorMessage, e);
 429  0
         }
 430  0
         if (nodeType == null) {
 431  0
             throw new WorkflowRuntimeException(errorMessage);
 432  
         }
 433  0
         return nodeType;
 434  
     }
 435  
 
 436  
     /**
 437  
      *
 438  
      * This method will find the base node type via the content fragment of the node.
 439  
      * Basically, it reads the node type, start, split, join, etc and then assigns
 440  
      * the base type to it.  This is necessary because there are cases where the
 441  
      * passed in nodeType will no be in the classpath. It should, in theory do
 442  
      * the same thing as getNodeTypeForNode.
 443  
      *
 444  
      * @param node
 445  
      * @return
 446  
      */
 447  
     private NodeType getNodeTypeForNodeFromFragment(RouteNode node) {
 448  0
         NodeType nodeType = null;
 449  0
         String contentFragment = node.getContentFragment();
 450  0
         String errorMessage = "Could not determine proper XML element for the given node type: " + node.getNodeType();
 451  
 
 452  0
         for (Iterator<NodeType> iterator = NodeType.getTypeList().iterator(); iterator.hasNext();) {
 453  0
                 NodeType nType = iterator.next();
 454  
                 // checks for something like <start
 455  
                 // or <split
 456  
                 // we may want to switch this out for something a little more robust.
 457  0
                 if(contentFragment.startsWith("<" + nType.getName())){
 458  0
                            nodeType = nType;
 459  
                    }
 460  0
         }
 461  
 
 462  0
         if (nodeType == null) {
 463  0
             throw new WorkflowRuntimeException(errorMessage);
 464  
         }
 465  0
         return nodeType;
 466  
     }
 467  
 
 468  
     /**
 469  
      * Any node can support activation type, this use to not be the case but now it is.
 470  
      */
 471  
     private boolean supportsActivationType(NodeType nodeType) {
 472  0
         return true;
 473  
     }
 474  
 
 475  
     /**
 476  
      * Any node can support route methods, this use to not be the case but now it is.
 477  
      */
 478  
     private boolean supportsRouteMethod(NodeType nodeType) {
 479  0
         return true;
 480  
     }
 481  
 
 482  
     private void exportRouteMethod(Element parent, RouteNode node) {
 483  0
         if (!StringUtils.isBlank(node.getRouteMethodName())) {
 484  0
             String routeMethodCode = node.getRouteMethodCode();
 485  0
             String elementName = null;
 486  0
             if (KEWConstants.ROUTE_LEVEL_FLEX_RM.equals(routeMethodCode)) {
 487  0
                 elementName = RULE_TEMPLATE;
 488  0
             } else if (KEWConstants.ROUTE_LEVEL_ROUTE_MODULE.equals(routeMethodCode)) {
 489  0
                 elementName = ROUTE_MODULE;
 490  
             } else {
 491  0
                 throw new WorkflowRuntimeException("Invalid route method code '"+routeMethodCode+"' for node " + node.getRouteNodeName());
 492  
             }
 493  0
             renderer.renderTextElement(parent, elementName, node.getRouteMethodName());
 494  
         }
 495  0
     }
 496  
 
 497  0
     private class DocumentTypeParentComparator implements Comparator {
 498  
 
 499  
         public int compare(Object object1, Object object2) {
 500  0
             DocumentType docType1 = (DocumentType)object1;
 501  0
             DocumentType docType2 = (DocumentType)object2;
 502  0
             Integer depth1 = getDepth(docType1);
 503  0
             Integer depth2 = getDepth(docType2);
 504  0
             return depth1.compareTo(depth2);
 505  
         }
 506  
 
 507  
         private Integer getDepth(DocumentType docType) {
 508  0
                 int depth = 0;
 509  0
                 while ((docType = docType.getParentDocType()) != null) {
 510  0
                         depth++;
 511  
                 }
 512  0
                 return new Integer(depth);
 513  
         }
 514  
 
 515  
     }
 516  
 
 517  0
     private class SplitJoinContext {
 518  
         public RouteNode splitNode;
 519  
         public RouteNode joinNode;
 520  
         public NodeType joinNodeType;
 521  0
         public SplitJoinContext(RouteNode splitNode) {
 522  0
             this.splitNode = splitNode;
 523  0
         }
 524  
     }
 525  
 
 526  
 
 527  
 }