1 /* 2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.util.xml; 27 28 import java.io.*; 29 import java.util.*; 30 import java.nio.charset.*; 31 import java.util.Map.Entry; 32 import org.xml.sax.*; 33 import org.w3c.dom.*; 34 import javax.xml.parsers.*; 35 import javax.xml.transform.*; 36 import javax.xml.transform.dom.*; 37 import javax.xml.transform.stream.*; 38 39 import sun.util.spi.XmlPropertiesProvider; 40 41 /** 42 * A {@code XmlPropertiesProvider} implementation that uses the JAXP API 43 * for parsing. 44 * 45 * @author Michael McCloskey 46 * @since 1.3 47 */ 48 public class PlatformXmlPropertiesProvider extends XmlPropertiesProvider { 49 50 // XML loading and saving methods for Properties 51 52 // The required DTD URI for exported properties 53 private static final String PROPS_DTD_URI = 54 "http://java.sun.com/dtd/properties.dtd"; 55 56 private static final String PROPS_DTD = 57 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + 58 "<!-- DTD for properties -->" + 59 "<!ELEMENT properties ( comment?, entry* ) >"+ 60 "<!ATTLIST properties" + 61 " version CDATA #FIXED \"1.0\">" + 62 "<!ELEMENT comment (#PCDATA) >" + 63 "<!ELEMENT entry (#PCDATA) >" + 64 "<!ATTLIST entry " + 65 " key CDATA #REQUIRED>"; 66 67 /** 68 * Version number for the format of exported properties files. 69 */ 70 private static final String EXTERNAL_XML_VERSION = "1.0"; 71 72 @Override 73 public void load(Properties props, InputStream in) 74 throws IOException, InvalidPropertiesFormatException 75 { 76 Document doc = null; 77 try { 78 doc = getLoadingDoc(in); 79 } catch (SAXException saxe) { 80 throw new InvalidPropertiesFormatException(saxe); 81 } 82 Element propertiesElement = doc.getDocumentElement(); 83 String xmlVersion = propertiesElement.getAttribute("version"); 84 if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) 85 throw new InvalidPropertiesFormatException( 86 "Exported Properties file format version " + xmlVersion + 87 " is not supported. This java installation can read" + 88 " versions " + EXTERNAL_XML_VERSION + " or older. You" + 89 " may need to install a newer version of JDK."); 90 importProperties(props, propertiesElement); 91 } 92 93 static Document getLoadingDoc(InputStream in) 94 throws SAXException, IOException 95 { 96 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 97 dbf.setIgnoringElementContentWhitespace(true); 98 dbf.setValidating(true); 99 dbf.setCoalescing(true); 100 dbf.setIgnoringComments(true); 101 try { 102 DocumentBuilder db = dbf.newDocumentBuilder(); 103 db.setEntityResolver(new Resolver()); 104 db.setErrorHandler(new EH()); 105 InputSource is = new InputSource(in); 106 return db.parse(is); 107 } catch (ParserConfigurationException x) { 108 throw new Error(x); 109 } 110 } 111 112 static void importProperties(Properties props, Element propertiesElement) { 113 NodeList entries = propertiesElement.getChildNodes(); 114 int numEntries = entries.getLength(); 115 int start = numEntries > 0 && 116 entries.item(0).getNodeName().equals("comment") ? 1 : 0; 117 for (int i=start; i<numEntries; i++) { 118 Element entry = (Element)entries.item(i); 119 if (entry.hasAttribute("key")) { 120 Node n = entry.getFirstChild(); 121 String val = (n == null) ? "" : n.getNodeValue(); 122 props.setProperty(entry.getAttribute("key"), val); 123 } 124 } 125 } 126 127 @Override 128 public void store(Properties props, OutputStream os, String comment, 129 String encoding) 130 throws IOException 131 { 132 // fast-fail for unsupported charsets as UnsupportedEncodingException may 133 // not be thrown later (see JDK-8000621) 134 try { 135 Charset.forName(encoding); 136 } catch (IllegalCharsetNameException | UnsupportedCharsetException x) { 137 throw new UnsupportedEncodingException(encoding); 138 } 139 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 140 DocumentBuilder db = null; 141 try { 142 db = dbf.newDocumentBuilder(); 143 } catch (ParserConfigurationException pce) { 144 assert(false); 145 } 146 Document doc = db.newDocument(); 147 Element properties = (Element) 148 doc.appendChild(doc.createElement("properties")); 149 150 if (comment != null) { 151 Element comments = (Element)properties.appendChild( 152 doc.createElement("comment")); 153 comments.appendChild(doc.createTextNode(comment)); 154 } 155 156 synchronized (props) { 157 for (Entry<Object, Object> e : props.entrySet()) { 158 final Object k = e.getKey(); 159 final Object v = e.getValue(); 160 if (k instanceof String && v instanceof String) { 161 Element entry = (Element)properties.appendChild( 162 doc.createElement("entry")); 163 entry.setAttribute("key", (String)k); 164 entry.appendChild(doc.createTextNode((String)v)); 165 } 166 } 167 } 168 emitDocument(doc, os, encoding); 169 } 170 171 static void emitDocument(Document doc, OutputStream os, String encoding) 172 throws IOException 173 { 174 TransformerFactory tf = TransformerFactory.newInstance(); 175 Transformer t = null; 176 try { 177 t = tf.newTransformer(); 178 t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, PROPS_DTD_URI); 179 t.setOutputProperty(OutputKeys.INDENT, "yes"); 180 t.setOutputProperty(OutputKeys.METHOD, "xml"); 181 t.setOutputProperty(OutputKeys.ENCODING, encoding); 182 } catch (TransformerConfigurationException tce) { 183 assert(false); 184 } 185 DOMSource doms = new DOMSource(doc); 186 StreamResult sr = new StreamResult(os); 187 try { 188 t.transform(doms, sr); 189 } catch (TransformerException te) { 190 throw new IOException(te); 191 } 192 } 193 194 private static class Resolver implements EntityResolver { 195 public InputSource resolveEntity(String pid, String sid) 196 throws SAXException 197 { 198 if (sid.equals(PROPS_DTD_URI)) { 199 InputSource is; 200 is = new InputSource(new StringReader(PROPS_DTD)); 201 is.setSystemId(PROPS_DTD_URI); 202 return is; 203 } 204 throw new SAXException("Invalid system identifier: " + sid); 205 } 206 } 207 208 private static class EH implements ErrorHandler { 209 public void error(SAXParseException x) throws SAXException { 210 throw x; 211 } 212 public void fatalError(SAXParseException x) throws SAXException { 213 throw x; 214 } 215 public void warning(SAXParseException x) throws SAXException { 216 throw x; 217 } 218 } 219 220 }