View Javadoc

1   /*
2    * Copyright 2004-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.io.File;
20  import java.io.PrintWriter;
21  import java.io.Reader;
22  import java.io.Writer;
23  import java.net.URL;
24  import java.util.Iterator;
25  import java.util.List;
26  import javax.xml.parsers.SAXParser;
27  import javax.xml.parsers.SAXParserFactory;
28  
29  import org.apache.commons.lang.StringEscapeUtils;
30  import org.apache.commons.lang.StringUtils;
31  
32  import org.xml.sax.Attributes;
33  import org.xml.sax.EntityResolver;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.XMLReader;
36  import org.xml.sax.helpers.DefaultHandler;
37  
38  /***
39   * This configuration implements the XML properties format introduced in Java
40   * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
41   * An XML properties file looks like this:
42   *
43   * <pre>
44   * &lt;?xml version="1.0"?>
45   * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
46   * &lt;properties>
47   *   &lt;comment>Description of the property list&lt;/comment>
48   *   &lt;entry key="key1">value1&lt;/entry>
49   *   &lt;entry key="key2">value2&lt;/entry>
50   *   &lt;entry key="key3">value3&lt;/entry>
51   * &lt;/properties>
52   * </pre>
53   *
54   * The Java 5.0 runtime is not required to use this class. The default encoding
55   * for this configuration format is UTF-8. Note that unlike
56   * <code>PropertiesConfiguration</code>, <code>XMLPropertiesConfiguration</code>
57   * does not support includes.
58   *
59   * @author Emmanuel Bourg
60   * @author Alistair Young
61   * @version $Revision$, $Date: 2005-10-09 20:27:12 +0200 (Sun, 09 Oct 2005) $
62   * @since 1.1
63   */
64  public class XMLPropertiesConfiguration extends PropertiesConfiguration
65  {
66      /***
67       * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
68       */
69      private static final String DEFAULT_ENCODING = "UTF-8";
70  
71      // initialization block to set the encoding before loading the file in the constructors
72      {
73          setEncoding(DEFAULT_ENCODING);
74      }
75  
76      /***
77       * Creates an empty XMLPropertyConfiguration object which can be
78       * used to synthesize a new Properties file by adding values and
79       * then saving(). An object constructed by this C'tor can not be
80       * tickled into loading included files because it cannot supply a
81       * base for relative includes.
82       */
83      public XMLPropertiesConfiguration()
84      {
85          super();
86      }
87  
88      /***
89       * Creates and loads the xml properties from the specified file.
90       * The specified file can contain "include" properties which then
91       * are loaded and merged into the properties.
92       *
93       * @param fileName The name of the properties file to load.
94       * @throws ConfigurationException Error while loading the properties file
95       */
96      public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
97      {
98          super(fileName);
99      }
100 
101     /***
102      * Creates and loads the xml properties from the specified file.
103      * The specified file can contain "include" properties which then
104      * are loaded and merged into the properties.
105      *
106      * @param file The properties file to load.
107      * @throws ConfigurationException Error while loading the properties file
108      */
109     public XMLPropertiesConfiguration(File file) throws ConfigurationException
110     {
111         super(file);
112     }
113 
114     /***
115      * Creates and loads the xml properties from the specified URL.
116      * The specified file can contain "include" properties which then
117      * are loaded and merged into the properties.
118      *
119      * @param url The location of the properties file to load.
120      * @throws ConfigurationException Error while loading the properties file
121      */
122     public XMLPropertiesConfiguration(URL url) throws ConfigurationException
123     {
124         super(url);
125     }
126 
127     public void load(Reader in) throws ConfigurationException
128     {
129         SAXParserFactory factory = SAXParserFactory.newInstance();
130         factory.setNamespaceAware(false);
131         factory.setValidating(true);
132 
133         try
134         {
135             SAXParser parser = factory.newSAXParser();
136 
137             XMLReader xmlReader = parser.getXMLReader();
138             xmlReader.setEntityResolver(new EntityResolver()
139             {
140                 public InputSource resolveEntity(String publicId, String systemId)
141                 {
142                     return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
143                 }
144             });
145             xmlReader.setContentHandler(new XMLPropertiesHandler());
146             xmlReader.parse(new InputSource(in));
147         }
148         catch (Exception e)
149         {
150             throw new ConfigurationException("Unable to parse the configuration file", e);
151         }
152 
153         // todo: support included properties ?
154     }
155 
156     public void save(Writer out) throws ConfigurationException
157     {
158         PrintWriter writer = new PrintWriter(out);
159 
160         String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
161         writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
162         writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
163         writer.println("<properties>");
164 
165         if (getHeader() != null)
166         {
167             writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
168         }
169 
170         Iterator keys = getKeys();
171         while (keys.hasNext())
172         {
173             String key = (String) keys.next();
174             Object value = getProperty(key);
175 
176             if (value instanceof List)
177             {
178                 writeProperty(writer, key, (List) value);
179             }
180             else
181             {
182                 writeProperty(writer, key, value);
183             }
184         }
185 
186         writer.println("</properties>");
187         writer.flush();
188     }
189 
190     /***
191      * Write a property.
192      *
193      * @param out the output stream
194      * @param key the key of the property
195      * @param value the value of the property
196      */
197     private void writeProperty(PrintWriter out, String key, Object value)
198     {
199         // escape the key
200         String k = StringEscapeUtils.escapeXml(key);
201 
202         if (value != null)
203         {
204             // escape the value
205             String v = StringEscapeUtils.escapeXml(String.valueOf(value));
206             v = StringUtils.replace(v, String.valueOf(getDelimiter()), "//" + getDelimiter());
207 
208             out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
209         }
210         else
211         {
212             out.println("  <entry key=\"" + k + "\"/>");
213         }
214     }
215 
216     /***
217      * Write a list property.
218      *
219      * @param out the output stream
220      * @param key the key of the property
221      * @param values a list with all property values
222      */
223     private void writeProperty(PrintWriter out, String key, List values)
224     {
225         for (int i = 0; i < values.size(); i++)
226         {
227             writeProperty(out, key, values.get(i));
228         }
229     }
230 
231     /***
232      * SAX Handler to parse a XML properties file.
233      *
234      * @author Alistair Young
235      * @since 1.2
236      */
237     private class XMLPropertiesHandler extends DefaultHandler
238     {
239         /*** The key of the current entry being parsed. */
240         private String key;
241 
242         /*** The value of the current entry being parsed. */
243         private StringBuffer value = new StringBuffer();
244 
245         /*** Indicates that a comment is being parsed. */
246         private boolean inCommentElement;
247 
248         /*** Indicates that an entry is being parsed. */
249         private boolean inEntryElement;
250 
251         public void startElement(String uri, String localName, String qName, Attributes attrs)
252         {
253             if ("comment".equals(qName))
254             {
255                 inCommentElement = true;
256             }
257 
258             if ("entry".equals(qName))
259             {
260                 key = attrs.getValue("key");
261                 inEntryElement = true;
262             }
263         }
264 
265         public void endElement(String uri, String localName, String qName)
266         {
267             if (inCommentElement)
268             {
269                 // We've just finished a <comment> element so set the header
270                 setHeader(value.toString());
271                 inCommentElement = false;
272             }
273 
274             if (inEntryElement)
275             {
276                 // We've just finished an <entry> element, so add the key/value pair
277                 addProperty(key, value.toString());
278                 inEntryElement = false;
279             }
280 
281             // Clear the element value buffer
282             value = new StringBuffer();
283         }
284 
285         public void characters(char[] chars, int start, int length)
286         {
287             /***
288              * We're currently processing an element. All character data from now until
289              * the next endElement() call will be the data for this  element.
290              */
291             value.append(chars, start, length);
292         }
293     }
294 }