/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.mapping.xml; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.runtime.client.Messages; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * <p>Reads an mapping definition file in XML format. When finished reading * the mapping info from the XML file, this class holds an object representation * of it, rooted in a <code>MappingNode</code> instance, and also holds an XML * <code>Document</code> representation. </p> * * <h3>Example usage (loading from stream; exceptions not shown)</h3> * <p><pre> * MappingLoader loader = new MappingLoader(); * MappingNode mappingRootNode = loader.loadDocument(istream); * </pre></p> * * @see MappingNode */ public class MappingLoader { private HashMap unresolvedNamespaces = new HashMap(); private final ITeiidServerVersion teiidVersion; /** * @param teiidVersion */ public MappingLoader(ITeiidServerVersion teiidVersion) { this.teiidVersion = teiidVersion; } /** * @return the teiidVersion */ public ITeiidServerVersion getTeiidVersion() { return this.teiidVersion; } /** * <p>Load mapping definition from XML Document object. </p> */ public MappingDocument loadDocument(InputStream stream) throws MappingException { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setValidating(false); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(stream); return loadContents(doc); } catch (IOException e) { throw new MappingException(e); } catch (ParserConfigurationException e) { throw new MappingException(e); } catch (SAXException e) { throw new MappingException(e); } } public MappingDocument loadDocument(String fileName) throws MappingException, FileNotFoundException { FileInputStream fis = new FileInputStream(fileName); return loadDocument(fis); } /** * Load contents into temporary structures. */ MappingDocument loadContents(Document document) throws MappingException { MappingDocument doc = new MappingDocument(getTeiidVersion(), false); loadDocumentProperties(doc, document.getDocumentElement()); // now load all the children Collection mappingChildren = getChildren(document.getDocumentElement(), MappingNodeConstants.Tags.MAPPING_NODE_NAME); doc = (MappingDocument)recursiveLoadContents(mappingChildren, doc); return doc; } private MappingNode recursiveLoadContents(Collection mappingChildren, MappingBaseNode parent) throws MappingException { MappingBaseNode node = null; for (Iterator i = mappingChildren.iterator(); i.hasNext();){ Element elem = (Element)i.next(); node = processMappingNode(elem, parent); Collection childrenMappingNodes = getChildren(elem, MappingNodeConstants.Tags.MAPPING_NODE_NAME); recursiveLoadContents(childrenMappingNodes, node); } return parent; } public Collection<Element> getChildren(Element elem, String name) { NodeList children = elem.getChildNodes(); LinkedList<Element> results = new LinkedList<Element>(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node instanceof Element && node.getNodeName().equals(name)) { results.add((Element)node); } } return results; } public Element getChild(Element elem, String name) { NodeList children = elem.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node instanceof Element && node.getNodeName().equals(name)) { return (Element)node; } } return null; } /** * Load a "sequence" node from the mapping document * @param element - parent element * @return a sequence node */ MappingSequenceNode loadSequenceNode(Element element, MappingBaseNode parentNode) { MappingSequenceNode node = new MappingSequenceNode(getTeiidVersion()); node.setMinOccurrs(getMinOccurrences(element)); node.setMaxOccurrs(getMaxOccurrences(element)); node.setSource(getSource(element)); node.setExclude(isExcluded(element)); node.setStagingTables(getStagingTableNames(element)); return node; } /** * Load a "Element" node from mapping document. This can be a * "Recursive" or "Criteria" or normal node. * @param element - parent element * @return retuns a MappingElement */ MappingElement loadElementNode(Element element, MappingBaseNode parentNode, boolean rootElement) throws MappingException{ MappingElement node = null; String name = getName(element); if (name == null || name.length()==0) { throw new MappingException(Messages.getString(Messages.Mapping.invalidName)); } Namespace[] namespaces = getNamespaceDeclarations(element); Namespace namespace = getNamespace(element, namespaces, parentNode); // There are effectively three types of elements, recursive, criteria and regular.. if (isRecursive(element)) { // first check if this is a "recursive" element MappingRecursiveElement elem = new MappingRecursiveElement(getTeiidVersion(), name, namespace, getRecursionMappingClass(element)); elem.setCriteria(getRecursionCriteria(element)); elem.setRecursionLimit(getRecursionLimit(element), throwExceptionOnRecursionLimit(element)); node = elem; } else { // this regular element node = new MappingElement(getTeiidVersion(), name, namespace); } // now load all other common properties. if (rootElement) { node.setMinOccurrs(1); node.setMaxOccurrs(1); } else { node.setMinOccurrs(getMinOccurrences(element)); node.setMaxOccurrs(getMaxOccurrences(element)); } node.setNameInSource(getNameInSource(element)); node.setSource(getSource(element)); node.setOptional(isOptional(element)); node.setDefaultValue(getDefaultValue(element)); node.setValue(getFixedValue(element)); node.setNillable(isNillable(element)); node.setExclude(isExcluded(element)); node.setType(getBuitInType(element)); node.setNormalizeText(getNormalizeText(element)); node.setAlwaysInclude(includeAlways(element)); node.setStagingTables(getStagingTableNames(element)); node.setNamespaces(namespaces); return node; } /** * Load an attribute node from the element */ void loadAttributeNode(Element element, MappingElement parent) throws MappingException { String name = getName(element); String nsPrefix = getElementValue(element, MappingNodeConstants.Tags.NAMESPACE_PREFIX); if (name == null || name.length()==0) { throw new MappingException(Messages.getString(Messages.Mapping.invalidName)); } Namespace namespace = null; boolean normalAttribute = true; if (name.equalsIgnoreCase(MappingNodeConstants.NAMESPACE_DECLARATION_ATTRIBUTE_NAMESPACE)) { // this is default name space where only "xmlns" is defined. We do not need to global map // as there may be more(I guess..) namespace = new Namespace("", getFixedValue(element)); //$NON-NLS-1$ parent.addNamespace(namespace); normalAttribute = false; } else if (nsPrefix != null && nsPrefix.equalsIgnoreCase(MappingNodeConstants.NAMESPACE_DECLARATION_ATTRIBUTE_NAMESPACE)) { // prior to 5.5, a document can use the namespaces before it declares them; this little // trick(hack) to fix it. namespace = (Namespace)this.unresolvedNamespaces.remove(name); if (namespace == null) { namespace = new Namespace(name); } // this is specific name space declaration like xsi, foo etc.. namespace.setUri(getFixedValue(element)); parent.addNamespace(namespace); normalAttribute = false; } else { // this is a invalid form of namespace = getNamespace(element, null, parent); } // if this not any namespace specific attribute then it is any normal attribute on the // tree; treat it as such. if (normalAttribute) { MappingAttribute attribute = new MappingAttribute(getTeiidVersion(), getName(element), namespace); // now get all other properties for the attribute. attribute.setNameInSource(getNameInSource(element)); attribute.setDefaultValue(getDefaultValue(element)); attribute.setValue(getFixedValue(element)); attribute.setExclude(isExcluded(element)); attribute.setNormalizeText(getNormalizeText(element)); attribute.setOptional(isOptional(element)); attribute.setAlwaysInclude(includeAlways(element)); parent.addAttribute(attribute); } } /** * Load a "Choice" Node * @param element * @return */ MappingChoiceNode loadChoiceNode(Element element, MappingBaseNode parentNode) { MappingChoiceNode node = new MappingChoiceNode(getTeiidVersion(), exceptionOnDefault(element)); node.setMinOccurrs(getMinOccurrences(element)); node.setMaxOccurrs(getMaxOccurrences(element)); node.setSource(getSource(element)); node.setExclude(isExcluded(element)); node.setStagingTables(getStagingTableNames(element)); return node; } /** * Load the "all" node */ MappingAllNode loadAllNode(Element element, MappingBaseNode parentNode) { MappingAllNode node = new MappingAllNode(getTeiidVersion()); node.setMinOccurrs(getMinOccurrences(element)); node.setMaxOccurrs(getMaxOccurrences(element)); node.setSource(getSource(element)); node.setExclude(isExcluded(element)); node.setStagingTables(getStagingTableNames(element)); return node; } /** * Load the comment node */ void loadCommentNode(Element element, MappingElement parent) { MappingCommentNode comment = new MappingCommentNode(getTeiidVersion(), getCommentText(element)); parent.addCommentNode(comment); } /** * Load the mapping document */ MappingDocument loadDocumentProperties (MappingDocument doc, Element element) { boolean formatted = isFormattedDocument(element); if (formatted != MappingNodeConstants.Defaults.DEFAULT_FORMATTED_DOCUMENT.booleanValue()) { doc.setFormatted(formatted); } String encoding = getDocumentEncoding(element); if (!MappingNodeConstants.Defaults.DEFAULT_DOCUMENT_ENCODING.equalsIgnoreCase(encoding)) { doc.setDocumentEncoding(encoding); } return doc; } /** * Load a criteria node; Criteria node can only be child of a choice node. */ MappingCriteriaNode loadCriteriaNode(Element element, MappingBaseNode parentNode) throws MappingException{ if (getCriteria(element) != null || isDefaultOnChoiceNode(element)) { // add this criteria node to the parent and make it the parent itself for rest of the information. return new MappingCriteriaNode(getTeiidVersion(), getCriteria(element), isDefaultOnChoiceNode(element)); } throw new MappingException(Messages.getString(Messages.Mapping.invalid_criteria_node)); } /** * Process a mapping node that has been encountered. * @param element XML Document Element which is the source of the * MappingNode * @param parentNode MappingNode parent - will be null only the first * time, for the root Element * @return MappingNode just processed */ MappingBaseNode processMappingNode(Element element, MappingBaseNode parentNode) throws MappingException { boolean isRootNode = (parentNode instanceof MappingDocument); // Parse the node based on the node type. String nodeType = getElementValue( element, MappingNodeConstants.Tags.NODE_TYPE ); if (nodeType == null || nodeType.length() == 0) { nodeType = MappingNodeConstants.ELEMENT; } // Load the document properties if (isRootNode && nodeType.equalsIgnoreCase(MappingNodeConstants.ELEMENT)) { MappingDocument doc = (MappingDocument)parentNode; loadDocumentProperties(doc, element); } // prior to 5.5, a criteria node also behaved like element, however it has been changed to // have only criteria information. however we still need to support the old vdbs with // old type of mapping document (this can be removed after couple versions). if (nodeType.equalsIgnoreCase(MappingNodeConstants.ELEMENT) && (getCriteria(element) != null || isDefaultOnChoiceNode(element))) { // add this criteria node to the parent and make it the parent itself for rest of the information. MappingCriteriaNode node = new MappingCriteriaNode(getTeiidVersion(), getCriteria(element), isDefaultOnChoiceNode(element)); parentNode.addCriteriaNode(node); parentNode = node; } if (nodeType.equalsIgnoreCase(MappingNodeConstants.ELEMENT)) { MappingElement child = loadElementNode(element, parentNode, isRootNode); parentNode.addChildElement(child); return child; } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.ATTRIBUTE)) { loadAttributeNode(element, (MappingElement)parentNode); } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.CHOICE)) { MappingChoiceNode child = loadChoiceNode(element, parentNode); parentNode.addChoiceNode(child); return child; } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.CRITERIA)) { MappingCriteriaNode child = loadCriteriaNode(element, parentNode); parentNode.addCriteriaNode(child); return child; } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.ALL)) { MappingAllNode child = loadAllNode(element, parentNode); parentNode.addAllNode(child); return child; } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.SEQUENCE)) { MappingSequenceNode child = loadSequenceNode(element, parentNode); parentNode.addSequenceNode(child); return child; } else if (nodeType.equalsIgnoreCase(MappingNodeConstants.COMMENT)) { loadCommentNode(element, (MappingElement)parentNode); } else { // should we ignore I am not sure?? throw new MappingException(Messages.getString(Messages.Mapping.unknown_node_type, nodeType)); } return null; } /** * Return Properties holding namespaces declarations, or null if none */ private Namespace[] getNamespaceDeclarations(Element element) { ArrayList namespaces = new ArrayList(); Iterator elements = getChildren(element, MappingNodeConstants.Tags.NAMESPACE_DECLARATION).iterator(); while (elements.hasNext()){ Element namespace = (Element)elements.next(); Element prefixEl = getElement(namespace, MappingNodeConstants.Tags.NAMESPACE_DECLARATION_PREFIX); Element uriEl = getElement(namespace, MappingNodeConstants.Tags.NAMESPACE_DECLARATION_URI); String prefix = (prefixEl != null ? getTextTrim(prefixEl) : MappingNodeConstants.DEFAULT_NAMESPACE_PREFIX); String uri = getTextTrim(uriEl); namespaces.add(new Namespace(prefix, uri)); } return (Namespace[])namespaces.toArray(new Namespace[namespaces.size()]); } static String getTextTrim(Element element) { if (element == null) { return null; } String result = element.getTextContent(); if (result != null) { return result.trim(); } return result; } /** * Get a specific child element of an element. */ Element getElement( Element element, String childName ) { return getChild(element, childName); } String getElementValue( Element element, String childName ) { Element child = getChild(element, childName); return getTextTrim(child); } String getElementValue( Element element, String childName , String defalt) { Element child = getChild(element, childName); String result = getTextTrim(child); if (result == null) { return defalt; } return result; } int getIntElementValue( Element element, String childName , int defalt) { Element child = getChild(element, childName); if (child != null) { return Integer.valueOf(getTextTrim(child)).intValue(); } return defalt; } boolean getBooleanElementValue( Element element, String childName , boolean defalt) { Element child = getChild(element, childName); if (child != null) { return Boolean.valueOf(getTextTrim(child)).booleanValue(); } return defalt; } String getName(Element element) { return getElementValue(element, MappingNodeConstants.Tags.NAME); } int getMinOccurrences(Element element) { return getIntElementValue(element, MappingNodeConstants.Tags.CARDINALITY_MIN_BOUND, MappingNodeConstants.Defaults.DEFAULT_CARDINALITY_MINIMUM_BOUND.intValue()); } int getMaxOccurrences(Element element) { String maxBound = getElementValue(element, MappingNodeConstants.Tags.CARDINALITY_MAX_BOUND ); if (maxBound != null && maxBound.equals(MappingNodeConstants.CARDINALITY_UNBOUNDED_STRING)){ return MappingNodeConstants.CARDINALITY_UNBOUNDED.intValue(); } else if (maxBound != null){ return Integer.valueOf(maxBound).intValue(); } else { return MappingNodeConstants.Defaults.DEFAULT_CARDINALITY_MAXIMUM_BOUND.intValue(); } } String getNameInSource(Element element) { return getElementValue(element, MappingNodeConstants.Tags.ELEMENT_NAME); } String getCommentText(Element element) { return getElementValue(element, MappingNodeConstants.Tags.COMMENT_TEXT); } boolean isOptional(Element element) { return getBooleanElementValue( element, MappingNodeConstants.Tags.IS_OPTIONAL, MappingNodeConstants.Defaults.DEFAULT_IS_OPTIONAL.booleanValue()); } String getSource(Element element) { return getElementValue(element, MappingNodeConstants.Tags.RESULT_SET_NAME ); } String getCriteria(Element element) { return getElementValue(element, MappingNodeConstants.Tags.CRITERIA ); } String getDefaultValue(Element element) { return getElementValue(element, MappingNodeConstants.Tags.DEFAULT_VALUE); } String getFixedValue(Element element) { return getElementValue(element, MappingNodeConstants.Tags.FIXED_VALUE); } boolean isNillable(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.IS_NILLABLE, MappingNodeConstants.Defaults.DEFAULT_IS_NILLABLE.booleanValue()); } boolean isExcluded(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.IS_EXCLUDED, MappingNodeConstants.Defaults.DEFAULT_IS_EXCLUDED.booleanValue()); } boolean isDefaultOnChoiceNode(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.IS_DEFAULT_CHOICE, MappingNodeConstants.Defaults.DEFAULT_IS_DEFAULT_CHOICE.booleanValue()); } boolean exceptionOnDefault(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.EXCEPTION_ON_DEFAULT, MappingNodeConstants.Defaults.DEFAULT_EXCEPTION_ON_DEFAULT.booleanValue()); } String getDocumentEncoding(Element element) { return getElementValue(element, MappingNodeConstants.Tags.DOCUMENT_ENCODING, MappingNodeConstants.Defaults.DEFAULT_DOCUMENT_ENCODING); } boolean isFormattedDocument(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.FORMATTED_DOCUMENT, MappingNodeConstants.Defaults.DEFAULT_FORMATTED_DOCUMENT.booleanValue()); } boolean isRecursive(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.IS_RECURSIVE, MappingNodeConstants.Defaults.DEFAULT_IS_RECURSIVE.booleanValue()); } String getRecursionCriteria(Element element) { return getElementValue(element, MappingNodeConstants.Tags.RECURSION_CRITERIA); } String getRecursionMappingClass(Element element) { return getElementValue(element, MappingNodeConstants.Tags.RECURSION_ROOT_MAPPING_CLASS); } int getRecursionLimit(Element element) { return getIntElementValue(element, MappingNodeConstants.Tags.RECURSION_LIMIT, MappingNodeConstants.Defaults.DEFAULT_RECURSION_LIMIT.intValue()); } boolean throwExceptionOnRecursionLimit(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.RECURSION_LIMIT_EXCEPTION, MappingNodeConstants.Defaults.DEFAULT_EXCEPTION_ON_RECURSION_LIMIT.booleanValue()); } List getStagingTableNames(Element element) { ArrayList cacheGroups = new ArrayList(); Collection tempGroupElements = getChildren(element, MappingNodeConstants.Tags.TEMP_GROUP_NAME); for (Iterator i = tempGroupElements.iterator(); i.hasNext();) { Element tempGroup = (Element)i.next(); cacheGroups.add(getTextTrim(tempGroup)); } return cacheGroups; } String getNormalizeText(Element element) { return getElementValue(element, MappingNodeConstants.Tags.NORMALIZE_TEXT, MappingNodeConstants.Defaults.DEFAULT_NORMALIZE_TEXT); } String getBuitInType(Element element) { return getElementValue(element, MappingNodeConstants.Tags.BUILT_IN_TYPE); } boolean includeAlways(Element element) { return getBooleanElementValue(element, MappingNodeConstants.Tags.ALWAYS_INCLUDE, false); } Namespace getNamespace(Element element, Namespace[] localNamespaces, MappingBaseNode parentNode) { String prefix = getElementValue(element, MappingNodeConstants.Tags.NAMESPACE_PREFIX); if (prefix != null) { if (localNamespaces != null) { // first try to find the name space in its own namespace attributes for (int i = 0; i < localNamespaces.length; i++) { if (localNamespaces[i].getPrefix().equals(prefix)) { return localNamespaces[i]; } } } // then look in the parent nodes. while (parentNode != null) { if (parentNode instanceof MappingElement) { MappingElement parentElement = (MappingElement)parentNode; return getNamespace(element, parentElement.getNamespaces(), parentElement.getParentNode()); } parentNode = parentNode.getParentNode(); } // default; we should never get here.. except otherwise a namespace is used before its // declaration; which is case sometimes. Namespace unresolved = new Namespace(prefix); this.unresolvedNamespaces.put(prefix, unresolved); return unresolved; } return MappingNodeConstants.NO_NAMESPACE; } } // END CLASS