org.ojbc.util.xml.XmlUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.ojbc.util.xml.XmlUtils.java

Source

/*
 * Unless explicitly acquired and licensed from Licensor under another license, the contents of
 * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent
 * versions as allowed by the RPL, and You may not copy or use this file in either source code
 * or executable form, except in compliance with the terms and conditions of the RPL
 *
 * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH
 * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language
 * governing rights and limitations under the RPL.
 *
 * http://opensource.org/licenses/RPL-1.5
 *
 * Copyright 2012-2015 Open Justice Broker Consortium
 */
package org.ojbc.util.xml;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
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.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;
import org.joda.time.format.ISODateTimeFormat;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.SAXException;

/**
 * XML Utility class. 
 * 
 * This class provides assistance with Xpath searching, document creation and NIEM document validation.
 * It works with the namespaces and prefixes provided by OjbcNamespaceContext. 
 * 
 * When creating DOM documents, this class should always be used first if possible.
 * 
 * The test class provides examples on how to properly use the methods provided.
 * 
 */
public class XmlUtils {

    public static final OjbcNamespaceContext OJBC_NAMESPACE_CONTEXT = new OjbcNamespaceContext();

    public static final DateTime parseXmlDateTime(String dateTime) {
        if (dateTime == null || dateTime.trim().equals("")) {
            return null;
        }
        DateTimeParser[] parsers = new DateTimeParser[] {
                DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").getParser(),
                ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed().getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS").getParser(),
                ISODateTimeFormat.dateTime().withOffsetParsed().getParser(), };
        DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder();
        dateTimeFormatterBuilder.append(null, parsers);
        return dateTimeFormatterBuilder.toFormatter().parseDateTime(dateTime);
    }

    /**
     * This method accepts an XML date string and will return a JodaTime object.
     * It will need to be updated to support timezones.
     * 
     * @param date
     * @return
     */
    public static final DateTime parseXmlDate(String date) {
        if (date == null || date.trim().equals("")) {
            return null;
        }
        DateTimeParser[] parsers = new DateTimeParser[] { DateTimeFormat.forPattern("yyyy-MM-dd").getParser(),
                ISODateTimeFormat.dateTimeNoMillis().withOffsetParsed().getParser(), };
        DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder();
        dateTimeFormatterBuilder.append(null, parsers);
        return dateTimeFormatterBuilder.toFormatter().parseDateTime(date);
    }

    /**
     * Compares two DOM nodes for (deep) equality.
     * @param n1
     * @param n2
     * @return whether the nodes are equal
     */
    public static final boolean compare(Node n1, Node n2) {
        boolean ret = true;
        ret &= n1.getLocalName().equals(n2.getLocalName());
        ret &= n1.getNamespaceURI().equals(n2.getNamespaceURI());
        String text = n1.getTextContent();
        if (text != null) {
            ret &= text.equals(n2.getTextContent());
        }
        NodeList children = n1.getChildNodes();
        ret &= (children.getLength() == n2.getChildNodes().getLength());
        for (int i = 0; ret && i < children.getLength(); i++) {
            final Node child = children.item(i);
            if (child instanceof Element) {
                ret &= compare(child, n2.getChildNodes().item(i));
            }
        }
        return ret;
    }

    /**
     * Print the specified XML DOM node to System.out
     * 
     * @param n
     *            the node to print
     * @throws Exception
     */
    public static final void printNode(Node n) throws Exception {
        printNode(n, System.out);
    }

    /**
     * Original intent was to represent root node as a string without 
     * any xml comments at the top 
     */
    public static String getRootNodeAsString(String fileClasspath) throws Exception {

        File file = new File(fileClasspath);
        Document doc = XmlUtils.parseFileToDocument(file);
        Element rootElement = doc.getDocumentElement();
        String sRoot = XmlUtils.getStringFromNode(rootElement);

        return sRoot;
    }

    public static String getStringFromNode(Node node) throws Exception {

        StringWriter writer = new StringWriter();
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.transform(new DOMSource(node), new StreamResult(writer));
        String xml = writer.toString();

        return xml;
    }

    /**
     * Print the specified XML DOM node to the specified output stream
     * 
     * @param n
     *            the node to print
     * @param os
     *            the output stream to print to
     * @throws Exception
     */
    public static void printNode(Node n, OutputStream os) throws Exception {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();
        t.setOutputProperty(OutputKeys.INDENT, "yes");
        t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        t.transform(new DOMSource(n), new StreamResult(os));
    }

    /**
     * Search the context node for a node that matches the specified xpath
     * 
     * @param context
     *            the node that's the context for the xpath
     * @param xPath
     *            the xpath query
     * @return the matching node, or null if no match
     * @throws Exception
     */
    public static final Node xPathNodeSearch(Node context, String xPath) throws Exception {
        if (xPath == null) {
            return null;
        }
        XPath xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(OJBC_NAMESPACE_CONTEXT);
        XPathExpression expression = xpath.compile(xPath);
        return (Node) expression.evaluate(context, XPathConstants.NODE);
    }

    /**
     * Search the context node for a String that matches the specified xpath
     * 
     * @param context
     *            the node that's the context for the xpath
     * @param xPath
     *            the xpath query
     * @return the matching string, or null if no match
     * @throws Exception
     */
    public static final String xPathStringSearch(Node context, String xPath) throws Exception {
        if (xPath == null) {
            return null;
        }
        XPath xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(OJBC_NAMESPACE_CONTEXT);
        XPathExpression expression = xpath.compile(xPath);
        return StringUtils.trimToNull((String) expression.evaluate(context, XPathConstants.STRING));
    }

    /**
     * Search the context node for a node set that matches the specified xpath
     * @param context the node that's the context for the xpath
     * @param xPath the xpath query
     * @return the matching node, or null if no match
     * @throws Exception
     */
    public static final NodeList xPathNodeListSearch(Node context, String xPath) throws Exception {
        if (xPath == null) {
            return null;
        }
        XPath xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(OJBC_NAMESPACE_CONTEXT);
        XPathExpression expression = xpath.compile(xPath);
        return (NodeList) expression.evaluate(context, XPathConstants.NODESET);

    }

    /**
     * Returns true if the node specified by the xpath exists in the context, and false otherwise
     * 
     * @param context
     *            the context node from which to apply the xpath
     * @param xPath
     *            the query
     * @return true if the specified node exists, false otherwise
     * @throws Exception
     */
    public static final boolean nodeExists(Node context, String xPath) throws Exception {
        return xPathNodeSearch(context, xPath) != null;
    }

    /**
     * Create a new element with the specified namespace and name, append it to the specified parent, and return it
     * 
     * @param parent
     *            the parent to contain the new element
     * @param ns
     *            the namespace URI of the new element
     * @param elementName
     *            the name of the new element
     * @return the new element
     */
    public static final Element appendElement(Element parent, String ns, String elementName) {
        Document doc = parent.getOwnerDocument();
        Element ret = doc.createElementNS(ns, elementName);
        parent.appendChild(ret);
        ret.setPrefix(OJBC_NAMESPACE_CONTEXT.getPrefix(ns));
        return ret;
    }

    /**
     * Add an attribute to the specified element
     * @param parent the element to which we add the attribute
     * @param ns the namespace of the attribute
     * @param attributeName the name of the attribute
     * @param value the value of the attribute
     * @return the attribute
     */
    public static final Attr addAttribute(Element parent, String ns, String attributeName, String value) {
        Document doc = parent.getOwnerDocument();
        Attr ret = doc.createAttributeNS(ns, attributeName);
        ret.setTextContent(value);
        ret.setPrefix(OJBC_NAMESPACE_CONTEXT.getPrefix(ns));
        parent.setAttributeNode(ret);
        return ret;
    }

    /**
     * if textValue is not blank, create a new element under the parent element with the namespace and 
     * elementName, then set the textContent of the element to the textValue. 
     * 
     * if textValue is blank,  do nothing. 
     * 
     * @param parent
     * @param namespace
     * @param elementName
     * @param textValue
     */
    public static final void appendTextElement(Element parent, String namespace, String elementName,
            String textValue) {
        if (StringUtils.isNotBlank(textValue)) {
            Element personEthnicityText = XmlUtils.appendElement(parent, namespace, elementName);
            personEthnicityText.setTextContent(textValue);
        }
    }

    /**
      * Create a new element with the specified namespace and name, insert it under the specified parent but before the specified sibling, and return it
      * 
      * @param parent
      *            the parent
      * @param sibling
      *            the parent's current child, in front of which the new element is to be inserted
      * @param ns
      *            the namespace URI of the new element
      * @param elementName
      *            the name of the new element
      * @return the new element
      */
    public static final Element insertElementBefore(Element parent, Element sibling, String ns,
            String elementName) {
        Document doc = parent.getOwnerDocument();
        Element ret = doc.createElementNS(ns, elementName);
        ret.setPrefix(OJBC_NAMESPACE_CONTEXT.getPrefix(ns));
        parent.insertBefore(ret, sibling);
        return ret;
    }

    /**
     * Validates a document against an IEPD, using the default IEPD resource resolver. See expanded method below for more detail.
     * 
     * @param niemSchemaContainerFolderName
     *            the name of the folder that contains all of the SSGT-generated schemas. Typically "NIEM_x.y" or "niem".
     * @param rootSchemaFileName
     *            the name of the document/exchange schema
     * @param d
     *            the document to validate
     * @param d
     *            the document to validate
     * @return the document that was validated
     * @throws Exception
     *             if the document is not valid
     */
    public static final Document validateInstance(String iepdRootPath, String niemSchemaContainerFolderName,
            String rootSchemaFileName, Document d) throws Exception {

        return validateInstance(iepdRootPath, niemSchemaContainerFolderName, rootSchemaFileName,
                new ArrayList<String>(), d);
    }

    /**
     * Validates a document against an IEPD, using the default IEPD resource resolver. See expanded method below for more detail.
     * 
     * @param niemSchemaContainerFolderName
     *            the name of the folder that contains all of the SSGT-generated schemas. Typically "NIEM_x.y" or "niem".
     * @param rootSchemaFileName
     *            the name of the document/exchange schema
     * @param additionalSchemaRelativePaths
     *            additional schema paths, within iepdRootPath
     * @param d
     *            the document to validate
     * @return the document that was validated
     * @throws Exception
     *             if the document is not valid
     */
    public static final Document validateInstance(String iepdRootPath, String niemSchemaContainerFolderName,
            String rootSchemaFileName, List<String> additionalSchemaRelativePaths, Document d) throws Exception {
        return validateInstance(iepdRootPath, rootSchemaFileName, additionalSchemaRelativePaths, d,
                new IEPDResourceResolver(niemSchemaContainerFolderName, iepdRootPath));
    }

    public static final Document validateInstanceWithAbsoluteClasspaths(String rootNodeXsdFilePath,
            List<String> supportingXsdDirPathList, Document xmlDoc) throws Exception {
        return validateInstanceWithAbsoluteClasspaths(rootNodeXsdFilePath, supportingXsdDirPathList,
                new ArrayList<String>(), xmlDoc);
    }

    public static final Document validateInstanceWithAbsoluteClasspaths(String rootNodeXsdFilePath,
            List<String> supportingXsdDirPathList, List<String> additionalSchemaRelativePaths, Document xmlDoc)
            throws Exception {

        List<String> resourcePathList = new ArrayList<String>();
        resourcePathList.add(rootNodeXsdFilePath);
        resourcePathList.addAll(supportingXsdDirPathList);

        IEPDFullPathResourceResolver fullPathResolver = new IEPDFullPathResourceResolver(resourcePathList);

        return validateInstance(rootNodeXsdFilePath, xmlDoc, fullPathResolver, additionalSchemaRelativePaths);
    }

    public static Document validateInstance(String rootXsdPath, Document docXmlToValidate,
            IEPDFullPathResourceResolver fullPathResolver) throws Exception {
        return validateInstance(rootXsdPath, docXmlToValidate, fullPathResolver, new ArrayList<String>());
    }

    public static Document validateInstance(String rootXsdPath, Document docXmlToValidate,
            IEPDFullPathResourceResolver fullPathResolver, List<String> additionalSchemaRelativePaths)
            throws Exception {

        List<String> schemaPaths = new ArrayList<String>();
        schemaPaths.add(rootXsdPath);
        schemaPaths.addAll(additionalSchemaRelativePaths);

        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        schemaFactory.setResourceResolver(fullPathResolver);

        Source[] sources = new Source[schemaPaths.size()];
        int i = 0;
        for (String path : schemaPaths) {
            sources[i++] = new StreamSource(XmlUtils.class.getClassLoader().getResourceAsStream(path));
        }

        Schema schema = schemaFactory.newSchema(sources);

        Validator v = schema.newValidator();

        try {
            v.validate(new DOMSource(docXmlToValidate));

        } catch (Exception e) {
            try {
                e.printStackTrace();
                System.err.println("FAILED Input Doc: \n");
                XmlUtils.printNode(docXmlToValidate);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw e;
        }
        return docXmlToValidate;
    }

    /**
     * Convenience method doesn't require a null param for the niem dir string
     */
    public static final Document validateInstance(String iepdRootPath, String rootSchemaFileName, Document doc,
            LSResourceResolver iepdResourceResolver) throws Exception {

        if (!iepdRootPath.endsWith("/")) {
            iepdRootPath = iepdRootPath + "/";
        }
        String fullRootSchemaPath = iepdRootPath + rootSchemaFileName;

        List<String> schemaPaths = new ArrayList<String>();
        schemaPaths.add(fullRootSchemaPath);

        validateInstance(doc, iepdResourceResolver, schemaPaths);

        return doc;
    }

    /**
     * Convenience method doesn't require a null param for the niem dir string
     */
    public static final Document validateInstance(String iepdRootPath, String rootSchemaFileName,
            List<String> additionalSchemaRelativePaths, Document d, LSResourceResolver iepdResourceResolver)
            throws Exception {

        if (!iepdRootPath.endsWith("/")) {
            iepdRootPath = iepdRootPath + "/";
        }
        String fullRootSchemaPath = iepdRootPath + rootSchemaFileName;

        List<String> schemaPaths = new ArrayList<String>();
        schemaPaths.add(fullRootSchemaPath);
        schemaPaths.addAll(additionalSchemaRelativePaths);

        validateInstance(d, iepdResourceResolver, schemaPaths);

        return d;
    }

    /**
      * Validate a document against an IEPD. Note that this does not require the xsi namespace location attributes to be set in the instance.
      * 
      * @param schemaPathList
      *            the paths to all schemas necessary to validate the instance; this is the equivalent of specifying these schemas in an xsi:schemaLocation attribute in the instance
      *                       
      * @param Never_Used_TODO_Remove 
      * 
      *            
      * @param rootSchemaFileName
      *            the name of the document/exchange schema
      * @param d
      *            the document to validate
      * @param iepdResourceResolver
      *            the resource resolver to use
      * @return the document that was validated
      * @throws Exception
      *             if the document is not valid
      */
    public static void validateInstance(Document d, LSResourceResolver iepdResourceResolver,
            List<String> schemaPathList) throws SAXException, Exception {
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        schemaFactory.setResourceResolver(iepdResourceResolver);

        List<Source> sourceList = new ArrayList<Source>();

        for (String schemaPath : schemaPathList) {

            InputStream schemaInStream = XmlUtils.class.getClassLoader().getResourceAsStream(schemaPath);

            StreamSource schemaStreamSource = new StreamSource(schemaInStream);

            sourceList.add(schemaStreamSource);
        }

        Source[] schemaSourcesArray = sourceList.toArray(new Source[] {});

        try {
            Schema schema = schemaFactory.newSchema(schemaSourcesArray);

            Validator validator = schema.newValidator();

            validator.validate(new DOMSource(d));

        } catch (Exception e) {
            try {
                e.printStackTrace();
                System.err.println("Input document:");
                XmlUtils.printNode(d);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            throw e;
        }
    }

    /**
     * This method will validate a non-niem xml instance.
     * 
     * @param xsdPath - this is a relative path to an xsd, typically in OJB_Utilies or in the enclosing project
     * @param xml - This is the XML document as a string
     * @throws Exception - typically a validation exception or a file path exception
     */

    public static void validateInstanceNonNIEMXsd(String xsdPath, String xml) throws Exception {

        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = factory.newSchema(new File(xsdPath));
        Validator validator = schema.newValidator();
        validator.validate(new StreamSource(new StringReader(xml)));
    }

    /**
     * Read the contents of the specified file into a DOM document
     * @param f the input XML file
     * @return the document
     * @throws Exception
     */
    public static final Document parseFileToDocument(File f) throws Exception {
        DocumentBuilderFactory docBuilderFact = DocumentBuilderFactory.newInstance();
        docBuilderFact.setNamespaceAware(true);
        DocumentBuilder docBuilder = docBuilderFact.newDocumentBuilder();
        Document document = docBuilder.parse(f);
        return document;
    }

    public static Source getDomSourceIgnoringDtd(String xmlContents)
            throws ParserConfigurationException, SAXException, IOException {

        InputStream inputStream = new ByteArrayInputStream(xmlContents.getBytes());

        Source source = getDomSourceIgnoringDtd(inputStream);

        return source;
    }

    public static Source getDomSourceIgnoringDtd(File file)
            throws FileNotFoundException, SAXException, IOException, ParserConfigurationException {

        InputStream inputStream = new FileInputStream(file);

        Source source = getDomSourceIgnoringDtd(inputStream);

        return source;
    }

    public static Source getDomSourceIgnoringDtd(InputStream inputStream)
            throws ParserConfigurationException, SAXException, IOException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // stop the loading of DTD files
        factory.setValidating(false);
        factory.setNamespaceAware(true);
        factory.setFeature("http://xml.org/sax/features/validation", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        DocumentBuilder docbuilder = factory.newDocumentBuilder();

        Document doc = docbuilder.parse(inputStream);

        Source domSource = new DOMSource(doc.getDocumentElement());

        return domSource;
    }

}