Path: blob/master/src/java.prefs/share/classes/java/util/prefs/XmlSupport.java
41159 views
/*1* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util.prefs;2627import java.util.*;28import java.io.*;29import javax.xml.parsers.*;30import javax.xml.transform.*;31import javax.xml.transform.dom.*;32import javax.xml.transform.stream.*;33import org.xml.sax.*;34import org.w3c.dom.*;3536import static java.nio.charset.StandardCharsets.UTF_8;3738/**39* XML Support for java.util.prefs. Methods to import and export preference40* nodes and subtrees.41*42* @author Josh Bloch and Mark Reinhold43* @see Preferences44* @since 1.445*/46class XmlSupport {47// The required DTD URI for exported preferences48private static final String PREFS_DTD_URI =49"http://java.sun.com/dtd/preferences.dtd";5051// The actual DTD corresponding to the URI52private static final String PREFS_DTD =53"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +5455"<!-- DTD for preferences -->" +5657"<!ELEMENT preferences (root) >" +58"<!ATTLIST preferences" +59" EXTERNAL_XML_VERSION CDATA \"0.0\" >" +6061"<!ELEMENT root (map, node*) >" +62"<!ATTLIST root" +63" type (system|user) #REQUIRED >" +6465"<!ELEMENT node (map, node*) >" +66"<!ATTLIST node" +67" name CDATA #REQUIRED >" +6869"<!ELEMENT map (entry*) >" +70"<!ATTLIST map" +71" MAP_XML_VERSION CDATA \"0.0\" >" +72"<!ELEMENT entry EMPTY >" +73"<!ATTLIST entry" +74" key CDATA #REQUIRED" +75" value CDATA #REQUIRED >" ;76/**77* Version number for the format exported preferences files.78*/79private static final String EXTERNAL_XML_VERSION = "1.0";8081/*82* Version number for the internal map files.83*/84private static final String MAP_XML_VERSION = "1.0";8586/**87* Export the specified preferences node and, if subTree is true, all88* subnodes, to the specified output stream. Preferences are exported as89* an XML document conforming to the definition in the Preferences spec.90*91* @throws IOException if writing to the specified output stream92* results in an {@code IOException}.93* @throws BackingStoreException if preference data cannot be read from94* backing store.95* @throws IllegalStateException if this node (or an ancestor) has been96* removed with the {@link Preferences#removeNode()} method.97*/98static void export(OutputStream os, final Preferences p, boolean subTree)99throws IOException, BackingStoreException {100if (((AbstractPreferences)p).isRemoved())101throw new IllegalStateException("Node has been removed");102Document doc = createPrefsDoc("preferences");103Element preferences = doc.getDocumentElement() ;104preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION);105Element xmlRoot = (Element)106preferences.appendChild(doc.createElement("root"));107xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));108109// Get bottom-up list of nodes from p to root, excluding root110List<Preferences> ancestors = new ArrayList<>();111112for (Preferences kid = p, dad = kid.parent(); dad != null;113kid = dad, dad = kid.parent()) {114ancestors.add(kid);115}116Element e = xmlRoot;117for (int i=ancestors.size()-1; i >= 0; i--) {118e.appendChild(doc.createElement("map"));119e = (Element) e.appendChild(doc.createElement("node"));120e.setAttribute("name", ancestors.get(i).name());121}122putPreferencesInXml(e, doc, p, subTree);123124writeDoc(doc, os);125}126127/**128* Put the preferences in the specified Preferences node into the129* specified XML element which is assumed to represent a node130* in the specified XML document which is assumed to conform to131* PREFS_DTD. If subTree is true, create children of the specified132* XML node conforming to all of the children of the specified133* Preferences node and recurse.134*135* @throws BackingStoreException if it is not possible to read136* the preferences or children out of the specified137* preferences node.138*/139private static void putPreferencesInXml(Element elt, Document doc,140Preferences prefs, boolean subTree) throws BackingStoreException141{142Preferences[] kidsCopy = null;143String[] kidNames = null;144145// Node is locked to export its contents and get a146// copy of children, then lock is released,147// and, if subTree = true, recursive calls are made on children148synchronized (((AbstractPreferences)prefs).lock) {149// Check if this node was concurrently removed. If yes150// remove it from XML Document and return.151if (((AbstractPreferences)prefs).isRemoved()) {152elt.getParentNode().removeChild(elt);153return;154}155// Put map in xml element156String[] keys = prefs.keys();157Element map = (Element) elt.appendChild(doc.createElement("map"));158for (String key : keys) {159Element entry = (Element)160map.appendChild(doc.createElement("entry"));161entry.setAttribute("key", key);162// NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL163entry.setAttribute("value", prefs.get(key, null));164}165// Recurse if appropriate166if (subTree) {167/* Get a copy of kids while lock is held */168kidNames = prefs.childrenNames();169kidsCopy = new Preferences[kidNames.length];170for (int i = 0; i < kidNames.length; i++)171kidsCopy[i] = prefs.node(kidNames[i]);172}173// release lock174}175176if (subTree) {177for (int i=0; i < kidNames.length; i++) {178Element xmlKid = (Element)179elt.appendChild(doc.createElement("node"));180xmlKid.setAttribute("name", kidNames[i]);181putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree);182}183}184}185186/**187* Import preferences from the specified input stream, which is assumed188* to contain an XML document in the format described in the Preferences189* spec.190*191* @throws IOException if reading from the specified output stream192* results in an {@code IOException}.193* @throws InvalidPreferencesFormatException Data on input stream does not194* constitute a valid XML document with the mandated document type.195*/196static void importPreferences(InputStream is)197throws IOException, InvalidPreferencesFormatException198{199try {200Document doc = loadPrefsDoc(is);201String xmlVersion =202doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION");203if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)204throw new InvalidPreferencesFormatException(205"Exported preferences file format version " + xmlVersion +206" is not supported. This java installation can read" +207" versions " + EXTERNAL_XML_VERSION + " or older. You may need" +208" to install a newer version of JDK.");209210Element xmlRoot = (Element) doc.getDocumentElement().211getChildNodes().item(0);212Preferences prefsRoot =213(xmlRoot.getAttribute("type").equals("user") ?214Preferences.userRoot() : Preferences.systemRoot());215ImportSubtree(prefsRoot, xmlRoot);216} catch(SAXException e) {217throw new InvalidPreferencesFormatException(e);218}219}220221/**222* Create a new prefs XML document.223*/224private static Document createPrefsDoc( String qname ) {225try {226DOMImplementation di = DocumentBuilderFactory.newInstance().227newDocumentBuilder().getDOMImplementation();228DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);229return di.createDocument(null, qname, dt);230} catch(ParserConfigurationException e) {231throw new AssertionError(e);232}233}234235/**236* Load an XML document from specified input stream, which must237* have the requisite DTD URI.238*/239private static Document loadPrefsDoc(InputStream in)240throws SAXException, IOException241{242DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();243dbf.setIgnoringElementContentWhitespace(true);244dbf.setValidating(true);245dbf.setCoalescing(true);246dbf.setIgnoringComments(true);247try {248DocumentBuilder db = dbf.newDocumentBuilder();249db.setEntityResolver(new Resolver());250db.setErrorHandler(new EH());251return db.parse(new InputSource(in));252} catch (ParserConfigurationException e) {253throw new AssertionError(e);254}255}256257/**258* Write XML document to the specified output stream.259*/260private static final void writeDoc(Document doc, OutputStream out)261throws IOException262{263try {264TransformerFactory tf = TransformerFactory.newInstance();265try {266tf.setAttribute("indent-number", 2);267} catch (IllegalArgumentException iae) {268//Ignore the IAE. Should not fail the writeout even the269//transformer provider does not support "indent-number".270}271Transformer t = tf.newTransformer();272t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());273t.setOutputProperty(OutputKeys.INDENT, "yes");274//Transformer resets the "indent" info if the "result" is a StreamResult with275//an OutputStream object embedded, creating a Writer object on top of that276//OutputStream object however works.277t.transform(new DOMSource(doc),278new StreamResult(new BufferedWriter(new OutputStreamWriter(out, UTF_8))));279} catch(TransformerException e) {280throw new AssertionError(e);281}282}283284/**285* Recursively traverse the specified preferences node and store286* the described preferences into the system or current user287* preferences tree, as appropriate.288*/289private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {290NodeList xmlKids = xmlNode.getChildNodes();291int numXmlKids = xmlKids.getLength();292/*293* We first lock the node, import its contents and get294* child nodes. Then we unlock the node and go to children295* Since some of the children might have been concurrently296* deleted we check for this.297*/298Preferences[] prefsKids;299/* Lock the node */300synchronized (((AbstractPreferences)prefsNode).lock) {301//If removed, return silently302if (((AbstractPreferences)prefsNode).isRemoved())303return;304305// Import any preferences at this node306Element firstXmlKid = (Element) xmlKids.item(0);307ImportPrefs(prefsNode, firstXmlKid);308prefsKids = new Preferences[numXmlKids - 1];309310// Get involved children311for (int i=1; i < numXmlKids; i++) {312Element xmlKid = (Element) xmlKids.item(i);313prefsKids[i-1] = prefsNode.node(xmlKid.getAttribute("name"));314}315} // unlocked the node316// import children317for (int i=1; i < numXmlKids; i++)318ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));319}320321/**322* Import the preferences described by the specified XML element323* (a map from a preferences document) into the specified324* preferences node.325*/326private static void ImportPrefs(Preferences prefsNode, Element map) {327NodeList entries = map.getChildNodes();328for (int i=0, numEntries = entries.getLength(); i < numEntries; i++) {329Element entry = (Element) entries.item(i);330prefsNode.put(entry.getAttribute("key"),331entry.getAttribute("value"));332}333}334335/**336* Export the specified Map<String,String> to a map document on337* the specified OutputStream as per the prefs DTD. This is used338* as the internal (undocumented) format for FileSystemPrefs.339*340* @throws IOException if writing to the specified output stream341* results in an {@code IOException}.342*/343static void exportMap(OutputStream os, Map<String, String> map) throws IOException {344Document doc = createPrefsDoc("map");345Element xmlMap = doc.getDocumentElement( ) ;346xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);347348for (Map.Entry<String, String> e : map.entrySet()) {349Element xe = (Element)350xmlMap.appendChild(doc.createElement("entry"));351xe.setAttribute("key", e.getKey());352xe.setAttribute("value", e.getValue());353}354355writeDoc(doc, os);356}357358/**359* Import Map from the specified input stream, which is assumed360* to contain a map document as per the prefs DTD. This is used361* as the internal (undocumented) format for FileSystemPrefs. The362* key-value pairs specified in the XML document will be put into363* the specified Map. (If this Map is empty, it will contain exactly364* the key-value pairs int the XML-document when this method returns.)365*366* @throws IOException if reading from the specified output stream367* results in an {@code IOException}.368* @throws InvalidPreferencesFormatException Data on input stream does not369* constitute a valid XML document with the mandated document type.370*/371static void importMap(InputStream is, Map<String, String> m)372throws IOException, InvalidPreferencesFormatException373{374try {375Document doc = loadPrefsDoc(is);376Element xmlMap = doc.getDocumentElement();377// check version378String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");379if (mapVersion.compareTo(MAP_XML_VERSION) > 0)380throw new InvalidPreferencesFormatException(381"Preferences map file format version " + mapVersion +382" is not supported. This java installation can read" +383" versions " + MAP_XML_VERSION + " or older. You may need" +384" to install a newer version of JDK.");385386NodeList entries = xmlMap.getChildNodes();387for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {388Element entry = (Element) entries.item(i);389m.put(entry.getAttribute("key"), entry.getAttribute("value"));390}391} catch(SAXException e) {392throw new InvalidPreferencesFormatException(e);393}394}395396private static class Resolver implements EntityResolver {397public InputSource resolveEntity(String pid, String sid)398throws SAXException399{400if (sid.equals(PREFS_DTD_URI)) {401InputSource is;402is = new InputSource(new StringReader(PREFS_DTD));403is.setSystemId(PREFS_DTD_URI);404return is;405}406throw new SAXException("Invalid system identifier: " + sid);407}408}409410private static class EH implements ErrorHandler {411public void error(SAXParseException x) throws SAXException {412throw x;413}414public void fatalError(SAXParseException x) throws SAXException {415throw x;416}417public void warning(SAXParseException x) throws SAXException {418throw x;419}420}421}422423424