KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > util > XMLProperties


1 /**
2  * $RCSfile: XMLProperties.java,v $
3  * $Revision: 1.9 $
4  * $Date: 2005/05/29 00:11:02 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.util;
13
14 import org.dom4j.Document;
15 import org.dom4j.Element;
16 import org.dom4j.io.OutputFormat;
17 import org.dom4j.io.SAXReader;
18
19 import java.io.*;
20 import java.util.*;
21
22 /**
23  * Provides the the ability to use simple XML property files. Each property is
24  * in the form X.Y.Z, which would map to an XML snippet of:
25  * <pre>
26  * &lt;X&gt;
27  * &lt;Y&gt;
28  * &lt;Z&gt;someValue&lt;/Z&gt;
29  * &lt;/Y&gt;
30  * &lt;/X&gt;
31  * </pre>
32  * <p/>
33  * The XML file is passed in to the constructor and must be readable and
34  * writtable. Setting property values will automatically persist those value
35  * to disk. The file encoding used is UTF-8.
36  *
37  * @author Derek DeMoro
38  * @author Iain Shigeoka
39  */

40 public class XMLProperties {
41
42     private File file;
43     private Document document;
44
45     /**
46      * Parsing the XML file every time we need a property is slow. Therefore,
47      * we use a Map to cache property values that are accessed more than once.
48      */

49     private Map propertyCache = new HashMap();
50
51     /**
52      * Creates a new XMLPropertiesTest object.
53      *
54      * @param fileName the full path the file that properties should be read from
55      * and written to.
56      * @throws IOException if an error occurs loading the properties.
57      */

58     public XMLProperties(String JavaDoc fileName) throws IOException {
59         this(new File(fileName));
60     }
61
62     /**
63      * Loads XML properties from a stream.
64      *
65      * @param in the input stream of XML.
66      * @throws IOException if an exception occurs when reading the stream.
67      */

68     public XMLProperties(InputStream in) throws IOException {
69         Reader reader = new BufferedReader(new InputStreamReader(in));
70         buildDoc(reader);
71     }
72
73     /**
74      * Creates a new XMLPropertiesTest object.
75      *
76      * @param file the file that properties should be read from and written to.
77      * @throws IOException if an error occurs loading the properties.
78      */

79     public XMLProperties(File file) throws IOException {
80         this.file = file;
81         if (!file.exists()) {
82             // Attempt to recover from this error case by seeing if the
83
// tmp file exists. It's possible that the rename of the
84
// tmp file failed the last time Jive was running,
85
// but that it exists now.
86
File tempFile;
87             tempFile = new File(file.getParentFile(), file.getName() + ".tmp");
88             if (tempFile.exists()) {
89                 Log.error("WARNING: " + file.getName() + " was not found, but temp file from " +
90                         "previous write operation was. Attempting automatic recovery." +
91                         " Please check file for data consistency.");
92                 tempFile.renameTo(file);
93             }
94             // There isn't a possible way to recover from the file not
95
// being there, so throw an error.
96
else {
97                 throw new FileNotFoundException("XML properties file does not exist: "
98                         + file.getName());
99             }
100         }
101         // Check read and write privs.
102
if (!file.canRead()) {
103             throw new IOException("XML properties file must be readable: " + file.getName());
104         }
105         if (!file.canWrite()) {
106             throw new IOException("XML properties file must be writable: " + file.getName());
107         }
108
109         FileReader reader = new FileReader(file);
110         buildDoc(reader);
111     }
112
113     /**
114      * Returns the value of the specified property.
115      *
116      * @param name the name of the property to get.
117      * @return the value of the specified property.
118      */

119     public synchronized String JavaDoc getProperty(String JavaDoc name) {
120         String JavaDoc value = (String JavaDoc)propertyCache.get(name);
121         if (value != null) {
122             return value;
123         }
124
125         String JavaDoc[] propName = parsePropertyName(name);
126         // Search for this property by traversing down the XML heirarchy.
127
Element element = document.getRootElement();
128         for (int i = 0; i < propName.length; i++) {
129             element = element.element(propName[i]);
130             if (element == null) {
131                 // This node doesn't match this part of the property name which
132
// indicates this property doesn't exist so return null.
133
return null;
134             }
135         }
136         // At this point, we found a matching property, so return its value.
137
// Empty strings are returned as null.
138
value = element.getTextTrim();
139         if ("".equals(value)) {
140             return null;
141         }
142         else {
143             // Add to cache so that getting property next time is fast.
144
propertyCache.put(name, value);
145             return value;
146         }
147     }
148
149     /**
150      * Return all values who's path matches the given property
151      * name as a String array, or an empty array if the if there
152      * are no children. This allows you to retrieve several values
153      * with the same property name. For example, consider the
154      * XML file entry:
155      * <pre>
156      * &lt;foo&gt;
157      * &lt;bar&gt;
158      * &lt;prop&gt;some value&lt;/prop&gt;
159      * &lt;prop&gt;other value&lt;/prop&gt;
160      * &lt;prop&gt;last value&lt;/prop&gt;
161      * &lt;/bar&gt;
162      * &lt;/foo&gt;
163      * </pre>
164      * If you call getProperties("foo.bar.prop") will return a string array containing
165      * {"some value", "other value", "last value"}.
166      *
167      * @param name the name of the property to retrieve
168      * @return all child property values for the given node name.
169      */

170     public String JavaDoc[] getProperties(String JavaDoc name) {
171         String JavaDoc[] propName = parsePropertyName(name);
172         // Search for this property by traversing down the XML heirarchy,
173
// stopping one short.
174
Element element = document.getRootElement();
175         for (int i = 0; i < propName.length - 1; i++) {
176             element = element.element(propName[i]);
177             if (element == null) {
178                 // This node doesn't match this part of the property name which
179
// indicates this property doesn't exist so return empty array.
180
return new String JavaDoc[]{};
181             }
182         }
183         // We found matching property, return names of children.
184
Iterator iter = element.elementIterator(propName[propName.length - 1]);
185         ArrayList props = new ArrayList();
186         String JavaDoc value;
187         while (iter.hasNext()) {
188             // Empty strings are skipped.
189
value = ((Element)iter.next()).getTextTrim();
190             if (!"".equals(value)) {
191                 props.add(value);
192             }
193         }
194         String JavaDoc[] childrenNames = new String JavaDoc[props.size()];
195         return (String JavaDoc[])props.toArray(childrenNames);
196     }
197
198     /**
199      * Return all values who's path matches the given property
200      * name as a String array, or an empty array if the if there
201      * are no children. This allows you to retrieve several values
202      * with the same property name. For example, consider the
203      * XML file entry:
204      * <pre>
205      * &lt;foo&gt;
206      * &lt;bar&gt;
207      * &lt;prop&gt;some value&lt;/prop&gt;
208      * &lt;prop&gt;other value&lt;/prop&gt;
209      * &lt;prop&gt;last value&lt;/prop&gt;
210      * &lt;/bar&gt;
211      * &lt;/foo&gt;
212      * </pre>
213      * If you call getProperties("foo.bar.prop") will return a string array containing
214      * {"some value", "other value", "last value"}.
215      *
216      * @param name the name of the property to retrieve
217      * @return all child property values for the given node name.
218      */

219     public Iterator getChildProperties(String JavaDoc name) {
220         String JavaDoc[] propName = parsePropertyName(name);
221         // Search for this property by traversing down the XML heirarchy,
222
// stopping one short.
223
Element element = document.getRootElement();
224         for (int i = 0; i < propName.length - 1; i++) {
225             element = element.element(propName[i]);
226             if (element == null) {
227                 // This node doesn't match this part of the property name which
228
// indicates this property doesn't exist so return empty array.
229
return Collections.EMPTY_LIST.iterator();
230             }
231         }
232         // We found matching property, return names of children.
233
Iterator iter = element.elementIterator(propName[propName.length - 1]);
234         ArrayList<String JavaDoc> props = new ArrayList<String JavaDoc>();
235         while (iter.hasNext()) {
236             props.add(((Element)iter.next()).getName());
237         }
238         return props.iterator();
239     }
240
241     /**
242      * Returns the value of the attribute of the given property name or <tt>null</tt>
243      * if it doesn't exist. Note, this
244      *
245      * @param name the property name to lookup - ie, "foo.bar"
246      * @param attribute the name of the attribute, ie "id"
247      * @return the value of the attribute of the given property or <tt>null</tt> if
248      * it doesn't exist.
249      */

250     public String JavaDoc getAttribute(String JavaDoc name, String JavaDoc attribute) {
251         if (name == null || attribute == null) {
252             return null;
253         }
254         String JavaDoc[] propName = parsePropertyName(name);
255         // Search for this property by traversing down the XML heirarchy.
256
Element element = document.getRootElement();
257         for (int i = 0; i < propName.length; i++) {
258             String JavaDoc child = propName[i];
259             element = element.element(child);
260             if (element == null) {
261                 // This node doesn't match this part of the property name which
262
// indicates this property doesn't exist so return empty array.
263
break;
264             }
265         }
266         if (element != null) {
267             // Get its attribute values
268
return element.attributeValue(attribute);
269         }
270         return null;
271     }
272
273     /**
274      * Sets a property to an array of values. Multiple values matching the same property
275      * is mapped to an XML file as multiple elements containing each value.
276      * For example, using the name "foo.bar.prop", and the value string array containing
277      * {"some value", "other value", "last value"} would produce the following XML:
278      * <pre>
279      * &lt;foo&gt;
280      * &lt;bar&gt;
281      * &lt;prop&gt;some value&lt;/prop&gt;
282      * &lt;prop&gt;other value&lt;/prop&gt;
283      * &lt;prop&gt;last value&lt;/prop&gt;
284      * &lt;/bar&gt;
285      * &lt;/foo&gt;
286      * </pre>
287      *
288      * @param name the name of the property.
289      * @param values the values for the property (can be empty but not null).
290      */

291     public void setProperties(String JavaDoc name, List<String JavaDoc> values) {
292         String JavaDoc[] propName = parsePropertyName(name);
293         // Search for this property by traversing down the XML heirarchy,
294
// stopping one short.
295
Element element = document.getRootElement();
296         for (int i = 0; i < propName.length - 1; i++) {
297             // If we don't find this part of the property in the XML heirarchy
298
// we add it as a new node
299
if (element.element(propName[i]) == null) {
300                 element.addElement(propName[i]);
301             }
302             element = element.element(propName[i]);
303         }
304         String JavaDoc childName = propName[propName.length - 1];
305         // We found matching property, clear all children.
306
List toRemove = new ArrayList();
307         Iterator iter = element.elementIterator(childName);
308         while (iter.hasNext()) {
309             toRemove.add(iter.next());
310         }
311         for (iter = toRemove.iterator(); iter.hasNext();) {
312             element.remove((Element)iter.next());
313         }
314         // Add the new children.
315
for (String JavaDoc value : values) {
316             element.addElement(childName).setText(value);
317         }
318         saveProperties();
319
320         // Generate event.
321
Map params = new HashMap();
322         params.put("value", values);
323         PropertyEventDispatcher.dispatchEvent((String JavaDoc)name,
324                 PropertyEventDispatcher.EventType.xml_property_set, params);
325     }
326
327     /**
328      * Return all children property names of a parent property as a String array,
329      * or an empty array if the if there are no children. For example, given
330      * the properties <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, and <tt>X.Y.C</tt>, then
331      * the child properties of <tt>X.Y</tt> are <tt>A</tt>, <tt>B</tt>, and
332      * <tt>C</tt>.
333      *
334      * @param parent the name of the parent property.
335      * @return all child property values for the given parent.
336      */

337     public String JavaDoc[] getChildrenProperties(String JavaDoc parent) {
338         String JavaDoc[] propName = parsePropertyName(parent);
339         // Search for this property by traversing down the XML heirarchy.
340
Element element = document.getRootElement();
341         for (int i = 0; i < propName.length; i++) {
342             element = element.element(propName[i]);
343             if (element == null) {
344                 // This node doesn't match this part of the property name which
345
// indicates this property doesn't exist so return empty array.
346
return new String JavaDoc[]{};
347             }
348         }
349         // We found matching property, return names of children.
350
List children = element.elements();
351         int childCount = children.size();
352         String JavaDoc[] childrenNames = new String JavaDoc[childCount];
353         for (int i = 0; i < childCount; i++) {
354             childrenNames[i] = ((Element)children.get(i)).getName();
355         }
356         return childrenNames;
357     }
358
359     /**
360      * Sets the value of the specified property. If the property doesn't
361      * currently exist, it will be automatically created.
362      *
363      * @param name the name of the property to set.
364      * @param value the new value for the property.
365      */

366     public synchronized void setProperty(String JavaDoc name, String JavaDoc value) {
367         if (name == null) return;
368         if (value == null) value = "";
369
370         // Set cache correctly with prop name and value.
371
propertyCache.put(name, value);
372
373         String JavaDoc[] propName = parsePropertyName(name);
374         // Search for this property by traversing down the XML heirarchy.
375
Element element = document.getRootElement();
376         for (int i = 0; i < propName.length; i++) {
377             // If we don't find this part of the property in the XML heirarchy
378
// we add it as a new node
379
if (element.element(propName[i]) == null) {
380                 element.addElement(propName[i]);
381             }
382             element = element.element(propName[i]);
383         }
384         // Set the value of the property in this node.
385
element.setText(value);
386         // Write the XML properties to disk
387
saveProperties();
388
389         // Generate event.
390
Map params = new HashMap();
391         params.put("value", value);
392         PropertyEventDispatcher.dispatchEvent((String JavaDoc)name,
393                 PropertyEventDispatcher.EventType.xml_property_set, params);
394     }
395
396     /**
397      * Deletes the specified property.
398      *
399      * @param name the property to delete.
400      */

401     public synchronized void deleteProperty(String JavaDoc name) {
402         // Remove property from cache.
403
propertyCache.remove(name);
404
405         String JavaDoc[] propName = parsePropertyName(name);
406         // Search for this property by traversing down the XML heirarchy.
407
Element element = document.getRootElement();
408         for (int i = 0; i < propName.length - 1; i++) {
409             element = element.element(propName[i]);
410             // Can't find the property so return.
411
if (element == null) {
412                 return;
413             }
414         }
415         // Found the correct element to remove, so remove it...
416
element.remove(element.element(propName[propName.length - 1]));
417         // .. then write to disk.
418
saveProperties();
419
420         // Generate event.
421
PropertyEventDispatcher.dispatchEvent((String JavaDoc)name,
422                 PropertyEventDispatcher.EventType.xml_property_deleted, Collections.emptyMap());
423     }
424
425     /**
426      * Builds the document XML model up based the given reader of XML data.
427      */

428     private void buildDoc(Reader in) throws IOException {
429         try {
430             SAXReader xmlReader = new SAXReader();
431             document = xmlReader.read(in);
432         }
433         catch (Exception JavaDoc e) {
434             Log.error("Error reading XML properties", e);
435             throw new IOException(e.getMessage());
436         }
437         finally {
438             if (in != null) {
439                 in.close();
440             }
441         }
442     }
443
444     /**
445      * Saves the properties to disk as an XML document. A temporary file is
446      * used during the writing process for maximum safety.
447      */

448     private synchronized void saveProperties() {
449         boolean error = false;
450         // Write data out to a temporary file first.
451
File tempFile = null;
452         Writer writer = null;
453         try {
454             tempFile = new File(file.getParentFile(), file.getName() + ".tmp");
455             writer = new FileWriter(tempFile);
456             OutputFormat prettyPrinter = OutputFormat.createPrettyPrint();
457             XMLWriter xmlWriter = new XMLWriter(writer, prettyPrinter);
458             xmlWriter.write(document);
459         }
460         catch (Exception JavaDoc e) {
461             Log.error(e);
462             // There were errors so abort replacing the old property file.
463
error = true;
464         }
465         finally {
466             if (writer != null) {
467                 try {
468                     writer.close();
469                 }
470                 catch (IOException e1) {
471                     Log.error(e1);
472                     error = true;
473                 }
474             }
475         }
476
477         // No errors occured, so delete the main file.
478
if (!error) {
479             // Delete the old file so we can replace it.
480
if (!file.delete()) {
481                 Log.error("Error deleting property file: " + file.getAbsolutePath());
482                 return;
483             }
484             // Copy new contents to the file.
485
try {
486                 copy(tempFile, file);
487             }
488             catch (Exception JavaDoc e) {
489                 Log.error(e);
490                 // There were errors so abort replacing the old property file.
491
error = true;
492             }
493             // If no errors, delete the temp file.
494
if (!error) {
495                 tempFile.delete();
496             }
497         }
498     }
499
500     /**
501      * Returns an array representation of the given Jive property. Jive
502      * properties are always in the format "prop.name.is.this" which would be
503      * represented as an array of four Strings.
504      *
505      * @param name the name of the Jive property.
506      * @return an array representation of the given Jive property.
507      */

508     private String JavaDoc[] parsePropertyName(String JavaDoc name) {
509         List propName = new ArrayList(5);
510         // Use a StringTokenizer to tokenize the property name.
511
StringTokenizer tokenizer = new StringTokenizer(name, ".");
512         while (tokenizer.hasMoreTokens()) {
513             propName.add(tokenizer.nextToken());
514         }
515         return (String JavaDoc[])propName.toArray(new String JavaDoc[propName.size()]);
516     }
517
518     public void setProperties(Map propertyMap) {
519         Iterator iter = propertyMap.keySet().iterator();
520         while (iter.hasNext()) {
521             String JavaDoc propertyName = (String JavaDoc) iter.next();
522             String JavaDoc propertyValue = (String JavaDoc) propertyMap.get(propertyName);
523             setProperty(propertyName, propertyValue);
524         }
525     }
526
527     /**
528      * Copies the inFile to the outFile.
529      *
530      * @param inFile The file to copy from
531      * @param outFile The file to copy to
532      * @throws IOException If there was a problem making the copy
533      */

534     private static void copy(File inFile, File outFile) throws IOException {
535         FileInputStream fin = null;
536         FileOutputStream fout = null;
537         try {
538             fin = new FileInputStream(inFile);
539             fout = new FileOutputStream(outFile);
540             copy(fin, fout);
541         }
542         finally {
543             try {
544                 if (fin != null) fin.close();
545             }
546             catch (IOException e) {
547                 // do nothing
548
}
549             try {
550                 if (fout != null) fout.close();
551             }
552             catch (IOException e) {
553                 // do nothing
554
}
555         }
556     }
557
558     /**
559      * Copies data from an input stream to an output stream
560      *
561      * @param in The stream to copy data from
562      * @param out The stream to copy data to
563      * @throws IOException if there's trouble during the copy
564      */

565     private static void copy(InputStream in, OutputStream out) throws IOException {
566         // do not allow other threads to whack on in or out during copy
567
synchronized (in) {
568             synchronized (out) {
569                 byte[] buffer = new byte[256];
570                 while (true) {
571                     int bytesRead = in.read(buffer);
572                     if (bytesRead == -1) break;
573                     out.write(buffer, 0, bytesRead);
574                 }
575             }
576         }
577     }
578 }
579
Popular Tags