All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.atlasmap.xml.core.XmlFieldWriter Maven / Gradle / Ivy

/**
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.atlasmap.xml.core;

import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import io.atlasmap.api.AtlasException;
import io.atlasmap.core.PathUtil;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.FieldType;
import io.atlasmap.xml.v2.XmlField;

public class XmlFieldWriter extends XmlFieldTransformer {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(XmlFieldWriter.class);

    private Document document = null;
    private boolean enableElementNamespaces = true;
    private boolean enableAttributeNamespaces = true;
    private boolean ignoreMissingNamespaces = true;

    public XmlFieldWriter() throws AtlasException {
        this(new HashMap<>(), null);
    }

    public XmlFieldWriter(Map namespaces, String seedDocument) throws AtlasException {
        super(namespaces);
        this.document = createDocument(namespaces, seedDocument);
        // check to see if the seed document has namespaces
        seedDocumentNamespaces(document);
    }

    public void write(List fields) throws AtlasException {
        if (fields == null) {
            throw new AtlasException(new IllegalArgumentException("Argument 'fields' cannot be null"));
        }
        for (XmlField f : fields) {
            write(f);
        }
    }

    public void write(Field field) throws AtlasException {
        if (field == null) {
            throw new AtlasException(new IllegalArgumentException("Argument 'field' cannot be null"));
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Now processing field p=%s t=%s v=%s", field.getPath(), field.getFieldType(),
                    field.getValue());
        }

        PathUtil path = new PathUtil(field.getPath());
        String lastSegment = path.getLastSegment();
        Element parentNode = null;
        String parentSegment = null;
        for (String segment : path.getSegments()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Now processing segment: " + segment);
                logger.debug("Parent element is currently: " + writeDocumentToString(true, parentNode));
            }
            if (parentNode == null) {
                // processing root node
                parentNode = document.getDocumentElement();
                String cleanedSegment = PathUtil.cleanPathSegment(segment);
                if (parentNode == null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Creating root element with name: " + cleanedSegment);
                    }
                    // no root node exists yet, create root node with this segment name;
                    Element rootNode = createElement(segment);
                    addNamespacesToElement(rootNode, namespaces);
                    document.appendChild(rootNode);
                    parentNode = rootNode;
                } else if (!(parentNode.getNodeName().equals(segment))) {
                    // make sure root element's name matches.
                    throw new AtlasException("Root element name '" + parentNode.getNodeName()
                            + "' does not match expected name '" + segment + "' from path: " + field.getPath());
                }
                parentSegment = segment;
            } else {
                if (logger.isDebugEnabled()) {
                    if (segment == lastSegment) {
                        logger.debug("Now processing field value segment: " + segment);
                    } else {
                        logger.debug("Now processing parent segment: " + segment);
                    }
                }

                if (!PathUtil.isAttributeSegment(segment)) {
                    // if current segment of path isn't attribute, it refers to a child element,
                    // find it or create it..
                    Element childNode = getChildNode(parentNode, parentSegment, segment);
                    if (childNode == null) {
                        childNode = createParentNode(parentNode, parentSegment, segment);
                    }
                    parentNode = childNode;
                    parentSegment = segment;
                }

                if (segment == lastSegment) {
                    writeValue(parentNode, segment, field);
                }
            }
        }
    }

    public static void addNamespacesToElement(Element node, Map namespaces) {
        for (String namespaceAlias : namespaces.keySet()) {
            String namespaceUri = namespaces.get(namespaceAlias);
            String attributeName = "xmlns";
            if (namespaceAlias != null && !namespaceAlias.equals("")) {
                attributeName += ":" + namespaceAlias;
            }
            node.setAttributeNS("http://www.w3.org/2000/xmlns/", attributeName, namespaceUri);
        }
    }

    public void writeValue(Element parentNode, String segment, Field field) throws AtlasException {
        if (logger.isDebugEnabled()) {
            logger.debug("Writing field value in parent node '" + segment + "', parentNode: "
                    + writeDocumentToString(true, parentNode));
        }
        String value = convertValue(field);
        if (PathUtil.isAttributeSegment(segment)) {
            String cleanedSegment = PathUtil.cleanPathSegment(segment);
            if (this.enableAttributeNamespaces) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Attribute namespaces are enabled, determining namespace.");
                }
                String namespaceAlias = null;
                String namespaceUri = null;
                if (PathUtil.isNamespaceSegment(segment)) {
                    namespaceAlias = PathUtil.getNamespace(segment);
                    namespaceUri = this.namespaces.get(namespaceAlias);
                    logger.debug("Parsed namespace alias '" + namespaceAlias + "', from segment '" + segment
                            + "', namespaceUri: " + namespaceUri);
                }
                if (!this.ignoreMissingNamespaces && namespaceUri == null) {
                    throw new AtlasException("Cannot find namespace URI for attribute: '" + segment
                            + "', available namespaces: " + this.namespaces);
                }
                if (namespaceUri != null) {
                    parentNode.setAttributeNS(namespaceUri, namespaceAlias + ":" + cleanedSegment, value);
                } else {
                    parentNode.setAttribute(cleanedSegment, value);
                }
            } else {
                parentNode.setAttribute(cleanedSegment, value);
            }
        } else { // set element value
            parentNode.setTextContent(value);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Parent node after value written: " + writeDocumentToString(true, parentNode));
        }
    }

    public static Element getChildNode(Element parentNode, String parentSegment, String segment) throws AtlasException {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for child node '" + segment + "' in parent '" + parentSegment + "': "
                    + writeDocumentToString(true, parentNode));
        }
        if (parentNode == null) {
            return null;
        }
        String cleanedSegment = PathUtil.cleanPathSegment(segment);
        String namespaceAlias = PathUtil.getNamespace(segment);
        if (namespaceAlias != null && !"".equals(namespaceAlias)) {
            cleanedSegment = namespaceAlias + ":" + cleanedSegment;
        }
        List children = getChildrenWithName(cleanedSegment, parentNode);
        if (logger.isDebugEnabled()) {
            logger.debug("Found " + children.size() + " children in '" + parentSegment + "' with the name '"
                    + cleanedSegment + "'.");
        }
        Element childNode = children.size() > 0 ? children.get(0) : null;
        if (children.size() > 0 && PathUtil.isCollectionSegment(segment)) {
            int index = PathUtil.indexOfSegment(segment);
            childNode = null;
            if (children.size() > index) {
                childNode = children.get(index);
            }
        }
        if (logger.isDebugEnabled()) {
            if (childNode == null) {
                logger.debug("Could not find child node '" + segment + "' in parent '" + parentSegment + "'.");
            } else {
                logger.debug("Found child node '" + segment + "' in parent '" + parentSegment + "', class: "
                        + childNode.getClass().getName() + ", node: " + writeDocumentToString(true, childNode));
            }
        }
        return childNode;
    }

    public Element createParentNode(Element parentNode, String parentSegment, String segment) throws AtlasException {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating parent node '" + segment + "' under previous parent '" + parentSegment + "'.");
        }
        Element childNode = null;
        String cleanedSegment = PathUtil.cleanPathSegment(segment);
        if (PathUtil.isCollectionSegment(segment)) {
            int index = PathUtil.indexOfSegment(segment);
            String namespaceAlias = PathUtil.getNamespace(segment);
            if (namespaceAlias != null && !"".equals(namespaceAlias)) {
                cleanedSegment = namespaceAlias + ":" + cleanedSegment;
            }

            List children = getChildrenWithName(cleanedSegment, parentNode);

            if (children.size() < (index + 1)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Child Element Array is too small, resizing to accomodate index: " + index
                            + ", current array: " + children);
                }
                // if our array doesn't have index + 1 items in it, add objects until we have
                // the index available
                while (children.size() < (index + 1)) {
                    Element child = (Element) parentNode.appendChild(createElement(segment));
                    children.add(child);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Child Element Array after resizing: " + children);
                }
            }
            children = getChildrenWithName(cleanedSegment, parentNode);
            childNode = (Element) children.get(index);
        } else {
            childNode = (Element) parentNode.appendChild(createElement(segment));
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Parent Node '" + parentSegment + "' after adding child parent node '" + segment + "':"
                    + writeDocumentToString(true, parentNode));
        }
        return childNode;
    }

    public Element createElement(String segment) throws AtlasException {
        String cleanedSegment = PathUtil.cleanPathSegment(segment);
        if (logger.isDebugEnabled()) {
            logger.debug("Creating element for segment '" + segment + "'.");
        }
        if (this.enableElementNamespaces) {
            if (logger.isDebugEnabled()) {
                logger.debug("Element namespaces are enabled, determining namespace.");
            }
            String namespaceAlias = null;
            String namespaceUri = null;
            if (PathUtil.isNamespaceSegment(segment)) {
                namespaceAlias = PathUtil.getNamespace(segment);
                namespaceUri = this.namespaces.get(namespaceAlias);
                logger.debug("Parsed namespace alias '" + namespaceAlias + "', from segment '" + segment
                        + "', namespaceUri: " + namespaceUri + ", known namespaces: " + this.namespaces);
            }
            if (!this.ignoreMissingNamespaces && namespaceUri == null) {
                throw new AtlasException("Cannot find namespace URI for element: '" + segment
                        + "', available namespaces: " + this.namespaces);
            }
            if (namespaceUri != null) {
                return document.createElementNS(namespaceUri, namespaceAlias + ":" + cleanedSegment);
            }
        }
        return document.createElement(cleanedSegment);
    }

    public static List getChildrenWithName(String name, Element parentNode) {
        List children = new LinkedList<>();
        if (parentNode == null) {
            return children;
        }
        NodeList nodeChildren = parentNode.getChildNodes();
        for (int i = 0; i < nodeChildren.getLength(); i++) {
            Node child = nodeChildren.item(i);
            if ((child instanceof Element) && child.getNodeName().equals(name)) {
                children.add((Element) child);
            }
        }
        return children;
    }

    public String convertValue(Field field) throws AtlasException {
        FieldType type = field.getFieldType();
        Object originalValue = field.getValue();
        String value = originalValue != null ? String.valueOf(originalValue) : null;
        if (logger.isDebugEnabled()) {
            String valueClass = originalValue == null ? "null" : originalValue.getClass().getName();
            logger.debug("Converted field value. Type: " + type + ", originalValue: " + originalValue + "(" + valueClass
                    + "), to: '" + value + "'.");
        }
        return value;
    }

    private static Document createDocument(Map namespaces, String seedDocument) throws AtlasException {
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            if (namespaces != null && !namespaces.isEmpty()) {
                documentBuilderFactory.setNamespaceAware(true);
            }
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            if (seedDocument != null) {
                Document document = documentBuilder.parse(new ByteArrayInputStream(seedDocument.getBytes("UTF-8")));

                Element rootNode = document.getDocumentElement();

                // extract namespaces from seed document
                NamedNodeMap attributes = rootNode.getAttributes();
                if (attributes != null) {
                    for (int i = 0; i < attributes.getLength(); i++) {
                        Node n = attributes.item(i);
                        String nodeName = n.getNodeName();
                        if (nodeName != null && nodeName.startsWith("xmlns")) {
                            String namespaceAlias = "";
                            if (nodeName.contains(":")) {
                                namespaceAlias = nodeName.substring(nodeName.indexOf(":") + 1);
                            }
                            if (!namespaces.containsKey(namespaceAlias)) {
                                namespaces.put(namespaceAlias, n.getNodeValue());
                            }
                        }
                    }
                }

                // rewrite root element to contain user-specified namespaces
                if (namespaces.size() > 0) {
                    Element oldRootNode = rootNode;
                    rootNode = (Element) oldRootNode.cloneNode(true);
                    addNamespacesToElement(rootNode, namespaces);
                    document.removeChild(oldRootNode);
                    document.appendChild(rootNode);
                }

                return document;
            }
            return documentBuilder.newDocument();
        } catch (Exception e) {
            throw new AtlasException(e);
        }
    }

    public static String writeDocumentToString(boolean stripSpaces, Node node) throws AtlasException {
        try {
            if (node == null) {
                return "";
            }
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(node), new StreamResult(writer));

            String result = writer.getBuffer().toString();
            if (stripSpaces) {
                result = result.replaceAll("\n|\r", "");
                result = result.replaceAll("> *?<", "><");
            }
            return result;
        } catch (Exception e) {
            throw new AtlasException(e);
        }
    }

    public Document getDocument() {
        return document;
    }

    public boolean isEnableElementNamespaces() {
        return enableElementNamespaces;
    }

    public void setEnableElementNamespaces(boolean enableElementNamespaces) {
        this.enableElementNamespaces = enableElementNamespaces;
    }

    public boolean isEnableAttributeNamespaces() {
        return enableAttributeNamespaces;
    }

    public void setEnableAttributeNamespaces(boolean enableAttributeNamespaces) {
        this.enableAttributeNamespaces = enableAttributeNamespaces;
    }

    public boolean isIgnoreMissingNamespaces() {
        return ignoreMissingNamespaces;
    }

    public void setIgnoreMissingNamespaces(boolean ignoreMissingNamespaces) {
        this.ignoreMissingNamespaces = ignoreMissingNamespaces;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy