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   * @author Emmanuel Bourg
    61   * @author Alistair Young
    62   * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $
    63   * @since 1.1
    64   */
    65  public class XMLPropertiesConfiguration extends PropertiesConfiguration
    66  {
    67      /***
    68       * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
    69       */
    70      private static final String DEFAULT_ENCODING = "UTF-8";
    71
    72      // initialization block to set the encoding before loading the file in the constructors
    73      {
    74          setEncoding(DEFAULT_ENCODING);
    75      }
    76
    77      /***
    78       * Creates an empty XMLPropertyConfiguration object which can be
    79       * used to synthesize a new Properties file by adding values and
    80       * then saving(). An object constructed by this C'tor can not be
    81       * tickled into loading included files because it cannot supply a
    82       * base for relative includes.
    83       */
    84      public XMLPropertiesConfiguration()
    85      {
    86          super();
    87      }
    88
    89      /***
    90       * Creates and loads the xml properties from the specified file.
    91       * The specified file can contain "include" properties which then
    92       * are loaded and merged into the properties.
    93       *
    94       * @param fileName The name of the properties file to load.
    95       * @throws ConfigurationException Error while loading the properties file
    96       */
    97      public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
    98      {
    99          super(fileName);
    100     }
    101
    102     /***
    103      * Creates and loads the xml properties from the specified file.
    104      * The specified file can contain "include" properties which then
    105      * are loaded and merged into the properties.
    106      *
    107      * @param file The properties file to load.
    108      * @throws ConfigurationException Error while loading the properties file
    109      */
    110     public XMLPropertiesConfiguration(File file) throws ConfigurationException
    111     {
    112         super(file);
    113     }
    114
    115     /***
    116      * Creates and loads the xml properties from the specified URL.
    117      * The specified file can contain "include" properties which then
    118      * are loaded and merged into the properties.
    119      *
    120      * @param url The location of the properties file to load.
    121      * @throws ConfigurationException Error while loading the properties file
    122      */
    123     public XMLPropertiesConfiguration(URL url) throws ConfigurationException
    124     {
    125         super(url);
    126     }
    127
    128     public void load(Reader in) throws ConfigurationException
    129     {
    130         SAXParserFactory factory = SAXParserFactory.newInstance();
    131         factory.setNamespaceAware(false);
    132         factory.setValidating(true);
    133
    134         try
    135         {
    136             SAXParser parser = factory.newSAXParser();
    137
    138             XMLReader xmlReader = parser.getXMLReader();
    139             xmlReader.setEntityResolver(new EntityResolver()
    140             {
    141                 public InputSource resolveEntity(String publicId, String systemId)
    142                 {
    143                     return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
    144                 }
    145             });
    146             xmlReader.setContentHandler(new XMLPropertiesHandler());
    147             xmlReader.parse(new InputSource(in));
    148         }
    149         catch (Exception e)
    150         {
    151             throw new ConfigurationException("Unable to parse the configuration file", e);
    152         }
    153
    154         // todo: support included properties ?
    155     }
    156
    157     public void save(Writer out) throws ConfigurationException
    158     {
    159         PrintWriter writer = new PrintWriter(out);
    160
    161         String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
    162         writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
    163         writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
    164         writer.println("<properties>");
    165
    166         if (getHeader() != null)
    167         {
    168             writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
    169         }
    170
    171         Iterator keys = getKeys();
    172         while (keys.hasNext())
    173         {
    174             String key = (String) keys.next();
    175             Object value = getProperty(key);
    176
    177             if (value instanceof List)
    178             {
    179                 writeProperty(writer, key, (List) value);
    180             }
    181             else
    182             {
    183                 writeProperty(writer, key, value);
    184             }
    185         }
    186
    187         writer.println("</properties>");
    188         writer.flush();
    189     }
    190
    191     /***
    192      * Write a property.
    193      *
    194      * @param out the output stream
    195      * @param key the key of the property
    196      * @param value the value of the property
    197      */
    198     private void writeProperty(PrintWriter out, String key, Object value)
    199     {
    200         // escape the key
    201         String k = StringEscapeUtils.escapeXml(key);
    202
    203         if (value != null)
    204         {
    205             // escape the value
    206             String v = StringEscapeUtils.escapeXml(String.valueOf(value));
    207             v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "//" + getListDelimiter());
    208
    209             out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
    210         }
    211         else
    212         {
    213             out.println("  <entry key=\"" + k + "\"/>");
    214         }
    215     }
    216
    217     /***
    218      * Write a list property.
    219      *
    220      * @param out the output stream
    221      * @param key the key of the property
    222      * @param values a list with all property values
    223      */
    224     private void writeProperty(PrintWriter out, String key, List values)
    225     {
    226         for (int i = 0; i < values.size(); i++)
    227         {
    228             writeProperty(out, key, values.get(i));
    229         }
    230     }
    231
    232     /***
    233      * SAX Handler to parse a XML properties file.
    234      *
    235      * @author Alistair Young
    236      * @since 1.2
    237      */
    238     private class XMLPropertiesHandler extends DefaultHandler
    239     {
    240         /*** The key of the current entry being parsed. */
    241         private String key;
    242
    243         /*** The value of the current entry being parsed. */
    244         private StringBuffer value = new StringBuffer();
    245
    246         /*** Indicates that a comment is being parsed. */
    247         private boolean inCommentElement;
    248
    249         /*** Indicates that an entry is being parsed. */
    250         private boolean inEntryElement;
    251
    252         public void startElement(String uri, String localName, String qName, Attributes attrs)
    253         {
    254             if ("comment".equals(qName))
    255             {
    256                 inCommentElement = true;
    257             }
    258
    259             if ("entry".equals(qName))
    260             {
    261                 key = attrs.getValue("key");
    262                 inEntryElement = true;
    263             }
    264         }
    265
    266         public void endElement(String uri, String localName, String qName)
    267         {
    268             if (inCommentElement)
    269             {
    270                 // We've just finished a <comment> element so set the header
    271                 setHeader(value.toString());
    272                 inCommentElement = false;
    273             }
    274
    275             if (inEntryElement)
    276             {
    277                 // We've just finished an <entry> element, so add the key/value pair
    278                 addProperty(key, value.toString());
    279                 inEntryElement = false;
    280             }
    281
    282             // Clear the element value buffer
    283             value = new StringBuffer();
    284         }
    285
    286         public void characters(char[] chars, int start, int length)
    287         {
    288             /***
    289              * We're currently processing an element. All character data from now until
    290              * the next endElement() call will be the data for this  element.
    291              */
    292             value.append(chars, start, length);
    293         }
    294     }
    295 }