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 private static final String PROPS_DTD_DECL = 56 "<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">"; 57 58 private static final String PROPS_DTD = 59 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + 60 "<!-- DTD for properties -->" + 61 "<!ELEMENT properties ( comment?, entry* ) >"+ 62 "<!ATTLIST properties" + 63 " version CDATA #FIXED \"1.0\">" + 64 "<!ELEMENT comment (#PCDATA) >" + 65 "<!ELEMENT entry (#PCDATA) >" + 66 "<!ATTLIST entry " + 67 " key CDATA #REQUIRED>"; 68 69 /** 70 * Version number for the format of exported properties files. 71 */ 72 private static final String EXTERNAL_XML_VERSION = "1.0"; 73 74 @Override 75 public void load(Properties props, InputStream in) 76 throws IOException, InvalidPropertiesFormatException 77 { 78 Document doc = null; 79 try { 80 doc = getLoadingDoc(in); 81 } catch (SAXException saxe) { 82 throw new InvalidPropertiesFormatException(saxe); 83 } 84 Element propertiesElement = doc.getDocumentElement(); 85 String xmlVersion = propertiesElement.getAttribute("version"); 86 if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) 87 throw new InvalidPropertiesFormatException( 88 "Exported Properties file format version " + xmlVersion + 89 " is not supported. This java installation can read" + 90 " versions " + EXTERNAL_XML_VERSION + " or older. You" + 91 " may need to install a newer version of JDK."); 92 importProperties(props, propertiesElement); 93 } 94 95 static Document getLoadingDoc(InputStream in) 96 throws SAXException, IOException 97 { 98 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 99 dbf.setIgnoringElementContentWhitespace(true); 100 dbf.setValidating(true); 101 dbf.setCoalescing(true); 102 dbf.setIgnoringComments(true); 103 try { 104 DocumentBuilder db = dbf.newDocumentBuilder(); 105 db.setEntityResolver(new Resolver()); 106 db.setErrorHandler(new EH()); 107 InputSource is = new InputSource(in); 108 Document doc = db.parse(is); 109 if (doc.getDoctype().getInternalSubset() != null) { 110 throw new SAXParseException("Internal DTD subset is not allowed. " + 111 "The Properties XML document must have the following DOCTYPE declaration: \n" + 112 PROPS_DTD_DECL, null); 113 } 114 return doc; 115 } catch (ParserConfigurationException x) { 116 throw new Error(x); 117 } 118 } 119 120 static void importProperties(Properties props, Element propertiesElement) { 121 NodeList entries = propertiesElement.getChildNodes(); 122 int numEntries = entries.getLength(); 123 int start = numEntries > 0 && 124 entries.item(0).getNodeName().equals("comment") ? 1 : 0; 125 for (int i=start; i<numEntries; i++) { 126 Element entry = (Element)entries.item(i); 127 if (entry.hasAttribute("key")) { 128 Node n = entry.getFirstChild(); 129 String val = (n == null) ? "" : n.getNodeValue(); 130 props.setProperty(entry.getAttribute("key"), val); 131 } 132 } 133 } 134 135 @Override 136 public void store(Properties props, OutputStream os, String comment, 137 String encoding) 138 throws IOException 139 { 140 // fast-fail for unsupported charsets as UnsupportedEncodingException may 141 // not be thrown later (see JDK-8000621) 142 try { 143 Charset.forName(encoding); 144 } catch (IllegalCharsetNameException | UnsupportedCharsetException x) { 145 throw new UnsupportedEncodingException(encoding); 146 } 147 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 148 DocumentBuilder db = null; 149 try { 150 db = dbf.newDocumentBuilder(); 151 } catch (ParserConfigurationException pce) { 152 assert(false); 153 } 154 Document doc = db.newDocument(); 155 Element properties = (Element) 156 doc.appendChild(doc.createElement("properties")); 157 158 if (comment != null) { 159 Element comments = (Element)properties.appendChild( 160 doc.createElement("comment")); 161 comments.appendChild(doc.createTextNode(comment)); 162 } 163 164 synchronized (props) { 165 for (Entry<Object, Object> e : props.entrySet()) { 166 final Object k = e.getKey(); 167 final Object v = e.getValue(); 168 if (k instanceof String && v instanceof String) { 169 Element entry = (Element)properties.appendChild( 170 doc.createElement("entry")); 171 entry.setAttribute("key", (String)k); 172 entry.appendChild(doc.createTextNode((String)v)); 173 } 174 } 175 } 176 emitDocument(doc, os, encoding); 177 } 178 179 static void emitDocument(Document doc, OutputStream os, String encoding) 180 throws IOException 181 { 182 TransformerFactory tf = TransformerFactory.newInstance(); 183 Transformer t = null; 184 try { 185 t = tf.newTransformer(); 186 t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, PROPS_DTD_URI); 187 t.setOutputProperty(OutputKeys.INDENT, "yes"); 188 t.setOutputProperty(OutputKeys.METHOD, "xml"); 189 t.setOutputProperty(OutputKeys.ENCODING, encoding); 190 } catch (TransformerConfigurationException tce) { 191 assert(false); 192 } 193 DOMSource doms = new DOMSource(doc); 194 StreamResult sr = new StreamResult(os); 195 try { 196 t.transform(doms, sr); 197 } catch (TransformerException te) { 198 throw new IOException(te); 199 } 200 } 201 202 private static class Resolver implements EntityResolver { 203 public InputSource resolveEntity(String pid, String sid) 204 throws SAXException 205 { 206 if (sid.equals(PROPS_DTD_URI)) { 207 InputSource is; 208 is = new InputSource(new StringReader(PROPS_DTD)); 209 is.setSystemId(PROPS_DTD_URI); 210 return is; 211 } 212 throw new SAXException("Invalid system identifier: " + sid); 213 } 214 } 215 216 private static class EH implements ErrorHandler { 217 public void error(SAXParseException x) throws SAXException { 218 throw x; 219 } 220 public void fatalError(SAXParseException x) throws SAXException { 221 throw x; 222 } 223 public void warning(SAXParseException x) throws SAXException { 224 throw x; 225 } 226 } 227 228 }