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