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.XPathFactory;
046    
047    import org.w3c.dom.Document;
048    import org.w3c.dom.Element;
049    import org.w3c.dom.Node;
050    import org.w3c.dom.NodeList;
051    import org.xml.sax.EntityResolver;
052    import org.xml.sax.ErrorHandler;
053    import org.xml.sax.InputSource;
054    import org.xml.sax.SAXException;
055    
056    /**
057     * @author Franck WOLFF
058     */
059    public class StdXMLUtil implements XMLUtil {
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 DocumentBuilderFactory documentBuilderFactory = null;
071            private DocumentBuilderFactory validatingDocumentBuilderFactory = null;
072        private TransformerFactory transformerFactory = null;
073            private Templates toStringTemplates = null;
074            private XPathFactory xPathFactory = null;
075    
076            
077            public Document newDocument() {
078            return newDocument(null);
079            }
080            
081            public Document newDocument(String root) {
082                    try {
083                            Document document = getDocumentBuilderFactory().newDocumentBuilder().newDocument();
084                            document.setXmlVersion("1.0");
085                    document.setXmlStandalone(true);
086                    if (root != null)
087                                    newElement(document, root);
088                    return document;
089                    }
090                    catch (Exception e) {
091                            throw new RuntimeException(e);
092                    }
093            }
094            
095            public Document getDocument(Node node) {
096                    return (node instanceof Document ? (Document)node : node.getOwnerDocument());
097            }
098    
099            public Element newElement(Node parent, String name) {
100                    return newElement(parent, name, null);
101            }
102    
103            public Element newElement(Node parent, String name, String value) {
104                    Element element = getDocument(parent).createElement(name);
105                    parent.appendChild(element);
106                    if (value != null)
107                            element.setTextContent(value);
108                    return element;
109            }
110            
111            public String getNormalizedValue(Node node) {
112                    if (node == null)
113                            return null;
114                    if (node.getNodeType() == Node.ELEMENT_NODE) {
115                            StringBuilder sb = new StringBuilder();
116                            for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
117                                    if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null) {
118                                            String value = child.getNodeValue().trim();
119                                            if (value.length() > 0) {
120                                                    if (sb.length() > 0)
121                                                            sb.append(' ');
122                                                    sb.append(value);
123                                            }
124                                    }
125                            }
126                            return sb.toString();
127                    }
128                    return (node.getNodeValue() != null ? node.getNodeValue().trim() : null);
129            }
130    
131            public String setValue(Node node, String value) {
132                    if (node != null) {
133                            String previousValue = getNormalizedValue(node);
134                            switch (node.getNodeType()) {
135                            case Node.ELEMENT_NODE:
136                                    ((Element)node).setTextContent(value);
137                                    break;
138                            case Node.ATTRIBUTE_NODE:
139                            case Node.TEXT_NODE:
140                                    node.setNodeValue(value);
141                                    break;
142                            default:
143                                    throw new RuntimeException("Illegal node for write operations: " + node);
144                            }
145                            return previousValue;
146                    }
147                    return null;
148            }
149            
150        public Document buildDocument(String xml) {
151            try {
152                DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
153                return builder.parse(new InputSource(new StringReader(xml)));
154            } catch (Exception e) {
155                throw new RuntimeException("Could not parse XML string", e);
156            }
157        }
158        
159            public Document loadDocument(InputStream input) throws IOException, SAXException {
160                    try {
161                            return getDocumentBuilderFactory().newDocumentBuilder().parse(input);
162                    } catch (ParserConfigurationException e) {
163                            throw new RuntimeException(e);
164                    }
165            }
166            
167            public Document loadDocument(InputStream input, EntityResolver resolver, ErrorHandler errorHandler) throws IOException, SAXException {
168                    try {
169                            DocumentBuilder builder = getValidatingDocumentBuilderFactory().newDocumentBuilder();
170                            builder.setEntityResolver(resolver);
171                            if (errorHandler != null)
172                                    builder.setErrorHandler(errorHandler);
173                            return builder.parse(input);
174                    } catch (ParserConfigurationException e) {
175                            throw new RuntimeException(e);
176                    }
177            }       
178        
179            public void saveDocument(Document document, OutputStream output) {
180                    try {
181                            Transformer transformer = TransformerFactory.newInstance().newTransformer();
182                            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
183                            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
184                            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
185                            transformer.setOutputProperty(OutputKeys.INDENT, "no");
186                            
187                            transformer.transform(new DOMSource(document), new StreamResult(output));
188                    }
189                    catch (TransformerException e) {
190                            throw new RuntimeException("Could not save document", e);
191                    }
192            }
193    
194        public String toString(Document doc) {
195            try {
196                Transformer transformer = getTransformerFactory().newTransformer();
197                StringWriter writer = new StringWriter();
198                transformer.transform(new DOMSource(doc), new StreamResult(writer));
199                return writer.toString();
200            } catch (Exception e) {
201                throw new RuntimeException("Could not serialize document", e);
202            }
203        }
204        
205            protected Templates getToStringTemplates() {
206                    if (toStringTemplates == null) {
207                            try {
208                                    toStringTemplates = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(TO_STRING_XSL)));
209                            } catch (Exception e) {
210                                    throw new RuntimeException(e);
211                            }
212                    }
213                    return toStringTemplates;
214            }
215    
216            public String toNodeString(Node node) {
217                    try {
218                            Transformer transformer = getToStringTemplates().newTransformer();
219                            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
220                            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
221                            transformer.setOutputProperty(OutputKeys.ENCODING, Charset.defaultCharset().name());
222                            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
223                            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
224                            
225                            StringWriter sw = new StringWriter();
226                            transformer.transform(new DOMSource(node), new StreamResult(sw));
227                            return sw.toString();
228                    } catch (Exception e) {
229                            throw new RuntimeException(e);
230                    }
231                                    
232            }
233            
234            public Node selectSingleNode(Object context, String expression) {
235                    try {
236                            return (Node)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODE);
237                    } catch (Exception e) {
238                            throw new RuntimeException(e);
239                    }
240            }
241            
242            public List<Node> selectNodeSet(Object context, String expression) {
243                    try {
244                            NodeList nodeList = (NodeList)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODESET);
245                            List<Node> nodes = new ArrayList<Node>(nodeList.getLength());
246                            for (int i = 0; i < nodeList.getLength(); i++)
247                                    nodes.add(nodeList.item(i));
248                            return nodes;
249                    } catch (Exception e) {
250                            throw new RuntimeException(e);
251                    }
252            }
253            
254            
255            private DocumentBuilderFactory getDocumentBuilderFactory() {
256                    if (documentBuilderFactory == null) {
257                            try {
258                                    documentBuilderFactory = DocumentBuilderFactory.newInstance();
259            
260                                    documentBuilderFactory.setCoalescing(true);
261                                    documentBuilderFactory.setIgnoringComments(true);
262                            } 
263                            catch (Exception e) {
264                                    throw new RuntimeException(e);
265                            }
266                    }
267                    return documentBuilderFactory;
268            }
269    
270            private DocumentBuilderFactory getValidatingDocumentBuilderFactory() {
271                    if (validatingDocumentBuilderFactory == null) {
272                            try {
273                                    validatingDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
274                                    validatingDocumentBuilderFactory.setCoalescing(true);
275                                    validatingDocumentBuilderFactory.setIgnoringComments(true);
276                                    validatingDocumentBuilderFactory.setValidating(true);
277                                    validatingDocumentBuilderFactory.setIgnoringElementContentWhitespace(true);
278                            } catch (Exception e) {
279                                    throw new RuntimeException(e);
280                            }
281                    }
282                    return validatingDocumentBuilderFactory;
283            }
284    
285        private TransformerFactory getTransformerFactory() {
286            if (transformerFactory == null)
287                transformerFactory = TransformerFactory.newInstance();
288            return transformerFactory;
289        }
290        
291            private XPathFactory getXPathFactory() {
292                    if (xPathFactory == null) {
293                            try {
294                                    xPathFactory = XPathFactory.newInstance();
295                            }
296                            catch (Exception e) {
297                                    try {
298                                            // Fallback to xalan for Google App Engine
299                                            Class<?> factoryClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.xpath.jaxp.XPathFactoryImpl");
300                                            Method m = factoryClass.getMethod("newInstance", String.class, String.class, ClassLoader.class);
301                                            xPathFactory = (XPathFactory)m.invoke(null, XPathFactory.DEFAULT_OBJECT_MODEL_URI, "org.apache.xpath.jaxp.XPathFactoryImpl", null);
302                                    }
303                                    catch (Exception f) {
304                                            throw new RuntimeException("XPathFactory could not be found", f);
305                                    }
306                            }
307                    }
308                    return xPathFactory;
309            }
310    
311    }