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 }