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 */ 022package org.granite.util; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.io.StringReader; 028import java.io.StringWriter; 029import java.lang.reflect.Method; 030import java.nio.charset.Charset; 031import java.util.ArrayList; 032import java.util.List; 033 034import javax.xml.parsers.DocumentBuilder; 035import javax.xml.parsers.DocumentBuilderFactory; 036import javax.xml.parsers.ParserConfigurationException; 037import javax.xml.transform.OutputKeys; 038import javax.xml.transform.Templates; 039import javax.xml.transform.Transformer; 040import javax.xml.transform.TransformerException; 041import javax.xml.transform.TransformerFactory; 042import javax.xml.transform.dom.DOMSource; 043import javax.xml.transform.stream.StreamResult; 044import javax.xml.transform.stream.StreamSource; 045import javax.xml.xpath.XPathConstants; 046import javax.xml.xpath.XPathFactory; 047 048import org.w3c.dom.Document; 049import org.w3c.dom.Element; 050import org.w3c.dom.Node; 051import org.w3c.dom.NodeList; 052import org.xml.sax.EntityResolver; 053import org.xml.sax.ErrorHandler; 054import org.xml.sax.InputSource; 055import org.xml.sax.SAXException; 056 057/** 058 * @author Franck WOLFF 059 */ 060public 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}