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}