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 }