View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.commons.configuration;
    19
    20  import java.io.File;
    21  import java.io.PrintWriter;
    22  import java.io.Reader;
    23  import java.io.Writer;
    24  import java.net.URL;
    25  import java.util.Iterator;
    26  import java.util.List;
    27  import javax.xml.parsers.SAXParser;
    28  import javax.xml.parsers.SAXParserFactory;
    29
    30  import org.apache.commons.lang.StringEscapeUtils;
    31  import org.apache.commons.lang.StringUtils;
    32
    33  import org.xml.sax.Attributes;
    34  import org.xml.sax.EntityResolver;
    35  import org.xml.sax.InputSource;
    36  import org.xml.sax.XMLReader;
    37  import org.xml.sax.helpers.DefaultHandler;
    38
    39  /***
    40   * This configuration implements the XML properties format introduced in Java
    41   * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
    42   * An XML properties file looks like this:
    43   *
    44   * <pre>
    45   * &lt;?xml version="1.0"?>
    46   * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    47   * &lt;properties>
    48   *   &lt;comment>Description of the property list&lt;/comment>
    49   *   &lt;entry key="key1">value1&lt;/entry>
    50   *   &lt;entry key="key2">value2&lt;/entry>
    51   *   &lt;entry key="key3">value3&lt;/entry>
    52   * &lt;/properties>
    53   * </pre>
    54   *
    55   * The Java 5.0 runtime is not required to use this class. The default encoding
    56   * for this configuration format is UTF-8. Note that unlike
    57   * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
    58   * does not support includes.
    59   *
    60   * <em>Note:</em>Configuration objects of this type can be read concurrently
    61   * by multiple threads. However if one of these threads modifies the object,
    62   * synchronization has to be performed manually.
    63   *
    64   * @author Emmanuel Bourg
    65   * @author Alistair Young
    66   * @version $Revision: 548098 $, $Date: 2007-06-17 21:34:03 +0200 (So, 17 Jun 2007) $
    67   * @since 1.1
    68   */
    69  public class XMLPropertiesConfiguration extends PropertiesConfiguration
    70  {
    71      /***
    72       * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
    73       */
    74      private static final String DEFAULT_ENCODING = "UTF-8";
    75
    76      // initialization block to set the encoding before loading the file in the constructors
    77      {
    78          setEncoding(DEFAULT_ENCODING);
    79      }
    80
    81      /***
    82       * Creates an empty XMLPropertyConfiguration object which can be
    83       * used to synthesize a new Properties file by adding values and
    84       * then saving(). An object constructed by this C'tor can not be
    85       * tickled into loading included files because it cannot supply a
    86       * base for relative includes.
    87       */
    88      public XMLPropertiesConfiguration()
    89      {
    90          super();
    91      }
    92
    93      /***
    94       * Creates and loads the xml properties from the specified file.
    95       * The specified file can contain "include" properties which then
    96       * are loaded and merged into the properties.
    97       *
    98       * @param fileName The name of the properties file to load.
    99       * @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 }