KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > configuration > XMLConfiguration


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 JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.OutputStream JavaDoc;
22 import java.io.Reader JavaDoc;
23 import java.io.Writer JavaDoc;
24 import java.net.URL JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Collection JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29
30 import javax.xml.parsers.DocumentBuilder JavaDoc;
31 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
32 import javax.xml.parsers.ParserConfigurationException JavaDoc;
33 import javax.xml.transform.OutputKeys JavaDoc;
34 import javax.xml.transform.Result JavaDoc;
35 import javax.xml.transform.Source JavaDoc;
36 import javax.xml.transform.Transformer JavaDoc;
37 import javax.xml.transform.TransformerException JavaDoc;
38 import javax.xml.transform.TransformerFactory JavaDoc;
39 import javax.xml.transform.dom.DOMSource JavaDoc;
40 import javax.xml.transform.stream.StreamResult JavaDoc;
41
42 import org.w3c.dom.Attr JavaDoc;
43 import org.w3c.dom.CDATASection JavaDoc;
44 import org.w3c.dom.DOMException JavaDoc;
45 import org.w3c.dom.Document JavaDoc;
46 import org.w3c.dom.Element JavaDoc;
47 import org.w3c.dom.NamedNodeMap JavaDoc;
48 import org.w3c.dom.NodeList JavaDoc;
49 import org.w3c.dom.Text JavaDoc;
50 import org.xml.sax.InputSource JavaDoc;
51 import org.apache.commons.configuration.reloading.ReloadingStrategy;
52
53 /**
54  * A specialized hierarchical configuration class that is able to parse XML
55  * documents.
56  *
57  * <p>The parsed document will be stored keeping its structure. The class also
58  * tries to preserve as much information from the loaded XML document as
59  * possible, including comments and processing instructions. These will be
60  * contained in documents created by the <code>save()</code> methods, too.
61  *
62  * @since commons-configuration 1.0
63  *
64  * @author J&ouml;rg Schaible
65  * @author <a HREF="mailto:oliver.heger@t-online.de">Oliver Heger </a>
66  * @version $Revision: 156237 $, $Date: 2005-03-05 11:26:22 +0100 (Sa, 05 Mrz 2005) $
67  */

68 public class XMLConfiguration extends HierarchicalConfiguration implements FileConfiguration
69 {
70     /** Constant for the default root element name. */
71     private static final String JavaDoc DEFAULT_ROOT_NAME = "configuration";
72
73     /** Delimiter character for attributes. */
74     private static char ATTR_DELIMITER = ',';
75
76     private FileConfigurationDelegate delegate = new FileConfigurationDelegate();
77
78     /** The document from this configuration's data source. */
79     private Document JavaDoc document;
80
81     /** Stores the name of the root element. */
82     private String JavaDoc rootElementName;
83
84     /**
85      * Creates a new instance of <code>XMLConfiguration</code>.
86      */

87     public XMLConfiguration()
88     {
89         super();
90     }
91
92     /**
93      * Creates a new instance of <code>XMLConfiguration</code>.
94      * The configuration is loaded from the specified file
95      *
96      * @param fileName the name of the file to load
97      * @throws ConfigurationException if the file cannot be loaded
98      */

99     public XMLConfiguration(String JavaDoc fileName) throws ConfigurationException
100     {
101         this();
102         setFileName(fileName);
103         load();
104     }
105
106     /**
107      * Creates a new instance of <code>XMLConfiguration</code>.
108      * The configuration is loaded from the specified file.
109      *
110      * @param file the file
111      * @throws ConfigurationException if an error occurs while loading the file
112      */

113     public XMLConfiguration(File JavaDoc file) throws ConfigurationException
114     {
115         this();
116         setFile(file);
117         if (file.exists())
118         {
119             load();
120         }
121     }
122
123     /**
124      * Creates a new instance of <code>XMLConfiguration</code>.
125      * The configuration is loaded from the specified URL.
126      *
127      * @param url the URL
128      * @throws ConfigurationException if loading causes an error
129      */

130     public XMLConfiguration(URL JavaDoc url) throws ConfigurationException
131     {
132         this();
133         setURL(url);
134         load();
135     }
136
137     /**
138      * Returns the name of the root element. If this configuration was loaded
139      * from a XML document, the name of this document's root element is
140      * returned. Otherwise it is possible to set a name for the root element
141      * that will be used when this configuration is stored.
142      *
143      * @return the name of the root element
144      */

145     public String JavaDoc getRootElementName()
146     {
147         if (getDocument() == null)
148         {
149             return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
150         }
151         else
152         {
153             return getDocument().getDocumentElement().getNodeName();
154         }
155     }
156
157     /**
158      * Sets the name of the root element. This name is used when this
159      * configuration object is stored in an XML file. Note that setting the name
160      * of the root element works only if this configuration has been newly
161      * created. If the configuration was loaded from an XML file, the name
162      * cannot be changed and an <code>UnsupportedOperationException</code>
163      * exception is thrown. Whether this configuration has been loaded from an
164      * XML document or not can be found out using the <code>getDocument()</code>
165      * method.
166      *
167      * @param name the name of the root element
168      */

169     public void setRootElementName(String JavaDoc name)
170     {
171         if (getDocument() != null)
172         {
173             throw new UnsupportedOperationException JavaDoc("The name of the root element "
174                     + "cannot be changed when loaded from an XML document!");
175         }
176         rootElementName = name;
177     }
178     
179     /**
180      * Returns the XML document this configuration was loaded from. The return
181      * value is <b>null</b> if this configuration was not loaded from a XML
182      * document.
183      *
184      * @return the XML document this configuration was loaded from
185      */

186     public Document JavaDoc getDocument()
187     {
188         return document;
189     }
190
191     /**
192      * @inheritDoc
193      */

194     protected void addPropertyDirect(String JavaDoc key, Object JavaDoc obj)
195     {
196         super.addPropertyDirect(key, obj);
197         delegate.possiblySave();
198     }
199
200     /**
201      * @inheritDoc
202      */

203     public void clearProperty(String JavaDoc key)
204     {
205         super.clearProperty(key);
206         delegate.possiblySave();
207     }
208
209     /**
210      * @inheritDoc
211      */

212     public void clearTree(String JavaDoc key)
213     {
214         super.clearTree(key);
215         delegate.possiblySave();
216     }
217     
218     /**
219      * @inheritDoc
220      */

221     public void setProperty(String JavaDoc key, Object JavaDoc value)
222     {
223         super.setProperty(key, value);
224         delegate.possiblySave();
225     }
226
227     /**
228      * Initializes this configuration from an XML document.
229      *
230      * @param document the document to be parsed
231      * @param elemRefs a flag whether references to the XML elements should be set
232      */

233     public void initProperties(Document JavaDoc document, boolean elemRefs)
234     {
235         constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);
236     }
237
238     /**
239      * Helper method for building the internal storage hierarchy. The XML
240      * elements are transformed into node objects.
241      *
242      * @param node the actual node
243      * @param element the actual XML element
244      * @param elemRefs a flag whether references to the XML elements should be set
245      */

246     private void constructHierarchy(Node node, Element JavaDoc element, boolean elemRefs)
247     {
248         processAttributes(node, element);
249         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
250         NodeList JavaDoc list = element.getChildNodes();
251         for (int i = 0; i < list.getLength(); i++)
252         {
253             org.w3c.dom.Node JavaDoc w3cNode = list.item(i);
254             if (w3cNode instanceof Element JavaDoc)
255             {
256                 Element JavaDoc child = (Element JavaDoc) w3cNode;
257                 Node childNode = new XMLNode(child.getTagName(),
258                         (elemRefs) ? child : null);
259                 constructHierarchy(childNode, child, elemRefs);
260                 node.addChild(childNode);
261             }
262             else if (w3cNode instanceof Text JavaDoc)
263             {
264                 Text JavaDoc data = (Text JavaDoc) w3cNode;
265                 buffer.append(data.getData());
266             }
267         }
268         String JavaDoc text = buffer.toString().trim();
269         if (text.length() > 0)
270         {
271             node.setValue(text);
272         }
273     }
274
275     /**
276      * Helper method for constructing node objects for the attributes of the
277      * given XML element.
278      *
279      * @param node the actual node
280      * @param element the actual XML element
281      */

282     private void processAttributes(Node node, Element JavaDoc element)
283     {
284         NamedNodeMap JavaDoc attributes = element.getAttributes();
285         for (int i = 0; i < attributes.getLength(); ++i)
286         {
287             org.w3c.dom.Node JavaDoc w3cNode = attributes.item(i);
288             if (w3cNode instanceof Attr JavaDoc)
289             {
290                 Attr JavaDoc attr = (Attr JavaDoc) w3cNode;
291                 for (Iterator JavaDoc it = PropertyConverter.split(attr.getValue(), ATTR_DELIMITER).iterator(); it.hasNext();)
292                 {
293                     Node child = new XMLNode(ConfigurationKey.constructAttributeKey(attr.getName()), element);
294                     child.setValue(it.next());
295                     node.addChild(child);
296                 }
297             }
298         }
299     }
300
301     /**
302      * Creates a DOM document from the internal tree of configuration nodes.
303      *
304      * @return the new document
305      * @throws ConfigurationException if an error occurs
306      */

307     protected Document JavaDoc createDocument() throws ConfigurationException
308     {
309         try
310         {
311             if (document == null)
312             {
313                 DocumentBuilder JavaDoc builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
314                 Document JavaDoc newDocument = builder.newDocument();
315                 Element JavaDoc rootElem = newDocument.createElement(getRootElementName());
316                 newDocument.appendChild(rootElem);
317                 document = newDocument;
318             }
319
320             XMLBuilderVisitor builder = new XMLBuilderVisitor(document);
321             builder.processDocument(getRoot());
322             return document;
323         } /* try */
324         catch (DOMException JavaDoc domEx)
325         {
326             throw new ConfigurationException(domEx);
327         }
328         catch (ParserConfigurationException JavaDoc pex)
329         {
330             throw new ConfigurationException(pex);
331         }
332     }
333
334     /**
335      * Creates a new node object. This implementation returns an instance of the
336      * <code>XMLNode</code> class.
337      *
338      * @param name the node's name
339      * @return the new node
340      */

341     protected Node createNode(String JavaDoc name)
342     {
343         return new XMLNode(name, null);
344     }
345
346     public void load() throws ConfigurationException
347     {
348         delegate.load();
349     }
350
351     public void load(String JavaDoc fileName) throws ConfigurationException
352     {
353         delegate.load(fileName);
354     }
355
356     public void load(File JavaDoc file) throws ConfigurationException
357     {
358         delegate.load(file);
359     }
360
361     public void load(URL JavaDoc url) throws ConfigurationException
362     {
363         delegate.load(url);
364     }
365
366     public void load(InputStream JavaDoc in) throws ConfigurationException
367     {
368         delegate.load(in);
369     }
370
371     public void load(InputStream JavaDoc in, String JavaDoc encoding) throws ConfigurationException
372     {
373         delegate.load(in, encoding);
374     }
375
376     /**
377      * Load the properties from the given reader.
378      * Note that the <code>clear()</code> method is not called, so
379      * the properties contained in the loaded file will be added to the
380      * actual set of properties.
381      *
382      * @param in An InputStream.
383      *
384      * @throws ConfigurationException
385      */

386     public void load(Reader JavaDoc in) throws ConfigurationException
387     {
388         try
389         {
390             DocumentBuilder JavaDoc builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
391             Document JavaDoc newDocument = builder.parse(new InputSource JavaDoc(in));
392             Document JavaDoc oldDocument = document;
393             document = null;
394             initProperties(newDocument, oldDocument == null);
395             document = (oldDocument == null) ? newDocument : oldDocument;
396         }
397         catch (Exception JavaDoc e)
398         {
399             throw new ConfigurationException(e.getMessage(), e);
400         }
401     }
402
403     public void save() throws ConfigurationException
404     {
405         delegate.save();
406     }
407
408     public void save(String JavaDoc fileName) throws ConfigurationException
409     {
410         delegate.save(fileName);
411     }
412
413     public void save(File JavaDoc file) throws ConfigurationException
414     {
415         delegate.save(file);
416     }
417
418     public void save(URL JavaDoc url) throws ConfigurationException
419     {
420         delegate.save(url);
421     }
422
423     public void save(OutputStream JavaDoc out) throws ConfigurationException
424     {
425         delegate.save(out);
426     }
427
428     public void save(OutputStream JavaDoc out, String JavaDoc encoding) throws ConfigurationException
429     {
430         delegate.save(out, encoding);
431     }
432
433     /**
434      * Saves the configuration to the specified writer.
435      *
436      * @param writer the writer used to save the configuration
437      * @throws ConfigurationException if an error occurs
438      */

439     public void save(Writer JavaDoc writer) throws ConfigurationException
440     {
441         try
442         {
443             Transformer JavaDoc transformer = TransformerFactory.newInstance().newTransformer();
444             Source JavaDoc source = new DOMSource JavaDoc(createDocument());
445             Result JavaDoc result = new StreamResult JavaDoc(writer);
446
447             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
448             transformer.transform(source, result);
449         }
450         catch (TransformerException JavaDoc e)
451         {
452             throw new ConfigurationException(e.getMessage(), e);
453         }
454     }
455
456     public String JavaDoc getFileName()
457     {
458         return delegate.getFileName();
459     }
460
461     public void setFileName(String JavaDoc fileName)
462     {
463         delegate.setFileName(fileName);
464     }
465
466     public String JavaDoc getBasePath()
467     {
468         return delegate.getBasePath();
469     }
470
471     public void setBasePath(String JavaDoc basePath)
472     {
473         delegate.setBasePath(basePath);
474     }
475
476     public File JavaDoc getFile()
477     {
478         return delegate.getFile();
479     }
480
481     public void setFile(File JavaDoc file)
482     {
483         delegate.setFile(file);
484     }
485
486     public URL JavaDoc getURL()
487     {
488         return delegate.getURL();
489     }
490
491     public void setURL(URL JavaDoc url)
492     {
493         delegate.setURL(url);
494     }
495
496     public void setAutoSave(boolean autoSave)
497     {
498         delegate.setAutoSave(autoSave);
499     }
500
501     public boolean isAutoSave()
502     {
503         return delegate.isAutoSave();
504     }
505
506     public ReloadingStrategy getReloadingStrategy()
507     {
508         return delegate.getReloadingStrategy();
509     }
510
511     public void setReloadingStrategy(ReloadingStrategy strategy)
512     {
513         delegate.setReloadingStrategy(strategy);
514     }
515
516     public void reload()
517     {
518         delegate.reload();
519     }
520
521     public String JavaDoc getEncoding()
522     {
523         return delegate.getEncoding();
524     }
525
526     public void setEncoding(String JavaDoc encoding)
527     {
528         delegate.setEncoding(encoding);
529     }
530
531     /**
532      * A specialized <code>Node</code> class that is connected with an XML
533      * element. Changes on a node are also performed on the associated element.
534      */

535     class XMLNode extends Node
536     {
537         /**
538          * Creates a new instance of <code>XMLNode</code> and initializes it
539          * with the corresponding XML element.
540          *
541          * @param elem the XML element
542          */

543         public XMLNode(Element JavaDoc elem)
544         {
545             super();
546             setReference(elem);
547         }
548
549         /**
550          * Creates a new instance of <code>XMLNode</code> and initializes it
551          * with a name and the corresponding XML element.
552          *
553          * @param name the node's name
554          * @param elem the XML element
555          */

556         public XMLNode(String JavaDoc name, Element JavaDoc elem)
557         {
558             super(name);
559             setReference(elem);
560         }
561
562         /**
563          * Sets the value of this node. If this node is associated with an XML
564          * element, this element will be updated, too.
565          *
566          * @param value the node's new value
567          */

568         public void setValue(Object JavaDoc value)
569         {
570             super.setValue(value);
571
572             if (getReference() != null && document != null)
573             {
574                 if (ConfigurationKey.isAttributeKey(getName()))
575                 {
576                     updateAttribute();
577                 }
578                 else
579                 {
580                     updateElement(value);
581                 }
582             }
583         }
584
585         /**
586          * Updates the associated XML elements when a node is removed.
587          */

588         protected void removeReference()
589         {
590             if (getReference() != null)
591             {
592                 Element JavaDoc element = (Element JavaDoc) getReference();
593                 if (ConfigurationKey.isAttributeKey(getName()))
594                 {
595                     updateAttribute();
596                 }
597                 else
598                 {
599                     org.w3c.dom.Node JavaDoc parentElem = element.getParentNode();
600                     if (parentElem != null)
601                     {
602                         parentElem.removeChild(element);
603                     }
604                 }
605             }
606         }
607
608         /**
609          * Updates the node's value if it represents an element node.
610          *
611          * @param value the new value
612          */

613         private void updateElement(Object JavaDoc value)
614         {
615             Text JavaDoc txtNode = findTextNodeForUpdate();
616             if (value == null)
617             {
618                 // remove text
619
if (txtNode != null)
620                 {
621                     ((Element JavaDoc) getReference()).removeChild(txtNode);
622                 }
623             }
624             else
625             {
626                 if (txtNode == null)
627                 {
628                     txtNode = document.createTextNode(value.toString());
629                     if (((Element JavaDoc) getReference()).getFirstChild() != null)
630                     {
631                         ((Element JavaDoc) getReference()).insertBefore(txtNode, ((Element JavaDoc) getReference()).getFirstChild());
632                     }
633                     else
634                     {
635                         ((Element JavaDoc) getReference()).appendChild(txtNode);
636                     }
637                 }
638                 else
639                 {
640                     txtNode.setNodeValue(value.toString());
641                 }
642             }
643         }
644
645         /**
646          * Updates the node's value if it represents an attribute.
647          *
648          */

649         private void updateAttribute()
650         {
651             XMLBuilderVisitor.updateAttribute(getParent(), getName());
652         }
653
654         /**
655          * Returns the only text node of this element for update. This method is
656          * called when the element's text changes. Then all text nodes except
657          * for the first are removed. A reference to the first is returned or
658          * <b>null </b> if there is no text node at all.
659          *
660          * @return the first and only text node
661          */

662         private Text JavaDoc findTextNodeForUpdate()
663         {
664             Text JavaDoc result = null;
665             Element JavaDoc elem = (Element JavaDoc) getReference();
666             // Find all Text nodes
667
NodeList JavaDoc children = elem.getChildNodes();
668             Collection JavaDoc textNodes = new ArrayList JavaDoc();
669             for (int i = 0; i < children.getLength(); i++)
670             {
671                 org.w3c.dom.Node JavaDoc nd = children.item(i);
672                 if (nd instanceof Text JavaDoc)
673                 {
674                     if (result == null)
675                     {
676                         result = (Text JavaDoc) nd;
677                     }
678                     else
679                     {
680                         textNodes.add(nd);
681                     }
682                 }
683             }
684
685             // We don't want CDATAs
686
if (result instanceof CDATASection JavaDoc)
687             {
688                 textNodes.add(result);
689                 result = null;
690             }
691
692             // Remove all but the first Text node
693
for (Iterator JavaDoc it = textNodes.iterator(); it.hasNext();)
694             {
695                 elem.removeChild((org.w3c.dom.Node JavaDoc) it.next());
696             }
697             return result;
698         }
699     }
700
701     /**
702      * A concrete <code>BuilderVisitor</code> that can construct XML
703      * documents.
704      */

705     static class XMLBuilderVisitor extends BuilderVisitor
706     {
707         /** Stores the document to be constructed. */
708         private Document JavaDoc document;
709
710         /**
711          * Creates a new instance of <code>XMLBuilderVisitor</code>
712          *
713          * @param doc the document to be created
714          */

715         public XMLBuilderVisitor(Document JavaDoc doc)
716         {
717             document = doc;
718         }
719
720         /**
721          * Processes the node hierarchy and adds new nodes to the document.
722          *
723          * @param rootNode the root node
724          */

725         public void processDocument(Node rootNode)
726         {
727             rootNode.visit(this, null);
728         }
729
730         /**
731          * @inheritDoc
732          */

733         protected Object JavaDoc insert(Node newNode, Node parent, Node sibling1, Node sibling2)
734         {
735             if (ConfigurationKey.isAttributeKey(newNode.getName()))
736             {
737                 updateAttribute(parent, getElement(parent), newNode.getName());
738                 return null;
739             }
740
741             else
742             {
743                 Element JavaDoc elem = document.createElement(newNode.getName());
744                 if (newNode.getValue() != null)
745                 {
746                     elem.appendChild(document.createTextNode(newNode.getValue().toString()));
747                 }
748                 if (sibling2 == null)
749                 {
750                     getElement(parent).appendChild(elem);
751                 }
752                 else if (sibling1 != null)
753                 {
754                     getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
755                 }
756                 else
757                 {
758                     getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
759                 }
760                 return elem;
761             }
762         }
763
764         /**
765          * Helper method for updating the value of the specified node's
766          * attribute with the given name.
767          *
768          * @param node the affected node
769          * @param elem the element that is associated with this node
770          * @param name the name of the affected attribute
771          */

772         private static void updateAttribute(Node node, Element JavaDoc elem, String JavaDoc name)
773         {
774             if (node != null && elem != null)
775             {
776                 List JavaDoc attrs = node.getChildren(name);
777                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
778                 for (Iterator JavaDoc it = attrs.iterator(); it.hasNext();)
779                 {
780                     Node attr = (Node) it.next();
781                     if (attr.getValue() != null)
782                     {
783                         if (buf.length() > 0)
784                         {
785                             buf.append(ATTR_DELIMITER);
786                         }
787                         buf.append(attr.getValue());
788                     }
789                     attr.setReference(elem);
790                 }
791
792                 if (buf.length() < 1)
793                 {
794                     elem.removeAttribute(ConfigurationKey.removeAttributeMarkers(name));
795                 }
796                 else
797                 {
798                     elem.setAttribute(ConfigurationKey.removeAttributeMarkers(name), buf.toString());
799                 }
800             }
801         }
802
803         /**
804          * Updates the value of the specified attribute of the given node.
805          * Because there can be multiple child nodes representing this attribute
806          * the new value is determined by iterating over all those child nodes.
807          *
808          * @param node the affected node
809          * @param name the name of the attribute
810          */

811         static void updateAttribute(Node node, String JavaDoc name)
812         {
813             if (node != null)
814             {
815                 updateAttribute(node, (Element JavaDoc) node.getReference(), name);
816             }
817         }
818
819         /**
820          * Helper method for accessing the element of the specified node.
821          *
822          * @param node the node
823          * @return the element of this node
824          */

825         private Element JavaDoc getElement(Node node)
826         {
827             // special treatement for root node of the hierarchy
828
return (node.getName() != null) ? (Element JavaDoc) node.getReference() : document.getDocumentElement();
829         }
830     }
831
832     private class FileConfigurationDelegate extends AbstractFileConfiguration
833     {
834         public void load(Reader JavaDoc in) throws ConfigurationException
835         {
836             XMLConfiguration.this.load(in);
837         }
838
839         public void save(Writer JavaDoc out) throws ConfigurationException
840         {
841             XMLConfiguration.this.save(out);
842         }
843     }
844 }
Popular Tags