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.IOException;
22  import java.io.InputStream;
23  import java.io.Reader;
24  import java.io.Writer;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  
35  import javax.xml.parsers.DocumentBuilder;
36  import javax.xml.parsers.DocumentBuilderFactory;
37  import javax.xml.parsers.ParserConfigurationException;
38  import javax.xml.transform.OutputKeys;
39  import javax.xml.transform.Result;
40  import javax.xml.transform.Source;
41  import javax.xml.transform.Transformer;
42  import javax.xml.transform.TransformerException;
43  import javax.xml.transform.TransformerFactory;
44  import javax.xml.transform.TransformerFactoryConfigurationError;
45  import javax.xml.transform.dom.DOMSource;
46  import javax.xml.transform.stream.StreamResult;
47  
48  import org.apache.commons.configuration.tree.ConfigurationNode;
49  import org.w3c.dom.Attr;
50  import org.w3c.dom.CDATASection;
51  import org.w3c.dom.DOMException;
52  import org.w3c.dom.Document;
53  import org.w3c.dom.Element;
54  import org.w3c.dom.NamedNodeMap;
55  import org.w3c.dom.NodeList;
56  import org.w3c.dom.Text;
57  import org.xml.sax.EntityResolver;
58  import org.xml.sax.InputSource;
59  import org.xml.sax.SAXException;
60  import org.xml.sax.SAXParseException;
61  import org.xml.sax.helpers.DefaultHandler;
62  
63  /***
64   * <p>A specialized hierarchical configuration class that is able to parse XML
65   * documents.</p>
66   *
67   * <p>The parsed document will be stored keeping its structure. The class also
68   * tries to preserve as much information from the loaded XML document as
69   * possible, including comments and processing instructions. These will be
70   * contained in documents created by the <code>save()</code> methods, too.</p>
71   *
72   * <p>Like other file based configuration classes this class maintains the name
73   * and path to the loaded configuration file. These properties can be altered
74   * using several setter methods, but they are not modified by <code>save()</code>
75   * and <code>load()</code> methods. If XML documents contain relative paths to
76   * other documents (e.g. to a DTD), these references are resolved based on the
77   * path set for this configuration.</p>
78   *
79   * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
80   * provides some extended functionality, e.g. interpolation of property values.
81   * Like in <code>{@link PropertiesConfiguration}</code> property values can
82   * contain delimiter characters (the comma ',' per default) and are then split
83   * into multiple values. This works for XML attributes and text content of
84   * elements as well. The delimiter can be escaped by a backslash. As an example
85   * consider the following XML fragment:</p>
86   *
87   * <p>
88   * <pre>
89   * &lt;config&gt;
90   *   &lt;array&gt;10,20,30,40&lt;/array&gt;
91   *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
92   *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
93   * &lt;/config&gt;
94   * </pre>
95   * </p>
96   * <p>Here the content of the <code>array</code> element will be split at
97   * the commas, so the <code>array</code> key will be assigned 4 values. In the
98   * <code>scalar</code> property and the <code>text</code> attribute of the
99   * <code>cite</code> element the comma is escaped, so that no splitting is
100  * performed.</p>
101  *
102  * <p>The configuration API allows setting multiple values for a single attribute,
103  * e.g. something like the following is legal (assuming that the default
104  * expression engine is used):
105  * <pre>
106  * XMLConfiguration config = new XMLConfiguration();
107  * config.addProperty("test.dir[@name]", "C://Temp//");
108  * config.addProperty("test.dir[@name]", "D://Data//");
109  * </pre></p>
110  *
111  * <p>Because in XML such a constellation is not directly supported (an attribute
112  * can appear only once for a single element), the values are concatenated to a
113  * single value. If delimiter parsing is enabled (refer to the
114  * <code>{@link #setDelimiterParsingDisabled(boolean)}</code> method), the
115  * current list delimiter character will be used as separator. Otherwise the
116  * pipe symbol ("|") will be used for this purpose. No matter which character is
117  * used as delimiter, it can always be escaped with a backslash. A backslash
118  * itself can also be escaped with another backslash. Consider the following
119  * example fragment from a configuration file:
120  * <pre>
121  * &lt;directories names="C:\Temp//|D:\Data\"/&gt;
122  * </pre>
123  * Here the backslash after Temp is escaped. This is necessary because it
124  * would escape the list delimiter (the pipe symbol assuming that list delimiter
125  * parsing is disabled) otherwise. So this attribute would have two values.</p>
126  *
127  * <p>Note: You should ensure that the <em>delimiter parsing disabled</em>
128  * property is always consistent when you load and save a configuration file.
129  * Otherwise the values of properties can become corrupted.</p>
130  *
131  * <p>Whitespace in the content of XML documents is trimmed per default. In most
132  * cases this is desired. However, sometimes whitespace is indeed important and
133  * should be treated as part of the value of a property as in the following
134  * example:
135  * <pre>
136  *   &lt;indent&gt;    &lt;/indent&gt;
137  * </pre></p>
138  *
139  * <p>Per default the spaces in the <code>indent</code> element will be trimmed
140  * resulting in an empty element. To tell <code>XMLConfiguration</code> that
141  * spaces are relevant the <code>xml:space</code> attribute can be used, which is
142  * defined in the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
143  * specification</a>. This will look as follows:
144  * <pre>
145  *   &lt;indent <strong>xml:space=&quot;preserve&quot;</strong>&gt;    &lt;/indent&gt;
146  * </pre>
147  * The value of the <code>indent</code> property will now contain the spaces.</p>
148  *
149  * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
150  * interface and thus provides full support for loading XML documents from
151  * different sources like files, URLs, or streams. A full description of these
152  * features can be found in the documentation of
153  * <code>{@link AbstractFileConfiguration}</code>.</p>
154  *
155  * <p><em>Note:</em>Configuration objects of this type can be read concurrently
156  * by multiple threads. However if one of these threads modifies the object,
157  * synchronization has to be performed manually.</p>
158  *
159  * @since commons-configuration 1.0
160  *
161  * @author J&ouml;rg Schaible
162  * @author Oliver Heger
163  * @version $Revision: 721895 $, $Date: 2008-11-30 22:08:42 +0100 (So, 30 Nov 2008) $
164  */
165 public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
166     implements EntityResolver
167 {
168     /***
169      * The serial version UID.
170      */
171     private static final long serialVersionUID = 2453781111653383552L;
172 
173     /*** Constant for the default root element name. */
174     private static final String DEFAULT_ROOT_NAME = "configuration";
175 
176     /*** Constant for the name of the space attribute.*/
177     private static final String ATTR_SPACE = "xml:space";
178 
179     /*** Constant for the xml:space value for preserving whitespace.*/
180     private static final String VALUE_PRESERVE = "preserve";
181 
182     /*** Constant for the delimiter for multiple attribute values.*/
183     private static final char ATTR_VALUE_DELIMITER = '|';
184 
185     /*** The document from this configuration's data source. */
186     private Document document;
187 
188     /*** Stores a map with the registered public IDs.*/
189     private Map registeredEntities = new HashMap();
190 
191     /*** Stores the name of the root element. */
192     private String rootElementName;
193 
194     /*** Stores the public ID from the DOCTYPE.*/
195     private String publicID;
196 
197     /*** Stores the system ID from the DOCTYPE.*/
198     private String systemID;
199 
200     /*** Stores the document builder that should be used for loading.*/
201     private DocumentBuilder documentBuilder;
202 
203     /*** Stores a flag whether DTD validation should be performed.*/
204     private boolean validating;
205 
206     /*** A flag whether attribute splitting is disabled.*/
207     private boolean attributeSplittingDisabled;
208 
209     /***
210      * Creates a new instance of <code>XMLConfiguration</code>.
211      */
212     public XMLConfiguration()
213     {
214         super();
215     }
216 
217     /***
218      * Creates a new instance of <code>XMLConfiguration</code> and copies the
219      * content of the passed in configuration into this object. Note that only
220      * the data of the passed in configuration will be copied. If, for instance,
221      * the other configuration is a <code>XMLConfiguration</code>, too,
222      * things like comments or processing instructions will be lost.
223      *
224      * @param c the configuration to copy
225      * @since 1.4
226      */
227     public XMLConfiguration(HierarchicalConfiguration c)
228     {
229         super(c);
230         clearReferences(getRootNode());
231         setRootElementName(getRootNode().getName());
232     }
233 
234     /***
235      * Creates a new instance of <code>XMLConfiguration</code>. The
236      * configuration is loaded from the specified file
237      *
238      * @param fileName the name of the file to load
239      * @throws ConfigurationException if the file cannot be loaded
240      */
241     public XMLConfiguration(String fileName) throws ConfigurationException
242     {
243         super(fileName);
244     }
245 
246     /***
247      * Creates a new instance of <code>XMLConfiguration</code>.
248      * The configuration is loaded from the specified file.
249      *
250      * @param file the file
251      * @throws ConfigurationException if an error occurs while loading the file
252      */
253     public XMLConfiguration(File file) throws ConfigurationException
254     {
255         super(file);
256     }
257 
258     /***
259      * Creates a new instance of <code>XMLConfiguration</code>.
260      * The configuration is loaded from the specified URL.
261      *
262      * @param url the URL
263      * @throws ConfigurationException if loading causes an error
264      */
265     public XMLConfiguration(URL url) throws ConfigurationException
266     {
267         super(url);
268     }
269 
270     /***
271      * Returns the name of the root element. If this configuration was loaded
272      * from a XML document, the name of this document's root element is
273      * returned. Otherwise it is possible to set a name for the root element
274      * that will be used when this configuration is stored.
275      *
276      * @return the name of the root element
277      */
278     public String getRootElementName()
279     {
280         if (getDocument() == null)
281         {
282             return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
283         }
284         else
285         {
286             return getDocument().getDocumentElement().getNodeName();
287         }
288     }
289 
290     /***
291      * Sets the name of the root element. This name is used when this
292      * configuration object is stored in an XML file. Note that setting the name
293      * of the root element works only if this configuration has been newly
294      * created. If the configuration was loaded from an XML file, the name
295      * cannot be changed and an <code>UnsupportedOperationException</code>
296      * exception is thrown. Whether this configuration has been loaded from an
297      * XML document or not can be found out using the <code>getDocument()</code>
298      * method.
299      *
300      * @param name the name of the root element
301      */
302     public void setRootElementName(String name)
303     {
304         if (getDocument() != null)
305         {
306             throw new UnsupportedOperationException("The name of the root element "
307                     + "cannot be changed when loaded from an XML document!");
308         }
309         rootElementName = name;
310         getRootNode().setName(name);
311     }
312 
313     /***
314      * Returns the <code>DocumentBuilder</code> object that is used for
315      * loading documents. If no specific builder has been set, this method
316      * returns <b>null</b>.
317      *
318      * @return the <code>DocumentBuilder</code> for loading new documents
319      * @since 1.2
320      */
321     public DocumentBuilder getDocumentBuilder()
322     {
323         return documentBuilder;
324     }
325 
326     /***
327      * Sets the <code>DocumentBuilder</code> object to be used for loading
328      * documents. This method makes it possible to specify the exact document
329      * builder. So an application can create a builder, configure it for its
330      * special needs, and then pass it to this method.
331      *
332      * @param documentBuilder the document builder to be used; if undefined, a
333      * default builder will be used
334      * @since 1.2
335      */
336     public void setDocumentBuilder(DocumentBuilder documentBuilder)
337     {
338         this.documentBuilder = documentBuilder;
339     }
340 
341     /***
342      * Returns the public ID of the DOCTYPE declaration from the loaded XML
343      * document. This is <b>null</b> if no document has been loaded yet or if
344      * the document does not contain a DOCTYPE declaration with a public ID.
345      *
346      * @return the public ID
347      * @since 1.3
348      */
349     public String getPublicID()
350     {
351         return publicID;
352     }
353 
354     /***
355      * Sets the public ID of the DOCTYPE declaration. When this configuration is
356      * saved, a DOCTYPE declaration will be constructed that contains this
357      * public ID.
358      *
359      * @param publicID the public ID
360      * @since 1.3
361      */
362     public void setPublicID(String publicID)
363     {
364         this.publicID = publicID;
365     }
366 
367     /***
368      * Returns the system ID of the DOCTYPE declaration from the loaded XML
369      * document. This is <b>null</b> if no document has been loaded yet or if
370      * the document does not contain a DOCTYPE declaration with a system ID.
371      *
372      * @return the system ID
373      * @since 1.3
374      */
375     public String getSystemID()
376     {
377         return systemID;
378     }
379 
380     /***
381      * Sets the system ID of the DOCTYPE declaration. When this configuration is
382      * saved, a DOCTYPE declaration will be constructed that contains this
383      * system ID.
384      *
385      * @param systemID the system ID
386      * @since 1.3
387      */
388     public void setSystemID(String systemID)
389     {
390         this.systemID = systemID;
391     }
392 
393     /***
394      * Returns the value of the validating flag.
395      *
396      * @return the validating flag
397      * @since 1.2
398      */
399     public boolean isValidating()
400     {
401         return validating;
402     }
403 
404     /***
405      * Sets the value of the validating flag. This flag determines whether
406      * DTD validation should be performed when loading XML documents. This
407      * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
408      *
409      * @param validating the validating flag
410      * @since 1.2
411      */
412     public void setValidating(boolean validating)
413     {
414         this.validating = validating;
415     }
416 
417     /***
418      * Returns the flag whether attribute splitting is disabled.
419      *
420      * @return the flag whether attribute splitting is disabled
421      * @see #setAttributeSplittingDisabled(boolean)
422      * @since 1.6
423      */
424     public boolean isAttributeSplittingDisabled()
425     {
426         return attributeSplittingDisabled;
427     }
428 
429     /***
430      * <p>
431      * Sets a flag whether attribute splitting is disabled.
432      * </p>
433      * <p>
434      * The Configuration API allows adding multiple values to an attribute. This
435      * is problematic when storing the configuration because in XML an attribute
436      * can appear only once with a single value. To solve this problem, per
437      * default multiple attribute values are concatenated using a special
438      * separator character and split again when the configuration is loaded. The
439      * separator character is either the list delimiter character (see
440      * {@link #setListDelimiter(char)}) or the pipe symbol (&quot;|&quot;) if
441      * list delimiter parsing is disabled.
442      * </p>
443      * <p>
444      * In some constellations the splitting of attribute values can have
445      * undesired effects, especially if list delimiter parsing is disabled and
446      * attributes may contain the &quot;|&quot; character. In these cases it is
447      * possible to disable the attribute splitting mechanism by calling this
448      * method with a boolean value set to <b>false</b>. If attribute splitting
449      * is disabled, the values of attributes will not be processed, but stored
450      * as configuration properties exactly as they are returned by the XML
451      * parser.
452      * </p>
453      * <p>
454      * Note that in this mode multiple attribute values cannot be handled
455      * correctly. It is possible to create a <code>XMLConfiguration</code>
456      * object, add multiple values to an attribute and save it. When the
457      * configuration is loaded again and attribute splitting is disabled, the
458      * attribute will only have a single value, which is the concatenation of
459      * all values set before. So it lies in the responsibility of the
460      * application to carefully set the values of attributes.
461      * </p>
462      * <p>
463      * As is true for the {@link #setDelimiterParsingDisabled(boolean)} method,
464      * this method must be called before the configuration is loaded. So it
465      * can't be used together with one of the constructors expecting the
466      * specification of the file to load. Instead the default constructor has to
467      * be used, then <code>setAttributeSplittingDisabled(false)</code> has to be
468      * called, and finally the configuration can be loaded using one of its
469      * <code>load()</code> methods.
470      * </p>
471      *
472      * @param attributeSplittingDisabled <b>true</b> for disabling attribute
473      *        splitting, <b>false</b> for enabling it
474      * @see #setDelimiterParsingDisabled(boolean)
475      * @since 1.6
476      */
477     public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
478     {
479         this.attributeSplittingDisabled = attributeSplittingDisabled;
480     }
481 
482     /***
483      * Returns the XML document this configuration was loaded from. The return
484      * value is <b>null</b> if this configuration was not loaded from a XML
485      * document.
486      *
487      * @return the XML document this configuration was loaded from
488      */
489     public Document getDocument()
490     {
491         return document;
492     }
493 
494     /***
495      * Removes all properties from this configuration. If this configuration
496      * was loaded from a file, the associated DOM document is also cleared.
497      */
498     public void clear()
499     {
500         super.clear();
501         document = null;
502     }
503 
504     /***
505      * Initializes this configuration from an XML document.
506      *
507      * @param document the document to be parsed
508      * @param elemRefs a flag whether references to the XML elements should be set
509      */
510     public void initProperties(Document document, boolean elemRefs)
511     {
512         if (document.getDoctype() != null)
513         {
514             setPublicID(document.getDoctype().getPublicId());
515             setSystemID(document.getDoctype().getSystemId());
516         }
517 
518         constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs, true);
519         getRootNode().setName(document.getDocumentElement().getNodeName());
520         if (elemRefs)
521         {
522             getRoot().setReference(document.getDocumentElement());
523         }
524     }
525 
526     /***
527      * Helper method for building the internal storage hierarchy. The XML
528      * elements are transformed into node objects.
529      *
530      * @param node the actual node
531      * @param element the actual XML element
532      * @param elemRefs a flag whether references to the XML elements should be set
533      * @param trim a flag whether the text content of elements should be trimmed;
534      * this controls the whitespace handling
535      */
536     private void constructHierarchy(Node node, Element element, boolean elemRefs, boolean trim)
537     {
538         boolean trimFlag = shouldTrim(element, trim);
539         processAttributes(node, element, elemRefs);
540         StringBuffer buffer = new StringBuffer();
541         NodeList list = element.getChildNodes();
542         for (int i = 0; i < list.getLength(); i++)
543         {
544             org.w3c.dom.Node w3cNode = list.item(i);
545             if (w3cNode instanceof Element)
546             {
547                 Element child = (Element) w3cNode;
548                 Node childNode = new XMLNode(child.getTagName(),
549                         elemRefs ? child : null);
550                 constructHierarchy(childNode, child, elemRefs, trimFlag);
551                 node.addChild(childNode);
552                 handleDelimiters(node, childNode, trimFlag);
553             }
554             else if (w3cNode instanceof Text)
555             {
556                 Text data = (Text) w3cNode;
557                 buffer.append(data.getData());
558             }
559         }
560 
561         String text = buffer.toString();
562         if (trimFlag)
563         {
564             text = text.trim();
565         }
566         if (text.length() > 0 || !node.hasChildren())
567         {
568             node.setValue(text);
569         }
570     }
571 
572     /***
573      * Helper method for constructing node objects for the attributes of the
574      * given XML element.
575      *
576      * @param node the actual node
577      * @param element the actual XML element
578      * @param elemRefs a flag whether references to the XML elements should be set
579      */
580     private void processAttributes(Node node, Element element, boolean elemRefs)
581     {
582         NamedNodeMap attributes = element.getAttributes();
583         for (int i = 0; i < attributes.getLength(); ++i)
584         {
585             org.w3c.dom.Node w3cNode = attributes.item(i);
586             if (w3cNode instanceof Attr)
587             {
588                 Attr attr = (Attr) w3cNode;
589                 List values;
590                 if (isAttributeSplittingDisabled())
591                 {
592                     values = Collections.singletonList(attr.getValue());
593                 }
594                 else
595                 {
596                     values = PropertyConverter.split(attr.getValue(),
597                             isDelimiterParsingDisabled() ? ATTR_VALUE_DELIMITER
598                                     : getListDelimiter());
599                 }
600 
601                 for (Iterator it = values.iterator(); it.hasNext();)
602                 {
603                     Node child = new XMLNode(attr.getName(), elemRefs ? element
604                             : null);
605                     child.setValue(it.next());
606                     node.addAttribute(child);
607                 }
608             }
609         }
610     }
611 
612     /***
613      * Deals with elements whose value is a list. In this case multiple child
614      * elements must be added.
615      *
616      * @param parent the parent element
617      * @param child the child element
618      * @param trim flag whether texts of elements should be trimmed
619      */
620     private void handleDelimiters(Node parent, Node child, boolean trim)
621     {
622         if (child.getValue() != null)
623         {
624             List values;
625             if (isDelimiterParsingDisabled())
626             {
627                 values = new ArrayList();
628                 values.add(child.getValue().toString());
629             }
630             else
631             {
632                 values = PropertyConverter.split(child.getValue().toString(),
633                     getListDelimiter(), trim);
634             }
635 
636             if (values.size() > 1)
637             {
638                 Iterator it = values.iterator();
639                 // Create new node for the original child's first value
640                 Node c = createNode(child.getName());
641                 c.setValue(it.next());
642                 // Copy original attributes to the new node
643                 for (Iterator itAttrs = child.getAttributes().iterator(); itAttrs
644                         .hasNext();)
645                 {
646                     Node ndAttr = (Node) itAttrs.next();
647                     ndAttr.setReference(null);
648                     c.addAttribute(ndAttr);
649                 }
650                 parent.remove(child);
651                 parent.addChild(c);
652 
653                 // add multiple new children
654                 while (it.hasNext())
655                 {
656                     c = new XMLNode(child.getName(), null);
657                     c.setValue(it.next());
658                     parent.addChild(c);
659                 }
660             }
661             else if (values.size() == 1)
662             {
663                 // we will have to replace the value because it might
664                 // contain escaped delimiters
665                 child.setValue(values.get(0));
666             }
667         }
668     }
669 
670     /***
671      * Checks whether the content of the current XML element should be trimmed.
672      * This method checks whether a <code>xml:space</code> attribute is
673      * present and evaluates its value. See <a
674      * href="http://www.w3.org/TR/REC-xml/#sec-white-space">
675      * http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
676      *
677      * @param element the current XML element
678      * @param currentTrim the current trim flag
679      * @return a flag whether the content of this element should be trimmed
680      */
681     private boolean shouldTrim(Element element, boolean currentTrim)
682     {
683         Attr attr = element.getAttributeNode(ATTR_SPACE);
684 
685         if (attr == null)
686         {
687             return currentTrim;
688         }
689         else
690         {
691             return !VALUE_PRESERVE.equals(attr.getValue());
692         }
693     }
694 
695     /***
696      * Creates the <code>DocumentBuilder</code> to be used for loading files.
697      * This implementation checks whether a specific
698      * <code>DocumentBuilder</code> has been set. If this is the case, this
699      * one is used. Otherwise a default builder is created. Depending on the
700      * value of the validating flag this builder will be a validating or a non
701      * validating <code>DocumentBuilder</code>.
702      *
703      * @return the <code>DocumentBuilder</code> for loading configuration
704      * files
705      * @throws ParserConfigurationException if an error occurs
706      * @since 1.2
707      */
708     protected DocumentBuilder createDocumentBuilder()
709             throws ParserConfigurationException
710     {
711         if (getDocumentBuilder() != null)
712         {
713             return getDocumentBuilder();
714         }
715         else
716         {
717             DocumentBuilderFactory factory = DocumentBuilderFactory
718                     .newInstance();
719             factory.setValidating(isValidating());
720             DocumentBuilder result = factory.newDocumentBuilder();
721             result.setEntityResolver(this);
722 
723             if (isValidating())
724             {
725                 // register an error handler which detects validation errors
726                 result.setErrorHandler(new DefaultHandler()
727                 {
728                     public void error(SAXParseException ex) throws SAXException
729                     {
730                         throw ex;
731                     }
732                 });
733             }
734             return result;
735         }
736     }
737 
738     /***
739      * Creates a DOM document from the internal tree of configuration nodes.
740      *
741      * @return the new document
742      * @throws ConfigurationException if an error occurs
743      */
744     protected Document createDocument() throws ConfigurationException
745     {
746         try
747         {
748             if (document == null)
749             {
750                 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
751                 Document newDocument = builder.newDocument();
752                 Element rootElem = newDocument.createElement(getRootElementName());
753                 newDocument.appendChild(rootElem);
754                 document = newDocument;
755             }
756 
757             XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
758                     isDelimiterParsingDisabled() ? (char) 0 : getListDelimiter());
759             builder.processDocument(getRoot());
760             initRootElementText(document, getRootNode().getValue());
761             return document;
762         }
763         catch (DOMException domEx)
764         {
765             throw new ConfigurationException(domEx);
766         }
767         catch (ParserConfigurationException pex)
768         {
769             throw new ConfigurationException(pex);
770         }
771     }
772 
773     /***
774      * Sets the text of the root element of a newly created XML Document.
775      *
776      * @param doc the document
777      * @param value the new text to be set
778      */
779     private void initRootElementText(Document doc, Object value)
780     {
781         Element elem = doc.getDocumentElement();
782         NodeList children = elem.getChildNodes();
783 
784         // Remove all existing text nodes
785         for (int i = 0; i < children.getLength(); i++)
786         {
787             org.w3c.dom.Node nd = children.item(i);
788             if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
789             {
790                 elem.removeChild(nd);
791             }
792         }
793 
794         if (value != null)
795         {
796             // Add a new text node
797             elem.appendChild(doc.createTextNode(String.valueOf(value)));
798         }
799     }
800 
801     /***
802      * Creates a new node object. This implementation returns an instance of the
803      * <code>XMLNode</code> class.
804      *
805      * @param name the node's name
806      * @return the new node
807      */
808     protected Node createNode(String name)
809     {
810         return new XMLNode(name, null);
811     }
812 
813     /***
814      * Loads the configuration from the given input stream.
815      *
816      * @param in the input stream
817      * @throws ConfigurationException if an error occurs
818      */
819     public void load(InputStream in) throws ConfigurationException
820     {
821         load(new InputSource(in));
822     }
823 
824     /***
825      * Load the configuration from the given reader.
826      * Note that the <code>clear()</code> method is not called, so
827      * the properties contained in the loaded file will be added to the
828      * actual set of properties.
829      *
830      * @param in An InputStream.
831      *
832      * @throws ConfigurationException if an error occurs
833      */
834     public void load(Reader in) throws ConfigurationException
835     {
836         load(new InputSource(in));
837     }
838 
839     /***
840      * Loads a configuration file from the specified input source.
841      * @param source the input source
842      * @throws ConfigurationException if an error occurs
843      */
844     private void load(InputSource source) throws ConfigurationException
845     {
846         try
847         {
848             URL sourceURL = getDelegate().getURL();
849             if (sourceURL != null)
850             {
851                 source.setSystemId(sourceURL.toString());
852             }
853 
854             DocumentBuilder builder = createDocumentBuilder();
855             Document newDocument = builder.parse(source);
856             Document oldDocument = document;
857             document = null;
858             initProperties(newDocument, oldDocument == null);
859             document = (oldDocument == null) ? newDocument : oldDocument;
860         }
861         catch (Exception e)
862         {
863             throw new ConfigurationException("Unable to load the configuration", e);
864         }
865     }
866 
867     /***
868      * Saves the configuration to the specified writer.
869      *
870      * @param writer the writer used to save the configuration
871      * @throws ConfigurationException if an error occurs
872      */
873     public void save(Writer writer) throws ConfigurationException
874     {
875         try
876         {
877             Transformer transformer = createTransformer();
878             Source source = new DOMSource(createDocument());
879             Result result = new StreamResult(writer);
880             transformer.transform(source, result);
881         }
882         catch (TransformerException e)
883         {
884             throw new ConfigurationException("Unable to save the configuration", e);
885         }
886         catch (TransformerFactoryConfigurationError e)
887         {
888             throw new ConfigurationException("Unable to save the configuration", e);
889         }
890     }
891 
892     /***
893      * Creates and initializes the transformer used for save operations. This
894      * base implementation initializes all of the default settings like
895      * indention mode and the DOCTYPE. Derived classes may overload this method
896      * if they have specific needs.
897      *
898      * @return the transformer to use for a save operation
899      * @throws TransformerException if an error occurs
900      * @since 1.3
901      */
902     protected Transformer createTransformer() throws TransformerException
903     {
904         Transformer transformer = TransformerFactory.newInstance()
905                 .newTransformer();
906 
907         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
908         if (getEncoding() != null)
909         {
910             transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
911         }
912         if (getPublicID() != null)
913         {
914             transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
915                     getPublicID());
916         }
917         if (getSystemID() != null)
918         {
919             transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
920                     getSystemID());
921         }
922 
923         return transformer;
924     }
925 
926     /***
927      * Creates a copy of this object. The new configuration object will contain
928      * the same properties as the original, but it will lose any connection to a
929      * source document (if one exists). This is to avoid race conditions if both
930      * the original and the copy are modified and then saved.
931      *
932      * @return the copy
933      */
934     public Object clone()
935     {
936         XMLConfiguration copy = (XMLConfiguration) super.clone();
937 
938         // clear document related properties
939         copy.document = null;
940         copy.setDelegate(copy.createDelegate());
941         // clear all references in the nodes, too
942         clearReferences(copy.getRootNode());
943 
944         return copy;
945     }
946 
947     /***
948      * Creates the file configuration delegate for this object. This implementation
949      * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
950      * that deals with some specialities of <code>XMLConfiguration</code>.
951      * @return the delegate for this object
952      */
953     protected FileConfigurationDelegate createDelegate()
954     {
955         return new XMLFileConfigurationDelegate();
956     }
957 
958     /***
959      * Adds a collection of nodes directly to this configuration. This
960      * implementation ensures that the nodes to be added are of the correct node
961      * type (they have to be converted to <code>XMLNode</code> if necessary).
962      *
963      * @param key the key where the nodes are to be added
964      * @param nodes the collection with the new nodes
965      * @since 1.5
966      */
967     public void addNodes(String key, Collection nodes)
968     {
969         Collection xmlNodes;
970 
971         if (nodes != null && !nodes.isEmpty())
972         {
973             xmlNodes = new ArrayList(nodes.size());
974             for (Iterator it = nodes.iterator(); it.hasNext();)
975             {
976                 xmlNodes.add(convertToXMLNode((ConfigurationNode) it.next()));
977             }
978         }
979         else
980         {
981             xmlNodes = nodes;
982         }
983 
984         super.addNodes(key, xmlNodes);
985     }
986 
987     /***
988      * Converts the specified node into a <code>XMLNode</code> if necessary.
989      * This is required for nodes that are directly added, e.g. by
990      * <code>addNodes()</code>. If the passed in node is already an instance
991      * of <code>XMLNode</code>, it is directly returned, and conversion
992      * stops. Otherwise a new <code>XMLNode</code> is created, and the
993      * children are also converted.
994      *
995      * @param node the node to be converted
996      * @return the converted node
997      */
998     private XMLNode convertToXMLNode(ConfigurationNode node)
999     {
1000         if (node instanceof XMLNode)
1001         {
1002             return (XMLNode) node;
1003         }
1004 
1005         XMLNode nd = (XMLNode) createNode(node.getName());
1006         nd.setValue(node.getValue());
1007         nd.setAttribute(node.isAttribute());
1008         for (Iterator it = node.getChildren().iterator(); it.hasNext();)
1009         {
1010             nd.addChild(convertToXMLNode((ConfigurationNode) it.next()));
1011         }
1012         for (Iterator it = node.getAttributes().iterator(); it.hasNext();)
1013         {
1014             nd.addAttribute(convertToXMLNode((ConfigurationNode) it.next()));
1015         }
1016         return nd;
1017     }
1018 
1019     /***
1020      * <p>
1021      * Registers the specified DTD URL for the specified public identifier.
1022      * </p>
1023      * <p>
1024      * <code>XMLConfiguration</code> contains an internal
1025      * <code>EntityResolver</code> implementation. This maps
1026      * <code>PUBLICID</code>'s to URLs (from which the resource will be
1027      * loaded). A common use case for this method is to register local URLs
1028      * (possibly computed at runtime by a class loader) for DTDs. This allows
1029      * the performance advantage of using a local version without having to
1030      * ensure every <code>SYSTEM</code> URI on every processed XML document is
1031      * local. This implementation provides only basic functionality. If more
1032      * sophisticated features are required, using
1033      * {@link #setDocumentBuilder(DocumentBuilder)} to set a custom
1034      * <code>DocumentBuilder</code> (which also can be initialized with a
1035      * custom <code>EntityResolver</code>) is recommended.
1036      * </p>
1037      * <p>
1038      * <strong>Note:</strong> This method will have no effect when a custom
1039      * <code>DocumentBuilder</code> has been set. (Setting a custom
1040      * <code>DocumentBuilder</code> overrides the internal implementation.)
1041      * </p>
1042      * <p>
1043      * <strong>Note:</strong> This method must be called before the
1044      * configuration is loaded. So the default constructor of
1045      * <code>XMLConfiguration</code> should be used, the location of the
1046      * configuration file set, <code>registerEntityId()</code> called, and
1047      * finally the <code>load()</code> method can be invoked.
1048      * </p>
1049      *
1050      * @param publicId Public identifier of the DTD to be resolved
1051      * @param entityURL The URL to use for reading this DTD
1052      * @throws IllegalArgumentException if the public ID is undefined
1053      * @since 1.5
1054      */
1055     public void registerEntityId(String publicId, URL entityURL)
1056     {
1057         if (publicId == null)
1058         {
1059             throw new IllegalArgumentException("Public ID must not be null!");
1060         }
1061         getRegisteredEntities().put(publicId, entityURL);
1062     }
1063 
1064     /***
1065      * Resolves the requested external entity. This is the default
1066      * implementation of the <code>EntityResolver</code> interface. It checks
1067      * the passed in public ID against the registered entity IDs and uses a
1068      * local URL if possible.
1069      *
1070      * @param publicId the public identifier of the entity being referenced
1071      * @param systemId the system identifier of the entity being referenced
1072      * @return an input source for the specified entity
1073      * @throws SAXException if a parsing exception occurs
1074      * @since 1.5
1075      */
1076     public InputSource resolveEntity(String publicId, String systemId)
1077             throws SAXException
1078     {
1079         // Has this system identifier been registered?
1080         URL entityURL = null;
1081         if (publicId != null)
1082         {
1083             entityURL = (URL) getRegisteredEntities().get(publicId);
1084         }
1085 
1086         if (entityURL != null)
1087         {
1088             // Obtain an InputSource for this URL. This code is based on the
1089             // createInputSourceFromURL() method of Commons Digester.
1090             try
1091             {
1092                 URLConnection connection = entityURL.openConnection();
1093                 connection.setUseCaches(false);
1094                 InputStream stream = connection.getInputStream();
1095                 InputSource source = new InputSource(stream);
1096                 source.setSystemId(entityURL.toExternalForm());
1097                 return source;
1098             }
1099             catch (IOException e)
1100             {
1101                 throw new SAXException(e);
1102             }
1103         }
1104         else
1105         {
1106             // default processing behavior
1107             return null;
1108         }
1109     }
1110 
1111     /***
1112      * Returns a map with the entity IDs that have been registered using the
1113      * <code>registerEntityId()</code> method.
1114      *
1115      * @return a map with the registered entity IDs
1116      */
1117     Map getRegisteredEntities()
1118     {
1119         return registeredEntities;
1120     }
1121 
1122     /***
1123      * A specialized <code>Node</code> class that is connected with an XML
1124      * element. Changes on a node are also performed on the associated element.
1125      */
1126     class XMLNode extends Node
1127     {
1128         /***
1129          * The serial version UID.
1130          */
1131         private static final long serialVersionUID = -4133988932174596562L;
1132 
1133         /***
1134          * Creates a new instance of <code>XMLNode</code> and initializes it
1135          * with a name and the corresponding XML element.
1136          *
1137          * @param name the node's name
1138          * @param elem the XML element
1139          */
1140         public XMLNode(String name, Element elem)
1141         {
1142             super(name);
1143             setReference(elem);
1144         }
1145 
1146         /***
1147          * Sets the value of this node. If this node is associated with an XML
1148          * element, this element will be updated, too.
1149          *
1150          * @param value the node's new value
1151          */
1152         public void setValue(Object value)
1153         {
1154             super.setValue(value);
1155 
1156             if (getReference() != null && document != null)
1157             {
1158                 if (isAttribute())
1159                 {
1160                     updateAttribute();
1161                 }
1162                 else
1163                 {
1164                     updateElement(value);
1165                 }
1166             }
1167         }
1168 
1169         /***
1170          * Updates the associated XML elements when a node is removed.
1171          */
1172         protected void removeReference()
1173         {
1174             if (getReference() != null)
1175             {
1176                 Element element = (Element) getReference();
1177                 if (isAttribute())
1178                 {
1179                     updateAttribute();
1180                 }
1181                 else
1182                 {
1183                     org.w3c.dom.Node parentElem = element.getParentNode();
1184                     if (parentElem != null)
1185                     {
1186                         parentElem.removeChild(element);
1187                     }
1188                 }
1189             }
1190         }
1191 
1192         /***
1193          * Updates the node's value if it represents an element node.
1194          *
1195          * @param value the new value
1196          */
1197         private void updateElement(Object value)
1198         {
1199             Text txtNode = findTextNodeForUpdate();
1200             if (value == null)
1201             {
1202                 // remove text
1203                 if (txtNode != null)
1204                 {
1205                     ((Element) getReference()).removeChild(txtNode);
1206                 }
1207             }
1208             else
1209             {
1210                 if (txtNode == null)
1211                 {
1212                     txtNode = document
1213                             .createTextNode(PropertyConverter.escapeDelimiters(
1214                                     value.toString(), getListDelimiter()));
1215                     if (((Element) getReference()).getFirstChild() != null)
1216                     {
1217                         ((Element) getReference()).insertBefore(txtNode,
1218                                 ((Element) getReference()).getFirstChild());
1219                     }
1220                     else
1221                     {
1222                         ((Element) getReference()).appendChild(txtNode);
1223                     }
1224                 }
1225                 else
1226                 {
1227                     txtNode.setNodeValue(PropertyConverter.escapeDelimiters(
1228                             value.toString(), getListDelimiter()));
1229                 }
1230             }
1231         }
1232 
1233         /***
1234          * Updates the node's value if it represents an attribute.
1235          *
1236          */
1237         private void updateAttribute()
1238         {
1239             XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter());
1240         }
1241 
1242         /***
1243          * Returns the only text node of this element for update. This method is
1244          * called when the element's text changes. Then all text nodes except
1245          * for the first are removed. A reference to the first is returned or
1246          * <b>null </b> if there is no text node at all.
1247          *
1248          * @return the first and only text node
1249          */
1250         private Text findTextNodeForUpdate()
1251         {
1252             Text result = null;
1253             Element elem = (Element) getReference();
1254             // Find all Text nodes
1255             NodeList children = elem.getChildNodes();
1256             Collection textNodes = new ArrayList();
1257             for (int i = 0; i < children.getLength(); i++)
1258             {
1259                 org.w3c.dom.Node nd = children.item(i);
1260                 if (nd instanceof Text)
1261                 {
1262                     if (result == null)
1263                     {
1264                         result = (Text) nd;
1265                     }
1266                     else
1267                     {
1268                         textNodes.add(nd);
1269                     }
1270                 }
1271             }
1272 
1273             // We don't want CDATAs
1274             if (result instanceof CDATASection)
1275             {
1276                 textNodes.add(result);
1277                 result = null;
1278             }
1279 
1280             // Remove all but the first Text node
1281             for (Iterator it = textNodes.iterator(); it.hasNext();)
1282             {
1283                 elem.removeChild((org.w3c.dom.Node) it.next());
1284             }
1285             return result;
1286         }
1287     }
1288 
1289     /***
1290      * A concrete <code>BuilderVisitor</code> that can construct XML
1291      * documents.
1292      */
1293     static class XMLBuilderVisitor extends BuilderVisitor
1294     {
1295         /*** Stores the document to be constructed. */
1296         private Document document;
1297 
1298         /*** Stores the list delimiter.*/
1299         private char listDelimiter = AbstractConfiguration.
1300                 getDefaultListDelimiter();
1301 
1302         /***
1303          * Creates a new instance of <code>XMLBuilderVisitor</code>
1304          *
1305          * @param doc the document to be created
1306          * @param listDelimiter the delimiter for attribute properties with multiple values
1307          */
1308         public XMLBuilderVisitor(Document doc, char listDelimiter)
1309         {
1310             document = doc;
1311             this.listDelimiter = listDelimiter;
1312         }
1313 
1314         /***
1315          * Processes the node hierarchy and adds new nodes to the document.
1316          *
1317          * @param rootNode the root node
1318          */
1319         public void processDocument(Node rootNode)
1320         {
1321             rootNode.visit(this, null);
1322         }
1323 
1324         /***
1325          * Inserts a new node. This implementation ensures that the correct
1326          * XML element is created and inserted between the given siblings.
1327          *
1328          * @param newNode the node to insert
1329          * @param parent the parent node
1330          * @param sibling1 the first sibling
1331          * @param sibling2 the second sibling
1332          * @return the new node
1333          */
1334         protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
1335         {
1336             if (newNode.isAttribute())
1337             {
1338                 updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter);
1339                 return null;
1340             }
1341 
1342             else
1343             {
1344                 Element elem = document.createElement(newNode.getName());
1345                 if (newNode.getValue() != null)
1346                 {
1347                     String txt = newNode.getValue().toString();
1348                     if (listDelimiter != 0)
1349                     {
1350                         txt = PropertyConverter.escapeDelimiters(txt, listDelimiter);
1351                     }
1352                     elem.appendChild(document.createTextNode(txt));
1353                 }
1354                 if (sibling2 == null)
1355                 {
1356                     getElement(parent).appendChild(elem);
1357                 }
1358                 else if (sibling1 != null)
1359                 {
1360                     getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
1361                 }
1362                 else
1363                 {
1364                     getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
1365                 }
1366                 return elem;
1367             }
1368         }
1369 
1370         /***
1371          * Helper method for updating the value of the specified node's
1372          * attribute with the given name.
1373          *
1374          * @param node the affected node
1375          * @param elem the element that is associated with this node
1376          * @param name the name of the affected attribute
1377          * @param listDelimiter the delimiter for attributes with multiple values
1378          */
1379         private static void updateAttribute(Node node, Element elem, String name, char listDelimiter)
1380         {
1381             if (node != null && elem != null)
1382             {
1383                 List attrs = node.getAttributes(name);
1384                 StringBuffer buf = new StringBuffer();
1385                 char delimiter = (listDelimiter != 0) ? listDelimiter : ATTR_VALUE_DELIMITER;
1386                 for (Iterator it = attrs.iterator(); it.hasNext();)
1387                 {
1388                     Node attr = (Node) it.next();
1389                     if (attr.getValue() != null)
1390                     {
1391                         if (buf.length() > 0)
1392                         {
1393                             buf.append(delimiter);
1394                         }
1395                         buf.append(PropertyConverter.escapeDelimiters(attr
1396                                 .getValue().toString(), delimiter));
1397                     }
1398                     attr.setReference(elem);
1399                 }
1400 
1401                 if (buf.length() < 1)
1402                 {
1403                     elem.removeAttribute(name);
1404                 }
1405                 else
1406                 {
1407                     elem.setAttribute(name, buf.toString());
1408                 }
1409             }
1410         }
1411 
1412         /***
1413          * Updates the value of the specified attribute of the given node.
1414          * Because there can be multiple child nodes representing this attribute
1415          * the new value is determined by iterating over all those child nodes.
1416          *
1417          * @param node the affected node
1418          * @param name the name of the attribute
1419          * @param listDelimiter the delimiter for attributes with multiple values
1420          */
1421         static void updateAttribute(Node node, String name, char listDelimiter)
1422         {
1423             if (node != null)
1424             {
1425                 updateAttribute(node, (Element) node.getReference(), name, listDelimiter);
1426             }
1427         }
1428 
1429         /***
1430          * Helper method for accessing the element of the specified node.
1431          *
1432          * @param node the node
1433          * @return the element of this node
1434          */
1435         private Element getElement(Node node)
1436         {
1437             // special treatment for root node of the hierarchy
1438             return (node.getName() != null && node.getReference() != null) ? (Element) node
1439                     .getReference()
1440                     : document.getDocumentElement();
1441         }
1442     }
1443 
1444     /***
1445      * A special implementation of the <code>FileConfiguration</code> interface that is
1446      * used internally to implement the <code>FileConfiguration</code> methods
1447      * for <code>XMLConfiguration</code>, too.
1448      */
1449     private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
1450     {
1451         public void load(InputStream in) throws ConfigurationException
1452         {
1453             XMLConfiguration.this.load(in);
1454         }
1455     }
1456 }