001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.util;
022    
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    import java.io.StringReader;
027    import java.io.StringWriter;
028    import java.lang.reflect.Method;
029    import java.nio.charset.Charset;
030    import java.util.ArrayList;
031    import java.util.List;
032    
033    import javax.xml.parsers.DocumentBuilder;
034    import javax.xml.parsers.DocumentBuilderFactory;
035    import javax.xml.parsers.ParserConfigurationException;
036    import javax.xml.transform.OutputKeys;
037    import javax.xml.transform.Templates;
038    import javax.xml.transform.Transformer;
039    import javax.xml.transform.TransformerException;
040    import javax.xml.transform.TransformerFactory;
041    import javax.xml.transform.dom.DOMSource;
042    import javax.xml.transform.stream.StreamResult;
043    import javax.xml.transform.stream.StreamSource;
044    import javax.xml.xpath.XPathConstants;
045    import javax.xml.xpath.XPathExpressionException;
046    import javax.xml.xpath.XPathFactory;
047    
048    import org.w3c.dom.Document;
049    import org.w3c.dom.Element;
050    import org.w3c.dom.Node;
051    import org.w3c.dom.NodeList;
052    import org.xml.sax.EntityResolver;
053    import org.xml.sax.ErrorHandler;
054    import org.xml.sax.SAXException;
055    
056    /**
057     * @author Franck WOLFF
058     */
059    public class DOM {
060    
061            protected static final String TO_STRING_XSL = 
062                    "<?xml version='1.0' encoding='UTF-8'?>" +
063                    "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" +
064                    "    <xsl:strip-space elements='*'/>" +
065                    "    <xsl:template match='/'>" +
066                    "        <xsl:copy-of select='*'/>" +
067                    "    </xsl:template>" +
068                    "</xsl:stylesheet>";
069    
070            private final ErrorHandler errorHandler;
071            
072            private DocumentBuilderFactory documentBuilderFactory = null;
073            private DocumentBuilderFactory validatingDocumentBuilderFactory = null;
074            private XPathFactory xPathFactory = null;
075            private Templates toStringTemplates = null;
076            
077            public DOM() {
078                    this(null);
079            }
080            
081            public DOM(ErrorHandler errorHandler) {
082                    this.errorHandler = errorHandler;
083            }
084            
085            protected DocumentBuilderFactory getDocumentBuilderFactory() {
086                    if (documentBuilderFactory == null) {
087                            try {
088                                    documentBuilderFactory = DocumentBuilderFactory.newInstance();
089            
090                                    documentBuilderFactory.setCoalescing(true);
091                                    documentBuilderFactory.setIgnoringComments(true);
092                            } catch (Exception e) {
093                                    throw new RuntimeException(e);
094                            }
095                    }
096                    return documentBuilderFactory;
097            }
098    
099            protected DocumentBuilderFactory getValidatingDocumentBuilderFactory() {
100                    if (validatingDocumentBuilderFactory == null) {
101                            try {
102                                    validatingDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
103                                    validatingDocumentBuilderFactory.setCoalescing(true);
104                                    validatingDocumentBuilderFactory.setIgnoringComments(true);
105                                    validatingDocumentBuilderFactory.setValidating(true);
106                                    validatingDocumentBuilderFactory.setIgnoringElementContentWhitespace(true);
107                            } catch (Exception e) {
108                                    throw new RuntimeException(e);
109                            }
110                    }
111                    return validatingDocumentBuilderFactory;
112            }
113    
114            protected XPathFactory getXPathFactory() {
115                    if (xPathFactory == null) {
116                            try {
117                                    xPathFactory = XPathFactory.newInstance();
118                            }
119                            catch (Exception e) {
120                                    try {
121                                            // Fallback to xalan for Google App Engine
122                                            Class<?> factoryClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.xpath.jaxp.XPathFactoryImpl");
123                                            Method m = factoryClass.getMethod("newInstance", String.class, String.class, ClassLoader.class);
124                                            xPathFactory = (XPathFactory)m.invoke(null, XPathFactory.DEFAULT_OBJECT_MODEL_URI, "org.apache.xpath.jaxp.XPathFactoryImpl", null);
125                                    }
126                                    catch (Exception f) {
127                                            throw new RuntimeException("XPathFactory could not be found", f);
128                                    }
129                            }
130                    }
131                    return xPathFactory;
132            }
133    
134            protected Templates getToStringTemplates() {
135                    if (toStringTemplates == null) {
136                            try {
137                                    toStringTemplates = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(TO_STRING_XSL)));
138                            } catch (Exception e) {
139                                    throw new RuntimeException(e);
140                            }
141                    }
142                    return toStringTemplates;
143            }
144    
145            public Document loadDocument(InputStream input) throws IOException, SAXException {
146                    try {
147                            return getDocumentBuilderFactory().newDocumentBuilder().parse(input);
148                    } catch (ParserConfigurationException e) {
149                            throw new RuntimeException(e);
150                    }
151            }
152            
153            public Document loadDocument(InputStream input, EntityResolver resolver) throws IOException, SAXException {
154                    try {
155                            DocumentBuilder builder = getValidatingDocumentBuilderFactory().newDocumentBuilder();
156                            builder.setEntityResolver(resolver);
157                            if (errorHandler != null)
158                                    builder.setErrorHandler(errorHandler);
159                            return builder.parse(input);
160                    } catch (ParserConfigurationException e) {
161                            throw new RuntimeException(e);
162                    }
163            }
164            
165            public void saveDocument(Document document, OutputStream output) throws TransformerException {
166                    Transformer transformer = TransformerFactory.newInstance().newTransformer();
167                    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
168                    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
169                    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
170                    transformer.setOutputProperty(OutputKeys.INDENT, "no");
171                    
172                    transformer.transform(new DOMSource(document), new StreamResult(output));
173            }
174            
175            public Document newDocument() {
176            return newDocument(null);
177            }
178            
179            public Document newDocument(String root) {
180                    try {
181                            Document document = getDocumentBuilderFactory().newDocumentBuilder().newDocument();
182                            document.setXmlVersion("1.0");
183                    document.setXmlStandalone(true);
184                    if (root != null)
185                                    newElement(document, root);
186                    return document;
187                    } catch (ParserConfigurationException e) {
188                            throw new RuntimeException(e);
189                    }
190            }
191            
192            public Document getDocument(Node node) {
193                    return (node instanceof Document ? (Document)node : node.getOwnerDocument());
194            }
195    
196            public Element newElement(Node parent, String name) {
197                    return newElement(parent, name, null);
198            }
199    
200            public Element newElement(Node parent, String name, String value) {
201                    Element element = getDocument(parent).createElement(name);
202                    parent.appendChild(element);
203                    if (value != null)
204                            element.setTextContent(value);
205                    return element;
206            }
207            
208            public String getNormalizedValue(Node node) {
209                    if (node == null)
210                            return null;
211                    if (node.getNodeType() == Node.ELEMENT_NODE) {
212                            StringBuilder sb = new StringBuilder();
213                            for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
214                                    if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null) {
215                                            String value = child.getNodeValue().trim();
216                                            if (value.length() > 0) {
217                                                    if (sb.length() > 0)
218                                                            sb.append(' ');
219                                                    sb.append(value);
220                                            }
221                                    }
222                            }
223                            return sb.toString();
224                    }
225                    return (node.getNodeValue() != null ? node.getNodeValue().trim() : null);
226            }
227    
228            public String setValue(Node node, String value) {
229                    if (node != null) {
230                            String previousValue = getNormalizedValue(node);
231                            switch (node.getNodeType()) {
232                            case Node.ELEMENT_NODE:
233                                    ((Element)node).setTextContent(value);
234                                    break;
235                            case Node.ATTRIBUTE_NODE:
236                            case Node.TEXT_NODE:
237                                    node.setNodeValue(value);
238                                    break;
239                            default:
240                                    throw new RuntimeException("Illegal node for write operations: " + node);
241                            }
242                            return previousValue;
243                    }
244                    return null;
245            }
246    
247            public Node selectSingleNode(Object context, String expression) throws XPathExpressionException {
248                    return (Node)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODE);
249            }
250    
251            public List<Node> selectNodes(Object context, String expression) throws XPathExpressionException {
252                    NodeList nodeList = (NodeList)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODESET);
253                    List<Node> nodes = new ArrayList<Node>(nodeList.getLength());
254                    for (int i = 0; i < nodeList.getLength(); i++)
255                            nodes.add(nodeList.item(i));
256                    return nodes;
257            }
258            
259            public String toString(Node node) {
260                    try {
261                            Transformer transformer = getToStringTemplates().newTransformer();
262                            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
263                            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
264                            transformer.setOutputProperty(OutputKeys.ENCODING, Charset.defaultCharset().name());
265                            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
266                            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
267                            
268                            StringWriter sw = new StringWriter();
269                            transformer.transform(new DOMSource(node), new StreamResult(sw));
270                            return sw.toString();
271                    } catch (Exception e) {
272                            throw new RuntimeException(e);
273                    }
274                    
275            }
276    }