/* * DSS - Digital Signature Services * * Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel * * Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" 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. * * DSS 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 * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.validation102853.xml; import java.io.ByteArrayOutputStream; 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.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; 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; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.DSSXMLUtils; import eu.europa.ec.markt.dss.NamespaceContextMap; import eu.europa.ec.markt.dss.exception.DSSException; /** * This class encapsulates an org.w3c.dom.Document. Its integrates the ability to execute XPath queries on XML * documents. * * @author bielecro */ public class XmlDom { private static final Logger LOG = LoggerFactory.getLogger(XmlDom.class); public static final String NAMESPACE = "http://dss.markt.ec.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); } 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); } // System.out.println(""); // System.out.println("--> " + formatedXPath); // System.out.println("--> " + stringBuilder.toString()); 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 (DSSUtils.isBlank(format)) { format = "yyyy-MM-dd"; } if (DSSUtils.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(); DSSXMLUtils.printDocument(rootElement, byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); } return DSSUtils.EMPTY_BYTE_ARRAY; } @Override public String toString() { if (rootElement != null) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DSSXMLUtils.printDocument(rootElement, byteArrayOutputStream); return DSSUtils.getUtf8String(byteArrayOutputStream.toByteArray()); } return super.toString(); } public Element getRootElement() { return rootElement; } }