Coverage Report - org.apache.commons.configuration.XMLPropertiesConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLPropertiesConfiguration
82%
79/96
59%
19/32
2,412
XMLPropertiesConfiguration$1
100%
2/2
N/A
2,412
XMLPropertiesConfiguration$XMLPropertiesHandler
100%
18/18
100%
8/8
2,412
 
 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  
 
 28  
 import javax.xml.parsers.SAXParser;
 29  
 import javax.xml.parsers.SAXParserFactory;
 30  
 
 31  
 import org.apache.commons.lang.StringEscapeUtils;
 32  
 import org.apache.commons.lang.StringUtils;
 33  
 import org.w3c.dom.Document;
 34  
 import org.w3c.dom.Element;
 35  
 import org.w3c.dom.Node;
 36  
 import org.w3c.dom.NodeList;
 37  
 import org.xml.sax.Attributes;
 38  
 import org.xml.sax.EntityResolver;
 39  
 import org.xml.sax.InputSource;
 40  
 import org.xml.sax.XMLReader;
 41  
 import org.xml.sax.helpers.DefaultHandler;
 42  
 
 43  
 /**
 44  
  * This configuration implements the XML properties format introduced in Java
 45  
  * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
 46  
  * An XML properties file looks like this:
 47  
  *
 48  
  * <pre>
 49  
  * &lt;?xml version="1.0"?>
 50  
  * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
 51  
  * &lt;properties>
 52  
  *   &lt;comment>Description of the property list&lt;/comment>
 53  
  *   &lt;entry key="key1">value1&lt;/entry>
 54  
  *   &lt;entry key="key2">value2&lt;/entry>
 55  
  *   &lt;entry key="key3">value3&lt;/entry>
 56  
  * &lt;/properties>
 57  
  * </pre>
 58  
  *
 59  
  * The Java 5.0 runtime is not required to use this class. The default encoding
 60  
  * for this configuration format is UTF-8. Note that unlike
 61  
  * {@code PropertiesConfiguration}, {@code XMLPropertiesConfiguration}
 62  
  * does not support includes.
 63  
  *
 64  
  * <em>Note:</em>Configuration objects of this type can be read concurrently
 65  
  * by multiple threads. However if one of these threads modifies the object,
 66  
  * synchronization has to be performed manually.
 67  
  *
 68  
  * @author Emmanuel Bourg
 69  
  * @author Alistair Young
 70  
  * @version $Id: XMLPropertiesConfiguration.java 1534399 2013-10-21 22:25:03Z henning $
 71  
  * @since 1.1
 72  
  */
 73  
 public class XMLPropertiesConfiguration extends PropertiesConfiguration
 74  
 {
 75  
     /**
 76  
      * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
 77  
      */
 78  
     private static final String DEFAULT_ENCODING = "UTF-8";
 79  
 
 80  
     /**
 81  
      * Default string used when the XML is malformed
 82  
      */
 83  
     private static final String MALFORMED_XML_EXCEPTION = "Malformed XML";
 84  
 
 85  
     // initialization block to set the encoding before loading the file in the constructors
 86  
     {
 87  25
         setEncoding(DEFAULT_ENCODING);
 88  
     }
 89  
 
 90  
     /**
 91  
      * Creates an empty XMLPropertyConfiguration object which can be
 92  
      * used to synthesize a new Properties file by adding values and
 93  
      * then saving(). An object constructed by this C'tor can not be
 94  
      * tickled into loading included files because it cannot supply a
 95  
      * base for relative includes.
 96  
      */
 97  
     public XMLPropertiesConfiguration()
 98  
     {
 99  20
         super();
 100  20
     }
 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 fileName The name of the properties file to load.
 108  
      * @throws ConfigurationException Error while loading the properties file
 109  
      */
 110  
     public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
 111  
     {
 112  3
         super(fileName);
 113  3
     }
 114  
 
 115  
     /**
 116  
      * Creates and loads the xml properties from the specified file.
 117  
      * The specified file can contain "include" properties which then
 118  
      * are loaded and merged into the properties.
 119  
      *
 120  
      * @param file The properties file to load.
 121  
      * @throws ConfigurationException Error while loading the properties file
 122  
      */
 123  
     public XMLPropertiesConfiguration(File file) throws ConfigurationException
 124  
     {
 125  1
         super(file);
 126  1
     }
 127  
 
 128  
     /**
 129  
      * Creates and loads the xml properties from the specified URL.
 130  
      * The specified file can contain "include" properties which then
 131  
      * are loaded and merged into the properties.
 132  
      *
 133  
      * @param url The location of the properties file to load.
 134  
      * @throws ConfigurationException Error while loading the properties file
 135  
      */
 136  
     public XMLPropertiesConfiguration(URL url) throws ConfigurationException
 137  
     {
 138  0
         super(url);
 139  0
     }
 140  
 
 141  
     /**
 142  
      * Creates and loads the xml properties from the specified DOM node.
 143  
      *
 144  
      * @param element The DOM element
 145  
      * @throws ConfigurationException Error while loading the properties file
 146  
      * @since 2.0
 147  
      */
 148  
     public XMLPropertiesConfiguration(Element element) throws ConfigurationException
 149  
     {
 150  1
         super();
 151  1
         this.load(element);
 152  1
     }
 153  
 
 154  
     @Override
 155  
     public void load(Reader in) throws ConfigurationException
 156  
     {
 157  24
         SAXParserFactory factory = SAXParserFactory.newInstance();
 158  24
         factory.setNamespaceAware(false);
 159  24
         factory.setValidating(true);
 160  
 
 161  
         try
 162  
         {
 163  24
             SAXParser parser = factory.newSAXParser();
 164  
 
 165  24
             XMLReader xmlReader = parser.getXMLReader();
 166  24
             xmlReader.setEntityResolver(new EntityResolver()
 167  24
             {
 168  
                 public InputSource resolveEntity(String publicId, String systemId)
 169  
                 {
 170  24
                     return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
 171  
                 }
 172  
             });
 173  24
             xmlReader.setContentHandler(new XMLPropertiesHandler());
 174  24
             xmlReader.parse(new InputSource(in));
 175  
         }
 176  0
         catch (Exception e)
 177  
         {
 178  0
             throw new ConfigurationException("Unable to parse the configuration file", e);
 179  24
         }
 180  
 
 181  
         // todo: support included properties ?
 182  24
     }
 183  
 
 184  
     /**
 185  
      * Parses a DOM element containing the properties. The DOM element has to follow
 186  
      * the XML properties format introduced in Java 5.0,
 187  
      * see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html
 188  
      *
 189  
      * @param element The DOM element
 190  
      * @throws ConfigurationException Error while interpreting the DOM
 191  
      * @since 2.0
 192  
      */
 193  
     public void load(Element element) throws ConfigurationException
 194  
     {
 195  1
         if (!element.getNodeName().equals("properties"))
 196  
         {
 197  0
             throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
 198  
         }
 199  1
         NodeList childNodes = element.getChildNodes();
 200  10
         for (int i = 0; i < childNodes.getLength(); i++)
 201  
         {
 202  9
             Node item = childNodes.item(i);
 203  9
             if (item instanceof Element)
 204  
             {
 205  4
                 if (item.getNodeName().equals("comment"))
 206  
                 {
 207  1
                     setHeader(item.getTextContent());
 208  
                 }
 209  3
                 else if (item.getNodeName().equals("entry"))
 210  
                 {
 211  3
                     String key = ((Element) item).getAttribute("key");
 212  3
                     addProperty(key, item.getTextContent());
 213  3
                 }
 214  
                 else
 215  
                 {
 216  0
                     throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
 217  
                 }
 218  
             }
 219  
         }
 220  1
     }
 221  
 
 222  
     @Override
 223  
     public void save(Writer out) throws ConfigurationException
 224  
     {
 225  1
         PrintWriter writer = new PrintWriter(out);
 226  
 
 227  1
         String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
 228  1
         writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
 229  1
         writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
 230  1
         writer.println("<properties>");
 231  
 
 232  1
         if (getHeader() != null)
 233  
         {
 234  1
             writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
 235  
         }
 236  
 
 237  1
         Iterator<String> keys = getKeys();
 238  4
         while (keys.hasNext())
 239  
         {
 240  3
             String key = keys.next();
 241  3
             Object value = getProperty(key);
 242  
 
 243  3
             if (value instanceof List)
 244  
             {
 245  0
                 writeProperty(writer, key, (List<?>) value);
 246  
             }
 247  
             else
 248  
             {
 249  3
                 writeProperty(writer, key, value);
 250  
             }
 251  3
         }
 252  
 
 253  1
         writer.println("</properties>");
 254  1
         writer.flush();
 255  1
     }
 256  
 
 257  
     /**
 258  
      * Write a property.
 259  
      *
 260  
      * @param out the output stream
 261  
      * @param key the key of the property
 262  
      * @param value the value of the property
 263  
      */
 264  
     private void writeProperty(PrintWriter out, String key, Object value)
 265  
     {
 266  
         // escape the key
 267  3
         String k = StringEscapeUtils.escapeXml(key);
 268  
 
 269  3
         if (value != null)
 270  
         {
 271  
             // escape the value
 272  3
             String v = StringEscapeUtils.escapeXml(String.valueOf(value));
 273  3
             v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
 274  
 
 275  3
             out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
 276  3
         }
 277  
         else
 278  
         {
 279  0
             out.println("  <entry key=\"" + k + "\"/>");
 280  
         }
 281  3
     }
 282  
 
 283  
     /**
 284  
      * Write a list property.
 285  
      *
 286  
      * @param out the output stream
 287  
      * @param key the key of the property
 288  
      * @param values a list with all property values
 289  
      */
 290  
     private void writeProperty(PrintWriter out, String key, List<?> values)
 291  
     {
 292  0
         for (Object value : values)
 293  
         {
 294  0
             writeProperty(out, key, value);
 295  0
         }
 296  0
     }
 297  
 
 298  
     /**
 299  
      * Writes the configuration as child to the given DOM node
 300  
      *
 301  
      * @param document The DOM document to add the configuration to
 302  
      * @param parent The DOM parent node
 303  
      * @since 2.0
 304  
      */
 305  
     public void save(Document document, Node parent)
 306  
     {
 307  1
         Element properties = document.createElement("properties");
 308  1
         parent.appendChild(properties);
 309  1
         if (getHeader() != null)
 310  
         {
 311  1
             Element comment = document.createElement("comment");
 312  1
             properties.appendChild(comment);
 313  1
             comment.setTextContent(StringEscapeUtils.escapeXml(getHeader()));
 314  
         }
 315  
 
 316  1
         Iterator<String> keys = getKeys();
 317  4
         while (keys.hasNext())
 318  
         {
 319  3
             String key = keys.next();
 320  3
             Object value = getProperty(key);
 321  
 
 322  3
             if (value instanceof List)
 323  
             {
 324  0
                 writeProperty(document, properties, key, (List<?>) value);
 325  
             }
 326  
             else
 327  
             {
 328  3
                 writeProperty(document, properties, key, value);
 329  
             }
 330  3
         }
 331  1
     }
 332  
 
 333  
     private void writeProperty(Document document, Node properties, String key, Object value)
 334  
     {
 335  3
         Element entry = document.createElement("entry");
 336  3
         properties.appendChild(entry);
 337  
 
 338  
         // escape the key
 339  3
         String k = StringEscapeUtils.escapeXml(key);
 340  3
         entry.setAttribute("key", k);
 341  
 
 342  3
         if (value != null)
 343  
         {
 344  
             // escape the value
 345  3
             String v = StringEscapeUtils.escapeXml(String.valueOf(value));
 346  3
             v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
 347  3
             entry.setTextContent(v);
 348  
         }
 349  3
     }
 350  
 
 351  
     private void writeProperty(Document document, Node properties, String key, List<?> values)
 352  
     {
 353  0
         for (Object value : values)
 354  
         {
 355  0
             writeProperty(document, properties, key, value);
 356  0
         }
 357  0
     }
 358  
 
 359  
     /**
 360  
      * SAX Handler to parse a XML properties file.
 361  
      *
 362  
      * @author Alistair Young
 363  
      * @since 1.2
 364  
      */
 365  48
     private class XMLPropertiesHandler extends DefaultHandler
 366  
     {
 367  
         /** The key of the current entry being parsed. */
 368  
         private String key;
 369  
 
 370  
         /** The value of the current entry being parsed. */
 371  24
         private StringBuilder value = new StringBuilder();
 372  
 
 373  
         /** Indicates that a comment is being parsed. */
 374  
         private boolean inCommentElement;
 375  
 
 376  
         /** Indicates that an entry is being parsed. */
 377  
         private boolean inEntryElement;
 378  
 
 379  
         @Override
 380  
         public void startElement(String uri, String localName, String qName, Attributes attrs)
 381  
         {
 382  120
             if ("comment".equals(qName))
 383  
             {
 384  24
                 inCommentElement = true;
 385  
             }
 386  
 
 387  120
             if ("entry".equals(qName))
 388  
             {
 389  72
                 key = attrs.getValue("key");
 390  72
                 inEntryElement = true;
 391  
             }
 392  120
         }
 393  
 
 394  
         @Override
 395  
         public void endElement(String uri, String localName, String qName)
 396  
         {
 397  120
             if (inCommentElement)
 398  
             {
 399  
                 // We've just finished a <comment> element so set the header
 400  24
                 setHeader(value.toString());
 401  24
                 inCommentElement = false;
 402  
             }
 403  
 
 404  120
             if (inEntryElement)
 405  
             {
 406  
                 // We've just finished an <entry> element, so add the key/value pair
 407  72
                 addProperty(key, value.toString());
 408  72
                 inEntryElement = false;
 409  
             }
 410  
 
 411  
             // Clear the element value buffer
 412  120
             value = new StringBuilder();
 413  120
         }
 414  
 
 415  
         @Override
 416  
         public void characters(char[] chars, int start, int length)
 417  
         {
 418  
             /**
 419  
              * We're currently processing an element. All character data from now until
 420  
              * the next endElement() call will be the data for this  element.
 421  
              */
 422  96
             value.append(chars, start, length);
 423  96
         }
 424  
     }
 425  
 }