eu.europa.esig.dss.XmlDom.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.esig.dss.XmlDom.java

Source

/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 *
 * This file is part of the "DSS - Digital Signature Services" project.
 *
 * 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 eu.europa.esig.dss;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

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 javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
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;

/**
 * This class encapsulates an org.w3c.dom.Document. Its integrates the ability to execute XPath queries on XML
 * documents.
 */
public class XmlDom {

    private static final Logger LOG = LoggerFactory.getLogger(XmlDom.class);

    public static final String NAMESPACE = "http://dss.esig.europa.eu/validation/diagnostic";

    private static final String NS_PREFIX = "dss";

    private static final XPathFactory factory = XPathFactory.newInstance();

    private static final NamespaceContextMap nsContext;

    private static final Map<String, String> namespaces;

    static {

        namespaces = new HashMap<String, String>();
        namespaces.put(NS_PREFIX, NAMESPACE);
        nsContext = new NamespaceContextMap();
        nsContext.registerNamespace(NS_PREFIX, NAMESPACE);
    }

    public final Element rootElement;

    String nameSpace;

    public XmlDom(final Document document) {

        this.rootElement = document.getDocumentElement();
        nameSpace = rootElement.getNamespaceURI();
    }

    public XmlDom(final Element element) {

        this.rootElement = element;
    }

    private static XPathExpression createXPathExpression(final String xpathString) {

        final XPath xpath = factory.newXPath();
        xpath.setNamespaceContext(nsContext);
        try {

            final XPathExpression expr = xpath.compile(xpathString);
            return expr;
        } catch (XPathExpressionException ex) {

            throw new RuntimeException(ex);
        }
    }

    private static NodeList getNodeList(final Node xmlNode, final String xpathString) {

        try {

            final XPathExpression expr = createXPathExpression(xpathString);
            return (NodeList) expr.evaluate(xmlNode, XPathConstants.NODESET);
        } catch (XPathExpressionException e) {

            throw new RuntimeException(e);
        }
    }

    /**
     * The list of elements corresponding the given XPath query and parameters.
     *
     * @param xPath
     * @param params
     * @return
     */
    public List<XmlDom> getElements(final String xPath, final Object... params) {

        try {

            String xPath_ = format(xPath, params);

            NodeList nodeList = getNodeList(rootElement, xPath_);
            List<XmlDom> list = new ArrayList<XmlDom>();
            for (int ii = 0; ii < nodeList.getLength(); ii++) {

                Node node = nodeList.item(ii);
                if ((node != null) && (node.getNodeType() == Node.ELEMENT_NODE)) {

                    list.add(new XmlDom((Element) node));
                }
            }
            return list;
        } catch (Exception e) {

            String message = "XPath error: '" + xPath + "'.";
            throw new DSSException(message, e);
        }
    }

    public XmlDom getElement(final String xPath, final Object... params) {

        try {

            String xPath_ = format(xPath, params);

            NodeList nodeList = getNodeList(rootElement, xPath_);
            for (int ii = 0; ii < nodeList.getLength(); ii++) {

                Node node = nodeList.item(ii);
                if ((node != null) && (node.getNodeType() == Node.ELEMENT_NODE)) {

                    return new XmlDom((Element) node);
                }
            }
            return null;
        } catch (Exception e) {

            String message = "XPath error: '" + xPath + "'.";
            throw new DSSException(message, e);
        }
    }

    /**
     * @param xPath
     * @param params
     * @return
     */
    private static String format(final String xPath, final Object... params) {

        String formattedXPath;
        if (params.length > 0) {

            formattedXPath = String.format(xPath, params);
        } else {

            formattedXPath = xPath;
        }
        formattedXPath = addNamespacePrefix(formattedXPath);
        return formattedXPath;
    }

    private static String addNamespacePrefix(final String formatedXPath) {

        if (formatedXPath.startsWith("/dss:") || formatedXPath.startsWith("./dss:")) {

            // Already formated.
            return formatedXPath;
        }
        String formatedXPath_ = formatedXPath;
        CharSequence from = "//";
        CharSequence to = "{#double}/";
        boolean special = formatedXPath_.indexOf("//") != -1;
        if (special) {
            formatedXPath_ = formatedXPath_.replace(from, to);
        }
        StringTokenizer tokenizer = new StringTokenizer(formatedXPath_, "/");

        StringBuilder stringBuilder = new StringBuilder();

        while (tokenizer.hasMoreTokens())

        {

            String token = tokenizer.nextToken();

            final boolean isDot = ".".equals(token);
            final boolean isCount = "count(".equals(token) || "count(.".equals(token);
            final boolean isDoubleDot = "..".equals(token);
            final boolean isAt = token.startsWith("@");
            final boolean isText = token.equals("text()");
            final boolean isDoubleSlash = token.equals("{#double}");
            final String slash = isDot || isCount || isDoubleSlash ? "" : "/";
            String prefix = isDot || isCount || isDoubleDot || isAt || isText || isDoubleSlash ? "" : "dss:";

            stringBuilder.append(slash).append(prefix).append(token);
        }

        String normalizedXPath = stringBuilder.toString();
        if (special) {
            normalizedXPath = normalizedXPath.replace(to, from);
        }
        return normalizedXPath;
    }

    /**
     * This method never returns null.
     *
     * @param xPath
     * @param params
     * @return {@code String} value or empty string
     */
    public String getValue(final String xPath, final Object... params) {

        String xPath_ = format(xPath, params);

        NodeList nodeList = getNodeList(rootElement, xPath_);
        if (nodeList.getLength() == 1) {
            Node node = nodeList.item(0);
            if (node.getNodeType() != Node.ELEMENT_NODE) {
                String value = nodeList.item(0).getTextContent();
                return value.trim();
            }
        }
        return "";
    }

    public int getIntValue(final String xPath, final Object... params) {

        String value = getValue(xPath, params);
        try {

            return Integer.parseInt(value);
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    public long getLongValue(final String xPath, final Object... params) {

        String value = getValue(xPath, params);
        try {

            value = value.trim();
            return Long.parseLong(value);
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    public boolean getBoolValue(final String xPath, final Object... params) {

        String value = getValue(xPath, params);
        if (value.equals("true")) {
            return true;

        } else if (value.isEmpty() || value.equals("false")) {

            return false;
        }
        throw new DSSException("Expected values are: true, false and not '" + value + "'.");
    }

    public long getCountValue(final String xPath, final Object... params) {

        String xpathString = format(xPath, params);
        try {

            XPathExpression xPathExpression = createXPathExpression(xpathString);
            Double number = (Double) xPathExpression.evaluate(rootElement, XPathConstants.NUMBER);
            return number.intValue();
        } catch (XPathExpressionException e) {

            throw new RuntimeException(e);
        }
    }

    public boolean exists(final String xPath, final Object... params) {

        XmlDom element = getElement(xPath, params);
        return element != null;
    }

    public Date getTimeValue(final String xPath, final Object... params) {

        String value = getValue(xPath, params);
        return DSSUtils.parseDate(value);
    }

    public Date getTimeValueOrNull(final String xPath, final Object... params) {

        String value = getValue(xPath, params);
        if (value.isEmpty()) {
            return null;
        }
        return DSSUtils.parseDate(value);
    }

    public String getText() {

        try {
            if (rootElement != null) {

                return rootElement.getTextContent().trim();
            }
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * The name of this node, depending on its type;
     *
     * @return
     */
    public String getName() {

        return rootElement.getNodeName();
    }

    /**
     * Retrieves an attribute value by name.
     *
     * @param attributeName
     * @return
     */
    public String getAttribute(final String attributeName) {

        return rootElement.getAttribute(attributeName);
    }

    /**
     * Retrieves an attribute value by name.
     *
     * @return
     */
    public NamedNodeMap getAttributes() {

        return rootElement.getAttributes();
    }

    /**
     * Converts the list of {@code XmlDom} to {@code List} of {@code String}. The children of the node are not taken
     * into account.
     *
     * @param xmlDomList the list of {@code XmlDom} to convert
     * @return converted {@code List} of {@code String}.
     */
    public static List<String> convertToStringList(final List<XmlDom> xmlDomList) {

        final List<String> stringList = new ArrayList<String>();
        for (final XmlDom xmlDom : xmlDomList) {

            stringList.add(xmlDom.getText());
        }
        return stringList;
    }

    /**
     * Converts the list of {@code XmlDom} to {@code Map} of {@code String}, {@code String}. The children of the node are not taken
     * into account.
     *
     * @param xmlDomList    the list of {@code XmlDom} to convert
     * @param attributeName the name of the attribute to use as value
     * @return converted {@code Map} of {@code String}, {@code String} corresponding to the element content and the attribute value.
     */
    public static Map<String, String> convertToStringMap(final List<XmlDom> xmlDomList,
            final String attributeName) {

        final Map<String, String> stringMap = new HashMap<String, String>();
        for (final XmlDom xmlDom : xmlDomList) {

            final String key = xmlDom.getText();
            final String value = xmlDom.getAttribute(attributeName);
            stringMap.put(key, value);
        }
        return stringMap;
    }

    /**
     * Converts the list of {@code XmlDom} to {@code Map} of {@code String}, {@code Date}. The children of the node are not taken
     * into account. If a problem is encountered during the conversion the pair key, value is ignored and a warning is logged.
     *
     * @param xmlDomList    the list of {@code XmlDom} to convert
     * @param attributeName the name of the attribute to use as value
     * @return converted {@code Map} of {@code String}, {@code Date} corresponding to the element content and the attribute value.
     */
    public static Map<String, Date> convertToStringDateMap(final List<XmlDom> xmlDomList,
            final String attributeName) {

        final Map<String, Date> stringMap = new HashMap<String, Date>();
        for (final XmlDom xmlDom : xmlDomList) {

            final String key = xmlDom.getText();
            final String dateString = xmlDom.getAttribute(attributeName);
            String format = xmlDom.getAttribute("Format");
            if (StringUtils.isBlank(format)) {
                format = "yyyy-MM-dd";
            }
            if (StringUtils.isBlank(dateString)) {

                LOG.warn(String.format("The date is not defined for key '%s'!", key));
                continue;
            }
            final Date date;
            try {
                date = DSSUtils.parseDate(format, dateString);
            } catch (DSSException e) {

                LOG.warn("The date conversion is not possible.", e);
                continue;
            }
            stringMap.put(key, date);
        }
        return stringMap;
    }

    public byte[] toByteArray() {

        if (rootElement != null) {

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            printDocument(rootElement, byteArrayOutputStream, false);
            return byteArrayOutputStream.toByteArray();
        }
        return DSSUtils.EMPTY_BYTE_ARRAY;
    }

    @Override
    public String toString() {

        if (rootElement != null) {

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            printDocument(rootElement, byteArrayOutputStream, false);
            return getUtf8String(byteArrayOutputStream.toByteArray());
        }
        return super.toString();
    }

    /**
     * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-8 charset.
     *
     * @param bytes The bytes to be decoded into characters
     * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-8 charset,
     * or <code>null</code> if the input byte array was <code>null</code>.
     * @throws IllegalStateException Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen since the
     *                               charset is required.
     */
    private static String getUtf8String(byte[] bytes) {

        if (bytes == null) {
            return null;
        }
        try {
            return new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method writes formatted {@link org.w3c.dom.Node} to the outputStream.
     *
     * @param node
     * @param out
     */
    private static void printDocument(final Node node, final OutputStream out, final boolean raw) {

        try {

            final TransformerFactory tf = TransformerFactory.newInstance();
            final Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            if (!raw) {

                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
            }
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

            final DOMSource xmlSource = new DOMSource(node);
            final OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
            final StreamResult outputTarget = new StreamResult(writer);
            transformer.transform(xmlSource, outputTarget);
        } catch (Exception e) {
            throw new DSSException(e);
        }

    }

    public Element getRootElement() {
        return rootElement;
    }
}