001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.xml.export;
018:
019: import java.util.Collection;
020: import java.util.Collections;
021: import java.util.Comparator;
022: import java.util.Iterator;
023: import java.util.List;
024:
025: import org.apache.commons.lang.StringUtils;
026: import org.jdom.Element;
027:
028: import edu.iu.uis.eden.EdenConstants;
029: import edu.iu.uis.eden.KEWServiceLocator;
030: import edu.iu.uis.eden.doctype.DocumentType;
031: import edu.iu.uis.eden.doctype.DocumentTypeAttribute;
032: import edu.iu.uis.eden.doctype.DocumentTypePolicy;
033: import edu.iu.uis.eden.engine.node.BranchPrototype;
034: import edu.iu.uis.eden.engine.node.NodeType;
035: import edu.iu.uis.eden.engine.node.Process;
036: import edu.iu.uis.eden.engine.node.RouteNode;
037: import edu.iu.uis.eden.exception.ResourceUnavailableException;
038: import edu.iu.uis.eden.exception.WorkflowRuntimeException;
039: import edu.iu.uis.eden.export.ExportDataSet;
040: import edu.iu.uis.eden.workgroup.Workgroup;
041: import edu.iu.uis.eden.xml.XmlConstants;
042:
043: /**
044: * Exports {@link DocumentType}s to XML.
045: *
046: * @see DocumentType
047: *
048: * @author ewestfal
049: */
050: public class DocumentTypeXmlExporter implements XmlExporter,
051: XmlConstants {
052:
053: protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
054: .getLogger(getClass());
055:
056: private ExportRenderer renderer = new ExportRenderer(
057: DOCUMENT_TYPE_NAMESPACE);
058:
059: public Element export(ExportDataSet dataSet) {
060: if (!dataSet.getDocumentTypes().isEmpty()) {
061: Collections.sort(dataSet.getDocumentTypes(),
062: new DocumentTypeParentComparator());
063: Element rootElement = renderer.renderElement(null,
064: DOCUMENT_TYPES);
065: rootElement.setAttribute(SCHEMA_LOCATION_ATTR,
066: DOCUMENT_TYPE_SCHEMA_LOCATION, SCHEMA_NAMESPACE);
067: for (Iterator iterator = dataSet.getDocumentTypes()
068: .iterator(); iterator.hasNext();) {
069: DocumentType documentType = (DocumentType) iterator
070: .next();
071: exportDocumentType(rootElement, documentType);
072: }
073: return rootElement;
074: }
075: return null;
076: }
077:
078: private void exportDocumentType(Element parent,
079: DocumentType documentType) {
080: Element docTypeElement = renderer.renderElement(parent,
081: DOCUMENT_TYPE);
082: List flattenedNodes = KEWServiceLocator.getRouteNodeService()
083: .getFlattenedNodes(documentType, false);
084: // derive a default exception workgroup by looking at how the nodes are configured
085: boolean hasDefaultExceptionWorkgroup = hasDefaultExceptionWorkgroup(flattenedNodes);
086: renderer.renderTextElement(docTypeElement, NAME, documentType
087: .getName());
088: if (documentType.getParentDocType() != null) {
089: renderer.renderTextElement(docTypeElement, PARENT,
090: documentType.getParentDocType().getName());
091: }
092: renderer.renderTextElement(docTypeElement, DESCRIPTION,
093: documentType.getDescription());
094: renderer.renderTextElement(docTypeElement, LABEL, documentType
095: .getLabel());
096: if (!StringUtils.isBlank(documentType.getActualMessageEntity())) {
097: renderer.renderTextElement(docTypeElement, MESSAGE_ENTITY,
098: documentType.getActualMessageEntity());
099: }
100: renderer.renderTextElement(docTypeElement, POST_PROCESSOR_NAME,
101: documentType.getPostProcessorName());
102: renderer.renderTextElement(docTypeElement,
103: SUPER_USER_WORKGROUP_NAME, documentType
104: .getSuperUserWorkgroup().getGroupNameId()
105: .getNameId());
106: Workgroup blanketWorkgroup = documentType
107: .getBlanketApproveWorkgroup();
108: if (blanketWorkgroup != null) {
109: renderer.renderTextElement(docTypeElement,
110: BLANKET_APPROVE_WORKGROUP_NAME, blanketWorkgroup
111: .getGroupNameId().getNameId());
112: }
113: if (documentType.getBlanketApprovePolicy() != null) {
114: renderer.renderTextElement(docTypeElement,
115: BLANKET_APPROVE_POLICY, documentType
116: .getBlanketApprovePolicy());
117: }
118: if (!flattenedNodes.isEmpty() && hasDefaultExceptionWorkgroup) {
119: renderer.renderTextElement(docTypeElement,
120: DEFAULT_EXCEPTION_WORKGROUP_NAME,
121: ((RouteNode) flattenedNodes.get(0))
122: .getExceptionWorkgroupName());
123: }
124: if (!StringUtils.isBlank(documentType
125: .getUnresolvedDocHandlerUrl())) {
126: renderer.renderTextElement(docTypeElement, DOC_HANDLER,
127: documentType.getUnresolvedDocHandlerUrl());
128: } else {
129: // TODO doc handler should really be optional
130: renderer.renderTextElement(docTypeElement, DOC_HANDLER,
131: "none");
132: }
133: if (!StringUtils.isBlank(documentType
134: .getNotificationFromAddress())) {
135: renderer.renderTextElement(docTypeElement,
136: NOTIFICATION_FROM_ADDRESS, documentType
137: .getNotificationFromAddress());
138: }
139: renderer.renderBooleanElement(docTypeElement, ACTIVE,
140: documentType.getActiveInd(), true);
141: exportPolicies(docTypeElement, documentType.getPolicies());
142: exportAttributes(docTypeElement, documentType
143: .getDocumentTypeAttributes());
144: ;
145: renderer.renderTextElement(docTypeElement, ROUTING_VERSION,
146: documentType.getRoutingVersion());
147: exportRouteData(docTypeElement, documentType, flattenedNodes,
148: hasDefaultExceptionWorkgroup);
149: }
150:
151: private void exportPolicies(Element parent, Collection policies) {
152: if (!policies.isEmpty()) {
153: Element policiesElement = renderer.renderElement(parent,
154: POLICIES);
155: for (Iterator iterator = policies.iterator(); iterator
156: .hasNext();) {
157: DocumentTypePolicy policy = (DocumentTypePolicy) iterator
158: .next();
159: Element policyElement = renderer.renderElement(
160: policiesElement, POLICY);
161: renderer.renderTextElement(policyElement, NAME, policy
162: .getPolicyName());
163: renderer.renderBooleanElement(policyElement, VALUE,
164: policy.getPolicyValue(), false);
165: }
166: }
167: }
168:
169: private void exportAttributes(Element parent, List attributes) {
170: if (!attributes.isEmpty()) {
171: Element attributesElement = renderer.renderElement(parent,
172: ATTRIBUTES);
173: for (Iterator iterator = attributes.iterator(); iterator
174: .hasNext();) {
175: DocumentTypeAttribute attribute = (DocumentTypeAttribute) iterator
176: .next();
177: Element attributeElement = renderer.renderElement(
178: attributesElement, ATTRIBUTE);
179: renderer.renderTextElement(attributeElement, NAME,
180: attribute.getRuleAttribute().getName());
181: }
182: }
183: }
184:
185: private void exportRouteData(Element parent,
186: DocumentType documentType, List flattenedNodes,
187: boolean hasDefaultExceptionWorkgroup) {
188: if (!flattenedNodes.isEmpty()) {
189: Element routePathsElement = renderer.renderElement(parent,
190: ROUTE_PATHS);
191: for (Iterator iterator = documentType.getProcesses()
192: .iterator(); iterator.hasNext();) {
193: Process process = (Process) iterator.next();
194: Element routePathElement = renderer.renderElement(
195: routePathsElement, ROUTE_PATH);
196: if (!process.isInitial()) {
197: renderer.renderAttribute(routePathElement,
198: INITIAL_NODE, process.getInitialRouteNode()
199: .getRouteNodeName());
200: renderer.renderAttribute(routePathElement,
201: PROCESS_NAME, process.getName());
202: }
203: exportProcess(routePathElement, process);
204: }
205:
206: Element routeNodesElement = renderer.renderElement(parent,
207: ROUTE_NODES);
208: for (Iterator iterator = flattenedNodes.iterator(); iterator
209: .hasNext();) {
210: RouteNode node = (RouteNode) iterator.next();
211: exportRouteNode(routeNodesElement, node,
212: hasDefaultExceptionWorkgroup);
213: }
214: }
215: }
216:
217: /* default exception workgroup is not stored independently in db, so derive
218: * one from the definition itself: if all nodes have the *same* exception workgroup name
219: * defined, then this is tantamount to a *default* exception workgroup and can be
220: * used as such.
221: */
222: private boolean hasDefaultExceptionWorkgroup(List flattenedNodes) {
223: boolean hasDefaultExceptionWorkgroup = true;
224: String exceptionWorkgroupName = null;
225: for (Iterator iterator = flattenedNodes.iterator(); iterator
226: .hasNext();) {
227: RouteNode node = (RouteNode) iterator.next();
228: if (exceptionWorkgroupName == null) {
229: exceptionWorkgroupName = node
230: .getExceptionWorkgroupName();
231: }
232: if (!exceptionWorkgroupName.equals(node
233: .getExceptionWorkgroupName())) {
234: hasDefaultExceptionWorkgroup = false;
235: break;
236: }
237: }
238: return hasDefaultExceptionWorkgroup;
239: }
240:
241: private void exportProcess(Element parent, Process process) {
242: exportNodeGraph(parent, process.getInitialRouteNode(), null);
243: }
244:
245: private void exportNodeGraph(Element parent, RouteNode node,
246: SplitJoinContext splitJoinContext) {
247: NodeType nodeType = getNodeTypeForNode(node);
248: if (nodeType.isAssignableFrom(NodeType.SPLIT)) {
249: exportSplitNode(parent, node, nodeType, splitJoinContext);
250: } else if (nodeType.isAssignableFrom(NodeType.JOIN)) {
251: exportJoinNode(parent, node, nodeType, splitJoinContext);
252: } else {
253: exportSimpleNode(parent, node, nodeType, splitJoinContext);
254: }
255: }
256:
257: private void exportSimpleNode(Element parent, RouteNode node,
258: NodeType nodeType, SplitJoinContext splitJoinContext) {
259: Element simpleElement = renderNodeElement(parent, node,
260: nodeType);
261: if (node.getNextNodes().size() > 1) {
262: throw new WorkflowRuntimeException(
263: "Simple node cannot have more than one next node: "
264: + node.getRouteNodeName());
265: }
266: if (node.getNextNodes().size() == 1) {
267: RouteNode nextNode = (RouteNode) node.getNextNodes().get(0);
268: renderer.renderAttribute(simpleElement, NEXT_NODE, nextNode
269: .getRouteNodeName());
270: exportNodeGraph(parent, nextNode, splitJoinContext);
271: }
272: }
273:
274: private void exportSplitNode(Element parent, RouteNode node,
275: NodeType nodeType, SplitJoinContext splitJoinContext) {
276: Element splitElement = renderNodeElement(parent, node, nodeType);
277: SplitJoinContext newSplitJoinContext = new SplitJoinContext(
278: node);
279: for (Iterator iterator = node.getNextNodes().iterator(); iterator
280: .hasNext();) {
281: RouteNode nextNode = (RouteNode) iterator.next();
282: BranchPrototype branch = nextNode.getBranch();
283: if (branch == null) {
284: throw new WorkflowRuntimeException(
285: "Found a split next node with no associated branch prototype: "
286: + nextNode.getRouteNodeName());
287: }
288: exportBranch(splitElement, nextNode, branch,
289: newSplitJoinContext);
290: }
291: RouteNode joinNode = newSplitJoinContext.joinNode;
292: if (joinNode == null) {
293: if (node.getNextNodes().size() > 0) {
294: throw new WorkflowRuntimeException(
295: "Could not locate the join node for the given split node "
296: + node.getRouteNodeName());
297: }
298: } else {
299: renderNodeElement(splitElement, joinNode,
300: newSplitJoinContext.joinNodeType);
301: if (joinNode.getNextNodes().size() > 1) {
302: throw new WorkflowRuntimeException(
303: "Join node cannot have more than one next node: "
304: + joinNode.getRouteNodeName());
305: }
306: if (joinNode.getNextNodes().size() == 1) {
307: RouteNode nextNode = (RouteNode) joinNode
308: .getNextNodes().get(0);
309: renderer.renderAttribute(splitElement, NEXT_NODE,
310: nextNode.getRouteNodeName());
311: exportNodeGraph(parent, nextNode, splitJoinContext);
312: }
313: }
314: }
315:
316: private void exportBranch(Element parent, RouteNode node,
317: BranchPrototype branch, SplitJoinContext splitJoinContext) {
318: Element branchElement = renderer.renderElement(parent, BRANCH);
319: renderer.renderAttribute(branchElement, NAME, branch.getName());
320: exportNodeGraph(branchElement, node, splitJoinContext);
321: }
322:
323: private void exportJoinNode(Element parent, RouteNode node,
324: NodeType nodeType, SplitJoinContext splitJoinContext) {
325: if (splitJoinContext == null) {
326: // 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
327: // not associated with a proper split node.
328: if (!node.getNextNodes().isEmpty()) {
329: throw new WorkflowRuntimeException(
330: "Could not export join node with next nodes that is not contained within a split.");
331: }
332: renderNodeElement(parent, node, nodeType);
333: } else if (splitJoinContext.joinNode == null) {
334: // this is the case where we are "inside" the split node in the XML, by setting up this context, the calling code knows that
335: // when it renders all of the branches of the split node, it can then use this context info to render the join node before
336: // closing the split
337: splitJoinContext.joinNode = node;
338: splitJoinContext.joinNodeType = nodeType;
339: }
340: }
341:
342: private Element renderNodeElement(Element parent, RouteNode node,
343: NodeType nodeType) {
344: Element nodeElement = renderer.renderElement(parent, nodeType
345: .getName());
346: renderer.renderAttribute(nodeElement, NAME, node
347: .getRouteNodeName());
348: return nodeElement;
349: }
350:
351: private void exportRouteNode(Element parent, RouteNode node,
352: boolean hasDefaultExceptionWorkgroup) {
353: NodeType nodeType = getNodeTypeForNode(node);
354: Element nodeElement = renderer.renderElement(parent, nodeType
355: .getName());
356: renderer.renderAttribute(nodeElement, NAME, node
357: .getRouteNodeName());
358: if (!hasDefaultExceptionWorkgroup) {
359: renderer.renderTextElement(nodeElement,
360: EXCEPTION_WORKGROUP_NAME, node
361: .getExceptionWorkgroupName());
362: }
363: if (supportsActivationType(nodeType)
364: && !StringUtils.isBlank(node.getActivationType())) {
365: renderer.renderTextElement(nodeElement, ACTIVATION_TYPE,
366: node.getActivationType());
367: }
368: if (supportsRouteMethod(nodeType)) {
369: exportRouteMethod(nodeElement, node);
370: renderer.renderBooleanElement(nodeElement, MANDATORY_ROUTE,
371: node.getMandatoryRouteInd(), false);
372: renderer.renderBooleanElement(nodeElement, FINAL_APPROVAL,
373: node.getFinalApprovalInd(), false);
374: }
375: if (nodeType.isCustomNode(node.getNodeType())) {
376: renderer.renderTextElement(nodeElement, TYPE, node
377: .getNodeType());
378: }
379: }
380:
381: private NodeType getNodeTypeForNode(RouteNode node) {
382: NodeType nodeType = null;
383: String errorMessage = "Could not determine proper XML element for the given node type: "
384: + node.getNodeType();
385: try {
386: nodeType = NodeType.fromClassName(node.getNodeType());
387: } catch (ResourceUnavailableException e) {
388: throw new WorkflowRuntimeException(errorMessage, e);
389: }
390: if (nodeType == null) {
391: throw new WorkflowRuntimeException(errorMessage);
392: }
393: return nodeType;
394: }
395:
396: /**
397: * Any node can support activation type, this use to not be the case but now it is.
398: */
399: private boolean supportsActivationType(NodeType nodeType) {
400: return true;
401: }
402:
403: /**
404: * Any node can support route methods, this use to not be the case but now it is.
405: */
406: private boolean supportsRouteMethod(NodeType nodeType) {
407: return true;
408: }
409:
410: private void exportRouteMethod(Element parent, RouteNode node) {
411: if (!StringUtils.isBlank(node.getRouteMethodName())) {
412: String routeMethodCode = node.getRouteMethodCode();
413: String elementName = null;
414: if (EdenConstants.ROUTE_LEVEL_FLEX_RM
415: .equals(routeMethodCode)) {
416: elementName = RULE_TEMPLATE;
417: } else if (EdenConstants.ROUTE_LEVEL_ROUTE_MODULE
418: .equals(routeMethodCode)) {
419: elementName = ROUTE_MODULE;
420: } else {
421: throw new WorkflowRuntimeException(
422: "Invalid route method code '" + routeMethodCode
423: + "' for node "
424: + node.getRouteNodeName());
425: }
426: renderer.renderTextElement(parent, elementName, node
427: .getRouteMethodName());
428: }
429: }
430:
431: private class DocumentTypeParentComparator implements Comparator {
432:
433: public int compare(Object object1, Object object2) {
434: DocumentType docType1 = (DocumentType) object1;
435: DocumentType docType2 = (DocumentType) object2;
436: Integer depth1 = getDepth(docType1);
437: Integer depth2 = getDepth(docType2);
438: return depth1.compareTo(depth2);
439: }
440:
441: private Integer getDepth(DocumentType docType) {
442: int depth = 0;
443: while ((docType = docType.getParentDocType()) != null) {
444: depth++;
445: }
446: return new Integer(depth);
447: }
448:
449: }
450:
451: private class SplitJoinContext {
452: public RouteNode splitNode;
453: public RouteNode joinNode;
454: public NodeType joinNodeType;
455:
456: public SplitJoinContext(RouteNode splitNode) {
457: this.splitNode = splitNode;
458: }
459: }
460:
461: }
|