1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 * <?xml version="1.0"?>
46 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
47 * <properties>
48 * <comment>Description of the property list</comment>
49 * <entry key="key1">value1</entry>
50 * <entry key="key2">value2</entry>
51 * <entry key="key3">value3</entry>
52 * </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
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
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
205 String k = StringEscapeUtils.escapeXml(key);
206
207 if (value != null)
208 {
209
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
275 setHeader(value.toString());
276 inCommentElement = false;
277 }
278
279 if (inEntryElement)
280 {
281
282 addProperty(key, value.toString());
283 inEntryElement = false;
284 }
285
286
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 }