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 }