jdk/src/share/classes/java/util/prefs/XmlSupport.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 2002-2006 Sun Microsystems, Inc.  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.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package java.util.prefs;
       
    27 
       
    28 import java.util.*;
       
    29 import java.io.*;
       
    30 import javax.xml.parsers.*;
       
    31 import javax.xml.transform.*;
       
    32 import javax.xml.transform.dom.*;
       
    33 import javax.xml.transform.stream.*;
       
    34 import org.xml.sax.*;
       
    35 import org.w3c.dom.*;
       
    36 
       
    37 /**
       
    38  * XML Support for java.util.prefs. Methods to import and export preference
       
    39  * nodes and subtrees.
       
    40  *
       
    41  * @author  Josh Bloch and Mark Reinhold
       
    42  * @see     Preferences
       
    43  * @since   1.4
       
    44  */
       
    45 class XmlSupport {
       
    46     // The required DTD URI for exported preferences
       
    47     private static final String PREFS_DTD_URI =
       
    48         "http://java.sun.com/dtd/preferences.dtd";
       
    49 
       
    50     // The actual DTD corresponding to the URI
       
    51     private static final String PREFS_DTD =
       
    52         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
       
    53 
       
    54         "<!-- DTD for preferences -->"               +
       
    55 
       
    56         "<!ELEMENT preferences (root) >"             +
       
    57         "<!ATTLIST preferences"                      +
       
    58         " EXTERNAL_XML_VERSION CDATA \"0.0\"  >"     +
       
    59 
       
    60         "<!ELEMENT root (map, node*) >"              +
       
    61         "<!ATTLIST root"                             +
       
    62         "          type (system|user) #REQUIRED >"   +
       
    63 
       
    64         "<!ELEMENT node (map, node*) >"              +
       
    65         "<!ATTLIST node"                             +
       
    66         "          name CDATA #REQUIRED >"           +
       
    67 
       
    68         "<!ELEMENT map (entry*) >"                   +
       
    69         "<!ATTLIST map"                              +
       
    70         "  MAP_XML_VERSION CDATA \"0.0\"  >"         +
       
    71         "<!ELEMENT entry EMPTY >"                    +
       
    72         "<!ATTLIST entry"                            +
       
    73         "          key CDATA #REQUIRED"              +
       
    74         "          value CDATA #REQUIRED >"          ;
       
    75     /**
       
    76      * Version number for the format exported preferences files.
       
    77      */
       
    78     private static final String EXTERNAL_XML_VERSION = "1.0";
       
    79 
       
    80     /*
       
    81      * Version number for the internal map files.
       
    82      */
       
    83     private static final String MAP_XML_VERSION = "1.0";
       
    84 
       
    85     /**
       
    86      * Export the specified preferences node and, if subTree is true, all
       
    87      * subnodes, to the specified output stream.  Preferences are exported as
       
    88      * an XML document conforming to the definition in the Preferences spec.
       
    89      *
       
    90      * @throws IOException if writing to the specified output stream
       
    91      *         results in an <tt>IOException</tt>.
       
    92      * @throws BackingStoreException if preference data cannot be read from
       
    93      *         backing store.
       
    94      * @throws IllegalStateException if this node (or an ancestor) has been
       
    95      *         removed with the {@link #removeNode()} method.
       
    96      */
       
    97     static void export(OutputStream os, final Preferences p, boolean subTree)
       
    98         throws IOException, BackingStoreException {
       
    99         if (((AbstractPreferences)p).isRemoved())
       
   100             throw new IllegalStateException("Node has been removed");
       
   101         Document doc = createPrefsDoc("preferences");
       
   102         Element preferences =  doc.getDocumentElement() ;
       
   103         preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION);
       
   104         Element xmlRoot =  (Element)
       
   105         preferences.appendChild(doc.createElement("root"));
       
   106         xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
       
   107 
       
   108         // Get bottom-up list of nodes from p to root, excluding root
       
   109         List ancestors = new ArrayList();
       
   110 
       
   111         for (Preferences kid = p, dad = kid.parent(); dad != null;
       
   112                                    kid = dad, dad = kid.parent()) {
       
   113             ancestors.add(kid);
       
   114         }
       
   115         Element e = xmlRoot;
       
   116         for (int i=ancestors.size()-1; i >= 0; i--) {
       
   117             e.appendChild(doc.createElement("map"));
       
   118             e = (Element) e.appendChild(doc.createElement("node"));
       
   119             e.setAttribute("name", ((Preferences)ancestors.get(i)).name());
       
   120         }
       
   121         putPreferencesInXml(e, doc, p, subTree);
       
   122 
       
   123         writeDoc(doc, os);
       
   124     }
       
   125 
       
   126     /**
       
   127      * Put the preferences in the specified Preferences node into the
       
   128      * specified XML element which is assumed to represent a node
       
   129      * in the specified XML document which is assumed to conform to
       
   130      * PREFS_DTD.  If subTree is true, create children of the specified
       
   131      * XML node conforming to all of the children of the specified
       
   132      * Preferences node and recurse.
       
   133      *
       
   134      * @throws BackingStoreException if it is not possible to read
       
   135      *         the preferences or children out of the specified
       
   136      *         preferences node.
       
   137      */
       
   138     private static void putPreferencesInXml(Element elt, Document doc,
       
   139                Preferences prefs, boolean subTree) throws BackingStoreException
       
   140     {
       
   141         Preferences[] kidsCopy = null;
       
   142         String[] kidNames = null;
       
   143 
       
   144         // Node is locked to export its contents and get a
       
   145         // copy of children, then lock is released,
       
   146         // and, if subTree = true, recursive calls are made on children
       
   147         synchronized (((AbstractPreferences)prefs).lock) {
       
   148             // Check if this node was concurrently removed. If yes
       
   149             // remove it from XML Document and return.
       
   150             if (((AbstractPreferences)prefs).isRemoved()) {
       
   151                 elt.getParentNode().removeChild(elt);
       
   152                 return;
       
   153             }
       
   154             // Put map in xml element
       
   155             String[] keys = prefs.keys();
       
   156             Element map = (Element) elt.appendChild(doc.createElement("map"));
       
   157             for (int i=0; i<keys.length; i++) {
       
   158                 Element entry = (Element)
       
   159                     map.appendChild(doc.createElement("entry"));
       
   160                 entry.setAttribute("key", keys[i]);
       
   161                 // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL
       
   162                 entry.setAttribute("value", prefs.get(keys[i], null));
       
   163             }
       
   164             // Recurse if appropriate
       
   165             if (subTree) {
       
   166                 /* Get a copy of kids while lock is held */
       
   167                 kidNames = prefs.childrenNames();
       
   168                 kidsCopy = new Preferences[kidNames.length];
       
   169                 for (int i = 0; i <  kidNames.length; i++)
       
   170                     kidsCopy[i] = prefs.node(kidNames[i]);
       
   171             }
       
   172             // release lock
       
   173         }
       
   174 
       
   175         if (subTree) {
       
   176             for (int i=0; i < kidNames.length; i++) {
       
   177                 Element xmlKid = (Element)
       
   178                     elt.appendChild(doc.createElement("node"));
       
   179                 xmlKid.setAttribute("name", kidNames[i]);
       
   180                 putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree);
       
   181             }
       
   182         }
       
   183     }
       
   184 
       
   185     /**
       
   186      * Import preferences from the specified input stream, which is assumed
       
   187      * to contain an XML document in the format described in the Preferences
       
   188      * spec.
       
   189      *
       
   190      * @throws IOException if reading from the specified output stream
       
   191      *         results in an <tt>IOException</tt>.
       
   192      * @throws InvalidPreferencesFormatException Data on input stream does not
       
   193      *         constitute a valid XML document with the mandated document type.
       
   194      */
       
   195     static void importPreferences(InputStream is)
       
   196         throws IOException, InvalidPreferencesFormatException
       
   197     {
       
   198         try {
       
   199             Document doc = loadPrefsDoc(is);
       
   200             String xmlVersion =
       
   201                 doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION");
       
   202             if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
       
   203                 throw new InvalidPreferencesFormatException(
       
   204                 "Exported preferences file format version " + xmlVersion +
       
   205                 " is not supported. This java installation can read" +
       
   206                 " versions " + EXTERNAL_XML_VERSION + " or older. You may need" +
       
   207                 " to install a newer version of JDK.");
       
   208 
       
   209             Element xmlRoot = (Element) doc.getDocumentElement().
       
   210                                                getChildNodes().item(0);
       
   211             Preferences prefsRoot =
       
   212                 (xmlRoot.getAttribute("type").equals("user") ?
       
   213                             Preferences.userRoot() : Preferences.systemRoot());
       
   214             ImportSubtree(prefsRoot, xmlRoot);
       
   215         } catch(SAXException e) {
       
   216             throw new InvalidPreferencesFormatException(e);
       
   217         }
       
   218     }
       
   219 
       
   220     /**
       
   221      * Create a new prefs XML document.
       
   222      */
       
   223     private static Document createPrefsDoc( String qname ) {
       
   224         try {
       
   225             DOMImplementation di = DocumentBuilderFactory.newInstance().
       
   226                 newDocumentBuilder().getDOMImplementation();
       
   227             DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
       
   228             return di.createDocument(null, qname, dt);
       
   229         } catch(ParserConfigurationException e) {
       
   230             throw new AssertionError(e);
       
   231         }
       
   232     }
       
   233 
       
   234     /**
       
   235      * Load an XML document from specified input stream, which must
       
   236      * have the requisite DTD URI.
       
   237      */
       
   238     private static Document loadPrefsDoc(InputStream in)
       
   239         throws SAXException, IOException
       
   240     {
       
   241         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       
   242         dbf.setIgnoringElementContentWhitespace(true);
       
   243         dbf.setValidating(true);
       
   244         dbf.setCoalescing(true);
       
   245         dbf.setIgnoringComments(true);
       
   246         try {
       
   247             DocumentBuilder db = dbf.newDocumentBuilder();
       
   248             db.setEntityResolver(new Resolver());
       
   249             db.setErrorHandler(new EH());
       
   250             return db.parse(new InputSource(in));
       
   251         } catch (ParserConfigurationException e) {
       
   252             throw new AssertionError(e);
       
   253         }
       
   254     }
       
   255 
       
   256     /**
       
   257      * Write XML document to the specified output stream.
       
   258      */
       
   259     private static final void writeDoc(Document doc, OutputStream out)
       
   260         throws IOException
       
   261     {
       
   262         try {
       
   263             TransformerFactory tf = TransformerFactory.newInstance();
       
   264             try {
       
   265                 tf.setAttribute("indent-number", new Integer(2));
       
   266             } catch (IllegalArgumentException iae) {
       
   267                 //Ignore the IAE. Should not fail the writeout even the
       
   268                 //transformer provider does not support "indent-number".
       
   269             }
       
   270             Transformer t = tf.newTransformer();
       
   271             t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
       
   272             t.setOutputProperty(OutputKeys.INDENT, "yes");
       
   273             //Transformer resets the "indent" info if the "result" is a StreamResult with
       
   274             //an OutputStream object embedded, creating a Writer object on top of that
       
   275             //OutputStream object however works.
       
   276             t.transform(new DOMSource(doc),
       
   277                         new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
       
   278         } catch(TransformerException e) {
       
   279             throw new AssertionError(e);
       
   280         }
       
   281     }
       
   282 
       
   283     /**
       
   284      * Recursively traverse the specified preferences node and store
       
   285      * the described preferences into the system or current user
       
   286      * preferences tree, as appropriate.
       
   287      */
       
   288     private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {
       
   289         NodeList xmlKids = xmlNode.getChildNodes();
       
   290         int numXmlKids = xmlKids.getLength();
       
   291         /*
       
   292          * We first lock the node, import its contents and get
       
   293          * child nodes. Then we unlock the node and go to children
       
   294          * Since some of the children might have been concurrently
       
   295          * deleted we check for this.
       
   296          */
       
   297         Preferences[] prefsKids;
       
   298         /* Lock the node */
       
   299         synchronized (((AbstractPreferences)prefsNode).lock) {
       
   300             //If removed, return silently
       
   301             if (((AbstractPreferences)prefsNode).isRemoved())
       
   302                 return;
       
   303 
       
   304             // Import any preferences at this node
       
   305             Element firstXmlKid = (Element) xmlKids.item(0);
       
   306             ImportPrefs(prefsNode, firstXmlKid);
       
   307             prefsKids = new Preferences[numXmlKids - 1];
       
   308 
       
   309             // Get involved children
       
   310             for (int i=1; i < numXmlKids; i++) {
       
   311                 Element xmlKid = (Element) xmlKids.item(i);
       
   312                 prefsKids[i-1] = prefsNode.node(xmlKid.getAttribute("name"));
       
   313             }
       
   314         } // unlocked the node
       
   315         // import children
       
   316         for (int i=1; i < numXmlKids; i++)
       
   317             ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));
       
   318     }
       
   319 
       
   320     /**
       
   321      * Import the preferences described by the specified XML element
       
   322      * (a map from a preferences document) into the specified
       
   323      * preferences node.
       
   324      */
       
   325     private static void ImportPrefs(Preferences prefsNode, Element map) {
       
   326         NodeList entries = map.getChildNodes();
       
   327         for (int i=0, numEntries = entries.getLength(); i < numEntries; i++) {
       
   328             Element entry = (Element) entries.item(i);
       
   329             prefsNode.put(entry.getAttribute("key"),
       
   330                           entry.getAttribute("value"));
       
   331         }
       
   332     }
       
   333 
       
   334     /**
       
   335      * Export the specified Map<String,String> to a map document on
       
   336      * the specified OutputStream as per the prefs DTD.  This is used
       
   337      * as the internal (undocumented) format for FileSystemPrefs.
       
   338      *
       
   339      * @throws IOException if writing to the specified output stream
       
   340      *         results in an <tt>IOException</tt>.
       
   341      */
       
   342     static void exportMap(OutputStream os, Map map) throws IOException {
       
   343         Document doc = createPrefsDoc("map");
       
   344         Element xmlMap = doc.getDocumentElement( ) ;
       
   345         xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
       
   346 
       
   347         for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
       
   348             Map.Entry e = (Map.Entry) i.next();
       
   349             Element xe = (Element)
       
   350                 xmlMap.appendChild(doc.createElement("entry"));
       
   351             xe.setAttribute("key",   (String) e.getKey());
       
   352             xe.setAttribute("value", (String) e.getValue());
       
   353         }
       
   354 
       
   355         writeDoc(doc, os);
       
   356     }
       
   357 
       
   358     /**
       
   359      * Import Map from the specified input stream, which is assumed
       
   360      * to contain a map document as per the prefs DTD.  This is used
       
   361      * as the internal (undocumented) format for FileSystemPrefs.  The
       
   362      * key-value pairs specified in the XML document will be put into
       
   363      * the specified Map.  (If this Map is empty, it will contain exactly
       
   364      * the key-value pairs int the XML-document when this method returns.)
       
   365      *
       
   366      * @throws IOException if reading from the specified output stream
       
   367      *         results in an <tt>IOException</tt>.
       
   368      * @throws InvalidPreferencesFormatException Data on input stream does not
       
   369      *         constitute a valid XML document with the mandated document type.
       
   370      */
       
   371     static void importMap(InputStream is, Map m)
       
   372         throws IOException, InvalidPreferencesFormatException
       
   373     {
       
   374         try {
       
   375             Document doc = loadPrefsDoc(is);
       
   376             Element xmlMap = doc.getDocumentElement();
       
   377             // check version
       
   378             String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
       
   379             if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
       
   380                 throw 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.");
       
   385 
       
   386             NodeList entries = xmlMap.getChildNodes();
       
   387             for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {
       
   388                 Element entry = (Element) entries.item(i);
       
   389                 m.put(entry.getAttribute("key"), entry.getAttribute("value"));
       
   390             }
       
   391         } catch(SAXException e) {
       
   392             throw new InvalidPreferencesFormatException(e);
       
   393         }
       
   394     }
       
   395 
       
   396     private static class Resolver implements EntityResolver {
       
   397         public InputSource resolveEntity(String pid, String sid)
       
   398             throws SAXException
       
   399         {
       
   400             if (sid.equals(PREFS_DTD_URI)) {
       
   401                 InputSource is;
       
   402                 is = new InputSource(new StringReader(PREFS_DTD));
       
   403                 is.setSystemId(PREFS_DTD_URI);
       
   404                 return is;
       
   405             }
       
   406             throw new SAXException("Invalid system identifier: " + sid);
       
   407         }
       
   408     }
       
   409 
       
   410     private static class EH implements ErrorHandler {
       
   411         public void error(SAXParseException x) throws SAXException {
       
   412             throw x;
       
   413         }
       
   414         public void fatalError(SAXParseException x) throws SAXException {
       
   415             throw x;
       
   416         }
       
   417         public void warning(SAXParseException x) throws SAXException {
       
   418             throw x;
       
   419         }
       
   420     }
       
   421 }