    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.configuration;
    019
    020    import java.io.File;
    021    import java.io.PrintWriter;
    022    import java.io.Reader;
    023    import java.io.Writer;
    024    import java.net.URL;
    025    import java.util.Iterator;
    026    import java.util.List;
    027    import javax.xml.parsers.SAXParser;
    028    import javax.xml.parsers.SAXParserFactory;
    029
    030    import org.apache.commons.lang.StringEscapeUtils;
    031    import org.apache.commons.lang.StringUtils;
    032
    033    import org.xml.sax.Attributes;
    034    import org.xml.sax.EntityResolver;
    035    import org.xml.sax.InputSource;
    036    import org.xml.sax.XMLReader;
    037    import org.xml.sax.helpers.DefaultHandler;
    038
    039    /**
    040     * This configuration implements the XML properties format introduced in Java
    041     * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
    042     * An XML properties file looks like this:
    043     *
    044     * <pre>
    045     * &lt;?xml version="1.0"?>
    046     * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    047     * &lt;properties>
    048     *   &lt;comment>Description of the property list&lt;/comment>
    049     *   &lt;entry key="key1">value1&lt;/entry>
    050     *   &lt;entry key="key2">value2&lt;/entry>
    051     *   &lt;entry key="key3">value3&lt;/entry>
    052     * &lt;/properties>
    053     * </pre>
    054     *
    055     * The Java 5.0 runtime is not required to use this class. The default encoding
    056     * for this configuration format is UTF-8. Note that unlike
    057     * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
    058     * does not support includes.
    059     *
    060     * <em>Note:</em>Configuration objects of this type can be read concurrently
    061     * by multiple threads. However if one of these threads modifies the object,
    062     * synchronization has to be performed manually.
    063     *
    064     * @author Emmanuel Bourg
    065     * @author Alistair Young
    066     * @version $Revision: 548098 $, $Date: 2007-06-17 21:34:03 +0200 (So, 17. Jun 2007) $
    067     * @since 1.1
    068     */
    069    public class XMLPropertiesConfiguration extends PropertiesConfiguration
    070    {
    071        /**
    072         * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
    073         */
    074        private static final String DEFAULT_ENCODING = "UTF-8";
    075
    076        // initialization block to set the encoding before loading the file in the constructors
    077        {
    078            setEncoding(DEFAULT_ENCODING);
    079        }
    080
    081        /**
    082         * Creates an empty XMLPropertyConfiguration object which can be
    083         * used to synthesize a new Properties file by adding values and
    084         * then saving(). An object constructed by this C'tor can not be
    085         * tickled into loading included files because it cannot supply a
    086         * base for relative includes.
    087         */
    088        public XMLPropertiesConfiguration()
    089        {
    090            super();
    091        }
    092
    093        /**
    094         * Creates and loads the xml properties from the specified file.
    095         * The specified file can contain "include" properties which then
    096         * are loaded and merged into the properties.
    097         *
    098         * @param fileName The name of the properties file to load.
    099         * @throws ConfigurationException Error while loading the properties file
    100         */
    101        public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
    102        {
    103            super(fileName);
    104        }
    105
    106        /**
    107         * Creates and loads the xml properties from the specified file.
    108         * The specified file can contain "include" properties which then
    109         * are loaded and merged into the properties.
    110         *
    111         * @param file The properties file to load.
    112         * @throws ConfigurationException Error while loading the properties file
    113         */
    114        public XMLPropertiesConfiguration(File file) throws ConfigurationException
    115        {
    116            super(file);
    117        }
    118
    119        /**
    120         * Creates and loads the xml properties from the specified URL.
    121         * The specified file can contain "include" properties which then
    122         * are loaded and merged into the properties.
    123         *
    124         * @param url The location of the properties file to load.
    125         * @throws ConfigurationException Error while loading the properties file
    126         */
    127        public XMLPropertiesConfiguration(URL url) throws ConfigurationException
    128        {
    129            super(url);
    130        }
    131
    132        public void load(Reader in) throws ConfigurationException
    133        {
    134            SAXParserFactory factory = SAXParserFactory.newInstance();
    135            factory.setNamespaceAware(false);
    136            factory.setValidating(true);
    137
    138            try
    139            {
    140                SAXParser parser = factory.newSAXParser();
    141
    142                XMLReader xmlReader = parser.getXMLReader();
    143                xmlReader.setEntityResolver(new EntityResolver()
    144                {
    145                    public InputSource resolveEntity(String publicId, String systemId)
    146                    {
    147                        return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
    148                    }
    149                });
    150                xmlReader.setContentHandler(new XMLPropertiesHandler());
    151                xmlReader.parse(new InputSource(in));
    152            }
    153            catch (Exception e)
    154            {
    155                throw new ConfigurationException("Unable to parse the configuration file", e);
    156            }
    157
    158            // todo: support included properties ?
    159        }
    160
    161        public void save(Writer out) throws ConfigurationException
    162        {
    163            PrintWriter writer = new PrintWriter(out);
    164
    165            String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
    166            writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
    167            writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
    168            writer.println("<properties>");
    169
    170            if (getHeader() != null)
    171            {
    172                writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
    173            }
    174
    175            Iterator keys = getKeys();
    176            while (keys.hasNext())
    177            {
    178                String key = (String) keys.next();
    179                Object value = getProperty(key);
    180
    181                if (value instanceof List)
    182                {
    183                    writeProperty(writer, key, (List) value);
    184                }
    185                else
    186                {
    187                    writeProperty(writer, key, value);
    188                }
    189            }
    190
    191            writer.println("</properties>");
    192            writer.flush();
    193        }
    194
    195        /**
    196         * Write a property.
    197         *
    198         * @param out the output stream
    199         * @param key the key of the property
    200         * @param value the value of the property
    201         */
    202        private void writeProperty(PrintWriter out, String key, Object value)
    203        {
    204            // escape the key
    205            String k = StringEscapeUtils.escapeXml(key);
    206
    207            if (value != null)
    208            {
    209                // escape the value
    210                String v = StringEscapeUtils.escapeXml(String.valueOf(value));
    211                v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
    212
    213                out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
    214            }
    215            else
    216            {
    217                out.println("  <entry key=\"" + k + "\"/>");
    218            }
    219        }
    220
    221        /**
    222         * Write a list property.
    223         *
    224         * @param out the output stream
    225         * @param key the key of the property
    226         * @param values a list with all property values
    227         */
    228        private void writeProperty(PrintWriter out, String key, List values)
    229        {
    230            for (int i = 0; i < values.size(); i++)
    231            {
    232                writeProperty(out, key, values.get(i));
    233            }
    234        }
    235
    236        /**
    237         * SAX Handler to parse a XML properties file.
    238         *
    239         * @author Alistair Young
    240         * @since 1.2
    241         */
    242        private class XMLPropertiesHandler extends DefaultHandler
    243        {
    244            /** The key of the current entry being parsed. */
    245            private String key;
    246
    247            /** The value of the current entry being parsed. */
    248            private StringBuffer value = new StringBuffer();
    249
    250            /** Indicates that a comment is being parsed. */
    251            private boolean inCommentElement;
    252
    253            /** Indicates that an entry is being parsed. */
    254            private boolean inEntryElement;
    255
    256            public void startElement(String uri, String localName, String qName, Attributes attrs)
    257            {
    258                if ("comment".equals(qName))
    259                {
    260                    inCommentElement = true;
    261                }
    262
    263                if ("entry".equals(qName))
    264                {
    265                    key = attrs.getValue("key");
    266                    inEntryElement = true;
    267                }
    268            }
    269
    270            public void endElement(String uri, String localName, String qName)
    271            {
    272                if (inCommentElement)
    273                {
    274                    // We've just finished a <comment> element so set the header
    275                    setHeader(value.toString());
    276                    inCommentElement = false;
    277                }
    278
    279                if (inEntryElement)
    280                {
    281                    // We've just finished an <entry> element, so add the key/value pair
    282                    addProperty(key, value.toString());
    283                    inEntryElement = false;
    284                }
    285
    286                // Clear the element value buffer
    287                value = new StringBuffer();
    288            }
    289
    290            public void characters(char[] chars, int start, int length)
    291            {
    292                /**
    293                 * We're currently processing an element. All character data from now until
    294                 * the next endElement() call will be the data for this  element.
    295                 */
    296                value.append(chars, start, length);
    297            }
    298        }
    299    }