001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import javax.xml.parsers.DocumentBuilder;
021import javax.xml.parsers.DocumentBuilderFactory;
022import javax.xml.parsers.ParserConfigurationException;
023import javax.xml.transform.OutputKeys;
024import javax.xml.transform.Result;
025import javax.xml.transform.Source;
026import javax.xml.transform.Transformer;
027import javax.xml.transform.dom.DOMSource;
028import javax.xml.transform.stream.StreamResult;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.Reader;
032import java.io.StringReader;
033import java.io.StringWriter;
034import java.io.Writer;
035import java.net.URL;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.Iterator;
041import java.util.Map;
042
043import org.apache.commons.configuration2.convert.ListDelimiterHandler;
044import org.apache.commons.configuration2.ex.ConfigurationException;
045import org.apache.commons.configuration2.io.ConfigurationLogger;
046import org.apache.commons.configuration2.io.FileLocator;
047import org.apache.commons.configuration2.io.FileLocatorAware;
048import org.apache.commons.configuration2.io.InputStreamSupport;
049import org.apache.commons.configuration2.resolver.DefaultEntityResolver;
050import org.apache.commons.configuration2.tree.ImmutableNode;
051import org.apache.commons.configuration2.tree.NodeTreeWalker;
052import org.apache.commons.configuration2.tree.ReferenceNodeHandler;
053import org.apache.commons.lang3.StringUtils;
054import org.apache.commons.lang3.mutable.MutableObject;
055import org.w3c.dom.Attr;
056import org.w3c.dom.CDATASection;
057import org.w3c.dom.Document;
058import org.w3c.dom.Element;
059import org.w3c.dom.NamedNodeMap;
060import org.w3c.dom.Node;
061import org.w3c.dom.NodeList;
062import org.w3c.dom.Text;
063import org.xml.sax.EntityResolver;
064import org.xml.sax.InputSource;
065import org.xml.sax.SAXException;
066import org.xml.sax.SAXParseException;
067import org.xml.sax.helpers.DefaultHandler;
068
069/**
070 * <p>
071 * A specialized hierarchical configuration class that is able to parse XML
072 * documents.
073 * </p>
074 * <p>
075 * The parsed document will be stored keeping its structure. The class also
076 * tries to preserve as much information from the loaded XML document as
077 * possible, including comments and processing instructions. These will be
078 * contained in documents created by the {@code save()} methods, too.
079 * </p>
080 * <p>
081 * Like other file based configuration classes this class maintains the name and
082 * path to the loaded configuration file. These properties can be altered using
083 * several setter methods, but they are not modified by {@code save()} and
084 * {@code load()} methods. If XML documents contain relative paths to other
085 * documents (e.g. to a DTD), these references are resolved based on the path
086 * set for this configuration.
087 * </p>
088 * <p>
089 * By inheriting from {@link AbstractConfiguration} this class provides some
090 * extended functionality, e.g. interpolation of property values. Like in
091 * {@link PropertiesConfiguration} property values can contain delimiter
092 * characters (the comma ',' per default) and are then split into multiple
093 * values. This works for XML attributes and text content of elements as well.
094 * The delimiter can be escaped by a backslash. As an example consider the
095 * following XML fragment:
096 * </p>
097 *
098 * <pre>
099 * &lt;config&gt;
100 *   &lt;array&gt;10,20,30,40&lt;/array&gt;
101 *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
102 *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
103 * &lt;/config&gt;
104 * </pre>
105 *
106 * <p>
107 * Here the content of the {@code array} element will be split at the commas, so
108 * the {@code array} key will be assigned 4 values. In the {@code scalar}
109 * property and the {@code text} attribute of the {@code cite} element the comma
110 * is escaped, so that no splitting is performed.
111 * </p>
112 * <p>
113 * The configuration API allows setting multiple values for a single attribute,
114 * e.g. something like the following is legal (assuming that the default
115 * expression engine is used):
116 * </p>
117 *
118 * <pre>
119 * XMLConfiguration config = new XMLConfiguration();
120 * config.addProperty(&quot;test.dir[@name]&quot;, &quot;C:\\Temp\\&quot;);
121 * config.addProperty(&quot;test.dir[@name]&quot;, &quot;D:\\Data\\&quot;);
122 * </pre>
123 *
124 * <p>
125 * However, in XML such a constellation is not supported; an attribute can
126 * appear only once for a single element. Therefore, an attempt to save a
127 * configuration which violates this condition will throw an exception.
128 * </p>
129 * <p>
130 * Like other {@code Configuration} implementations, {@code XMLConfiguration}
131 * uses a {@link ListDelimiterHandler} object for controlling list split
132 * operations. Per default, a list delimiter handler object is set which
133 * disables this feature. XML has a built-in support for complex structures
134 * including list properties; therefore, list splitting is not that relevant for
135 * this configuration type. Nevertheless, by setting an alternative
136 * {@code ListDelimiterHandler} implementation, this feature can be enabled. It
137 * works as for any other concrete {@code Configuration} implementation.
138 * </p>
139 * <p>
140 * Whitespace in the content of XML documents is trimmed per default. In most
141 * cases this is desired. However, sometimes whitespace is indeed important and
142 * should be treated as part of the value of a property as in the following
143 * example:
144 * </p>
145 * <pre>
146 *   &lt;indent&gt;    &lt;/indent&gt;
147 * </pre>
148 *
149 * <p>
150 * Per default the spaces in the {@code indent} element will be trimmed
151 * resulting in an empty element. To tell {@code XMLConfiguration} that spaces
152 * are relevant the {@code xml:space} attribute can be used, which is defined in
153 * the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
154 * specification</a>. This will look as follows:
155 * </p>
156 * <pre>
157 *   &lt;indent <strong>xml:space=&quot;preserve&quot;</strong>&gt;    &lt;/indent&gt;
158 * </pre>
159 *
160 * <p>
161 * The value of the {@code indent} property will now contain the spaces.
162 * </p>
163 * <p>
164 * {@code XMLConfiguration} implements the {@link FileBasedConfiguration}
165 * interface and thus can be used together with a file-based builder to load XML
166 * configuration files from various sources like files, URLs, or streams.
167 * </p>
168 * <p>
169 * Like other {@code Configuration} implementations, this class uses a
170 * {@code Synchronizer} object to control concurrent access. By choosing a
171 * suitable implementation of the {@code Synchronizer} interface, an instance
172 * can be made thread-safe or not. Note that access to most of the properties
173 * typically set through a builder is not protected by the {@code Synchronizer}.
174 * The intended usage is that these properties are set once at construction time
175 * through the builder and after that remain constant. If you wish to change
176 * such properties during life time of an instance, you have to use the
177 * {@code lock()} and {@code unlock()} methods manually to ensure that other
178 * threads see your changes.
179 * </p>
180 * <p>
181 * More information about the basic functionality supported by
182 * {@code XMLConfiguration} can be found at the user's guide at
183 * <a href="http://commons.apache.org/proper/commons-configuration/userguide/howto_basicfeatures.html">
184 * Basic features and AbstractConfiguration</a>. There is
185 * also a separate chapter dealing with
186 * <a href="commons.apache.org/proper/commons-configuration/userguide/howto_xml.html">
187 * XML Configurations</a> in special.
188 * </p>
189 *
190 * @since commons-configuration 1.0
191 * @author J&ouml;rg Schaible
192 * @version $Id: XMLConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
193 */
194public class XMLConfiguration extends BaseHierarchicalConfiguration implements
195        FileBasedConfiguration, FileLocatorAware, InputStreamSupport
196{
197    /** Constant for the default root element name. */
198    private static final String DEFAULT_ROOT_NAME = "configuration";
199
200    /** Constant for the name of the space attribute.*/
201    private static final String ATTR_SPACE = "xml:space";
202
203    /** Constant for an internally used space attribute. */
204    private static final String ATTR_SPACE_INTERNAL = "config-xml:space";
205
206    /** Constant for the xml:space value for preserving whitespace.*/
207    private static final String VALUE_PRESERVE = "preserve";
208
209    /** Schema Langauge key for the parser */
210    private static final String JAXP_SCHEMA_LANGUAGE =
211        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
212
213    /** Schema Language for the parser */
214    private static final String W3C_XML_SCHEMA =
215        "http://www.w3.org/2001/XMLSchema";
216
217    /** Stores the name of the root element. */
218    private String rootElementName;
219
220    /** Stores the public ID from the DOCTYPE.*/
221    private String publicID;
222
223    /** Stores the system ID from the DOCTYPE.*/
224    private String systemID;
225
226    /** Stores the document builder that should be used for loading.*/
227    private DocumentBuilder documentBuilder;
228
229    /** Stores a flag whether DTD or Schema validation should be performed.*/
230    private boolean validating;
231
232    /** Stores a flag whether DTD or Schema validation is used */
233    private boolean schemaValidation;
234
235    /** The EntityResolver to use */
236    private EntityResolver entityResolver = new DefaultEntityResolver();
237
238    /** The current file locator. */
239    private FileLocator locator;
240
241    /**
242     * Creates a new instance of {@code XMLConfiguration}.
243     */
244    public XMLConfiguration()
245    {
246        super();
247        initLogger(new ConfigurationLogger(XMLConfiguration.class));
248    }
249
250    /**
251     * Creates a new instance of {@code XMLConfiguration} and copies the
252     * content of the passed in configuration into this object. Note that only
253     * the data of the passed in configuration will be copied. If, for instance,
254     * the other configuration is a {@code XMLConfiguration}, too,
255     * things like comments or processing instructions will be lost.
256     *
257     * @param c the configuration to copy
258     * @since 1.4
259     */
260    public XMLConfiguration(final HierarchicalConfiguration<ImmutableNode> c)
261    {
262        super(c);
263        rootElementName =
264                (c != null) ? c.getRootElementName() : null;
265        initLogger(new ConfigurationLogger(XMLConfiguration.class));
266    }
267
268    /**
269     * Returns the name of the root element. If this configuration was loaded
270     * from a XML document, the name of this document's root element is
271     * returned. Otherwise it is possible to set a name for the root element
272     * that will be used when this configuration is stored.
273     *
274     * @return the name of the root element
275     */
276    @Override
277    protected String getRootElementNameInternal()
278    {
279        final Document doc = getDocument();
280        if (doc == null)
281        {
282            return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
283        }
284        return doc.getDocumentElement().getNodeName();
285    }
286
287    /**
288     * Sets the name of the root element. This name is used when this
289     * configuration object is stored in an XML file. Note that setting the name
290     * of the root element works only if this configuration has been newly
291     * created. If the configuration was loaded from an XML file, the name
292     * cannot be changed and an {@code UnsupportedOperationException}
293     * exception is thrown. Whether this configuration has been loaded from an
294     * XML document or not can be found out using the {@code getDocument()}
295     * method.
296     *
297     * @param name the name of the root element
298     */
299    public void setRootElementName(final String name)
300    {
301        beginRead(true);
302        try
303        {
304            if (getDocument() != null)
305            {
306                throw new UnsupportedOperationException(
307                        "The name of the root element "
308                                + "cannot be changed when loaded from an XML document!");
309            }
310            rootElementName = name;
311        }
312        finally
313        {
314            endRead();
315        }
316    }
317
318    /**
319     * Returns the {@code DocumentBuilder} object that is used for
320     * loading documents. If no specific builder has been set, this method
321     * returns <b>null</b>.
322     *
323     * @return the {@code DocumentBuilder} for loading new documents
324     * @since 1.2
325     */
326    public DocumentBuilder getDocumentBuilder()
327    {
328        return documentBuilder;
329    }
330
331    /**
332     * Sets the {@code DocumentBuilder} object to be used for loading
333     * documents. This method makes it possible to specify the exact document
334     * builder. So an application can create a builder, configure it for its
335     * special needs, and then pass it to this method.
336     *
337     * @param documentBuilder the document builder to be used; if undefined, a
338     * default builder will be used
339     * @since 1.2
340     */
341    public void setDocumentBuilder(final DocumentBuilder documentBuilder)
342    {
343        this.documentBuilder = documentBuilder;
344    }
345
346    /**
347     * Returns the public ID of the DOCTYPE declaration from the loaded XML
348     * document. This is <b>null</b> if no document has been loaded yet or if
349     * the document does not contain a DOCTYPE declaration with a public ID.
350     *
351     * @return the public ID
352     * @since 1.3
353     */
354    public String getPublicID()
355    {
356        beginRead(false);
357        try
358        {
359            return publicID;
360        }
361        finally
362        {
363            endRead();
364        }
365    }
366
367    /**
368     * Sets the public ID of the DOCTYPE declaration. When this configuration is
369     * saved, a DOCTYPE declaration will be constructed that contains this
370     * public ID.
371     *
372     * @param publicID the public ID
373     * @since 1.3
374     */
375    public void setPublicID(final String publicID)
376    {
377        beginWrite(false);
378        try
379        {
380            this.publicID = publicID;
381        }
382        finally
383        {
384            endWrite();
385        }
386    }
387
388    /**
389     * Returns the system ID of the DOCTYPE declaration from the loaded XML
390     * document. This is <b>null</b> if no document has been loaded yet or if
391     * the document does not contain a DOCTYPE declaration with a system ID.
392     *
393     * @return the system ID
394     * @since 1.3
395     */
396    public String getSystemID()
397    {
398        beginRead(false);
399        try
400        {
401            return systemID;
402        }
403        finally
404        {
405            endRead();
406        }
407    }
408
409    /**
410     * Sets the system ID of the DOCTYPE declaration. When this configuration is
411     * saved, a DOCTYPE declaration will be constructed that contains this
412     * system ID.
413     *
414     * @param systemID the system ID
415     * @since 1.3
416     */
417    public void setSystemID(final String systemID)
418    {
419        beginWrite(false);
420        try
421        {
422            this.systemID = systemID;
423        }
424        finally
425        {
426            endWrite();
427        }
428    }
429
430    /**
431     * Returns the value of the validating flag.
432     *
433     * @return the validating flag
434     * @since 1.2
435     */
436    public boolean isValidating()
437    {
438        return validating;
439    }
440
441    /**
442     * Sets the value of the validating flag. This flag determines whether
443     * DTD/Schema validation should be performed when loading XML documents. This
444     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
445     *
446     * @param validating the validating flag
447     * @since 1.2
448     */
449    public void setValidating(final boolean validating)
450    {
451        if (!schemaValidation)
452        {
453            this.validating = validating;
454        }
455    }
456
457
458    /**
459     * Returns the value of the schemaValidation flag.
460     *
461     * @return the schemaValidation flag
462     * @since 1.7
463     */
464    public boolean isSchemaValidation()
465    {
466        return schemaValidation;
467    }
468
469    /**
470     * Sets the value of the schemaValidation flag. This flag determines whether
471     * DTD or Schema validation should be used. This
472     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
473     * If set to true the XML document must contain a schemaLocation definition
474     * that provides resolvable hints to the required schemas.
475     *
476     * @param schemaValidation the validating flag
477     * @since 1.7
478     */
479    public void setSchemaValidation(final boolean schemaValidation)
480    {
481        this.schemaValidation = schemaValidation;
482        if (schemaValidation)
483        {
484            this.validating = true;
485        }
486    }
487
488    /**
489     * Sets a new EntityResolver. Setting this will cause RegisterEntityId to have no
490     * effect.
491     * @param resolver The EntityResolver to use.
492     * @since 1.7
493     */
494    public void setEntityResolver(final EntityResolver resolver)
495    {
496        this.entityResolver = resolver;
497    }
498
499    /**
500     * Returns the EntityResolver.
501     * @return The EntityResolver.
502     * @since 1.7
503     */
504    public EntityResolver getEntityResolver()
505    {
506        return this.entityResolver;
507    }
508
509    /**
510     * Returns the XML document this configuration was loaded from. The return
511     * value is <b>null</b> if this configuration was not loaded from a XML
512     * document.
513     *
514     * @return the XML document this configuration was loaded from
515     */
516    public Document getDocument()
517    {
518        final XMLDocumentHelper docHelper = getDocumentHelper();
519        return (docHelper != null) ? docHelper.getDocument() : null;
520    }
521
522    /**
523     * Returns the helper object for managing the underlying document.
524     *
525     * @return the {@code XMLDocumentHelper}
526     */
527    private XMLDocumentHelper getDocumentHelper()
528    {
529        final ReferenceNodeHandler handler = getReferenceHandler();
530        return (XMLDocumentHelper) handler.getReference(handler.getRootNode());
531    }
532
533    /**
534     * Returns the extended node handler with support for references.
535     *
536     * @return the {@code ReferenceNodeHandler}
537     */
538    private ReferenceNodeHandler getReferenceHandler()
539    {
540        return getSubConfigurationParentModel().getReferenceNodeHandler();
541    }
542
543    /**
544     * Initializes this configuration from an XML document.
545     *
546     * @param docHelper the helper object with the document to be parsed
547     * @param elemRefs a flag whether references to the XML elements should be set
548     */
549    private void initProperties(final XMLDocumentHelper docHelper, final boolean elemRefs)
550    {
551        final Document document = docHelper.getDocument();
552        setPublicID(docHelper.getSourcePublicID());
553        setSystemID(docHelper.getSourceSystemID());
554
555        final ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder();
556        final MutableObject<String> rootValue = new MutableObject<>();
557        final Map<ImmutableNode, Object> elemRefMap =
558                elemRefs ? new HashMap<>() : null;
559        final Map<String, String> attributes =
560                constructHierarchy(rootBuilder, rootValue,
561                        document.getDocumentElement(), elemRefMap, true, 0);
562        attributes.remove(ATTR_SPACE_INTERNAL);
563        final ImmutableNode top =
564                rootBuilder.value(rootValue.getValue())
565                        .addAttributes(attributes).create();
566        getSubConfigurationParentModel().mergeRoot(top,
567                document.getDocumentElement().getTagName(), elemRefMap,
568                elemRefs ? docHelper : null, this);
569    }
570
571    /**
572     * Helper method for building the internal storage hierarchy. The XML
573     * elements are transformed into node objects.
574     *
575     * @param node a builder for the current node
576     * @param refValue stores the text value of the element
577     * @param element the current XML element
578     * @param elemRefs a map for assigning references objects to nodes; can be
579     *        <b>null</b>, then reference objects are irrelevant
580     * @param trim a flag whether the text content of elements should be
581     *        trimmed; this controls the whitespace handling
582     * @param level the current level in the hierarchy
583     * @return a map with all attribute values extracted for the current node;
584     *         this map also contains the value of the trim flag for this node
585     *         under the key {@value #ATTR_SPACE}
586     */
587    private Map<String, String> constructHierarchy(final ImmutableNode.Builder node,
588            final MutableObject<String> refValue, final Element element,
589            final Map<ImmutableNode, Object> elemRefs, final boolean trim, final int level)
590    {
591        final boolean trimFlag = shouldTrim(element, trim);
592        final Map<String, String> attributes = processAttributes(element);
593        attributes.put(ATTR_SPACE_INTERNAL, String.valueOf(trimFlag));
594        final StringBuilder buffer = new StringBuilder();
595        final NodeList list = element.getChildNodes();
596        boolean hasChildren = false;
597
598        for (int i = 0; i < list.getLength(); i++)
599        {
600            final org.w3c.dom.Node w3cNode = list.item(i);
601            if (w3cNode instanceof Element)
602            {
603                final Element child = (Element) w3cNode;
604                final ImmutableNode.Builder childNode = new ImmutableNode.Builder();
605                childNode.name(child.getTagName());
606                final MutableObject<String> refChildValue =
607                        new MutableObject<>();
608                final Map<String, String> attrmap =
609                        constructHierarchy(childNode, refChildValue, child,
610                                elemRefs, trimFlag, level + 1);
611                final Boolean childTrim = Boolean.valueOf(attrmap.remove(ATTR_SPACE_INTERNAL));
612                childNode.addAttributes(attrmap);
613                final ImmutableNode newChild =
614                        createChildNodeWithValue(node, childNode, child,
615                                refChildValue.getValue(),
616                                childTrim.booleanValue(), attrmap, elemRefs);
617                if (elemRefs != null && !elemRefs.containsKey(newChild))
618                {
619                    elemRefs.put(newChild, child);
620                }
621                hasChildren = true;
622            }
623            else if (w3cNode instanceof Text)
624            {
625                final Text data = (Text) w3cNode;
626                buffer.append(data.getData());
627            }
628        }
629
630        boolean childrenFlag = false;
631        if (hasChildren || trimFlag)
632        {
633            childrenFlag = hasChildren || attributes.size() > 1;
634        }
635        final String text = determineValue(buffer.toString(), childrenFlag, trimFlag);
636        if (text.length() > 0 || (!childrenFlag && level != 0))
637        {
638            refValue.setValue(text);
639        }
640        return attributes;
641    }
642
643    /**
644     * Determines the value of a configuration node. This method mainly checks
645     * whether the text value is to be trimmed or not. This is normally defined
646     * by the trim flag. However, if the node has children and its content is
647     * only whitespace, then it makes no sense to store any value; this would
648     * only scramble layout when the configuration is saved again.
649     *
650     * @param content the text content of this node
651     * @param hasChildren a flag whether the node has children
652     * @param trimFlag the trim flag
653     * @return the value to be stored for this node
654     */
655    private static String determineValue(final String content, final boolean hasChildren,
656            final boolean trimFlag)
657    {
658        final boolean shouldTrim =
659                trimFlag || (StringUtils.isBlank(content) && hasChildren);
660        return shouldTrim ? content.trim() : content;
661    }
662
663    /**
664     * Helper method for initializing the attributes of a configuration node
665     * from the given XML element.
666     *
667     * @param element the current XML element
668     * @return a map with all attribute values extracted for the current node
669     */
670    private static Map<String, String> processAttributes(final Element element)
671    {
672        final NamedNodeMap attributes = element.getAttributes();
673        final Map<String, String> attrmap = new HashMap<>();
674
675        for (int i = 0; i < attributes.getLength(); ++i)
676        {
677            final org.w3c.dom.Node w3cNode = attributes.item(i);
678            if (w3cNode instanceof Attr)
679            {
680                final Attr attr = (Attr) w3cNode;
681                attrmap.put(attr.getName(), attr.getValue());
682            }
683        }
684
685        return attrmap;
686    }
687
688    /**
689     * Creates a new child node, assigns its value, and adds it to its parent.
690     * This method also deals with elements whose value is a list. In this case
691     * multiple child elements must be added. The return value is the first
692     * child node which was added.
693     *
694     * @param parent the builder for the parent element
695     * @param child the builder for the child element
696     * @param elem the associated XML element
697     * @param value the value of the child element
698     * @param trim flag whether texts of elements should be trimmed
699     * @param attrmap a map with the attributes of the current node
700     * @param elemRefs a map for assigning references objects to nodes; can be
701     *        <b>null</b>, then reference objects are irrelevant
702     * @return the first child node added to the parent
703     */
704    private ImmutableNode createChildNodeWithValue(final ImmutableNode.Builder parent,
705            final ImmutableNode.Builder child, final Element elem, final String value,
706            final boolean trim, final Map<String, String> attrmap,
707            final Map<ImmutableNode, Object> elemRefs)
708    {
709        ImmutableNode addedChildNode;
710        Collection<String> values;
711
712        if (value != null)
713        {
714            values = getListDelimiterHandler().split(value, trim);
715        }
716        else
717        {
718            values = Collections.emptyList();
719        }
720
721        if (values.size() > 1)
722        {
723            final Map<ImmutableNode, Object> refs = isSingleElementList(elem) ? elemRefs : null;
724            final Iterator<String> it = values.iterator();
725            // Create new node for the original child's first value
726            child.value(it.next());
727            addedChildNode = child.create();
728            parent.addChild(addedChildNode);
729            XMLListReference.assignListReference(refs, addedChildNode, elem);
730
731            // add multiple new children
732            while (it.hasNext())
733            {
734                final ImmutableNode.Builder c = new ImmutableNode.Builder();
735                c.name(addedChildNode.getNodeName());
736                c.value(it.next());
737                c.addAttributes(attrmap);
738                final ImmutableNode newChild = c.create();
739                parent.addChild(newChild);
740                XMLListReference.assignListReference(refs, newChild, null);
741            }
742        }
743        else if (values.size() == 1)
744        {
745            // we will have to replace the value because it might
746            // contain escaped delimiters
747            child.value(values.iterator().next());
748            addedChildNode = child.create();
749            parent.addChild(addedChildNode);
750        }
751        else
752        {
753            addedChildNode = child.create();
754            parent.addChild(addedChildNode);
755        }
756
757        return addedChildNode;
758    }
759
760    /**
761     * Checks whether an element defines a complete list. If this is the case,
762     * extended list handling can be applied.
763     *
764     * @param element the element to be checked
765     * @return a flag whether this is the only element defining the list
766     */
767    private static boolean isSingleElementList(final Element element)
768    {
769        final Node parentNode = element.getParentNode();
770        return countChildElements(parentNode, element.getTagName()) == 1;
771    }
772
773    /**
774     * Determines the number of child elements of this given node with the
775     * specified node name.
776     *
777     * @param parent the parent node
778     * @param name the name in question
779     * @return the number of child elements with this name
780     */
781    private static int countChildElements(final Node parent, final String name)
782    {
783        final NodeList childNodes = parent.getChildNodes();
784        int count = 0;
785        for (int i = 0; i < childNodes.getLength(); i++)
786        {
787            final Node item = childNodes.item(i);
788            if (item instanceof Element)
789            {
790                if (name.equals(((Element) item).getTagName()))
791                {
792                    count++;
793                }
794            }
795        }
796        return count;
797    }
798
799    /**
800     * Checks whether the content of the current XML element should be trimmed.
801     * This method checks whether a {@code xml:space} attribute is
802     * present and evaluates its value. See <a
803     * href="http://www.w3.org/TR/REC-xml/#sec-white-space">
804     * http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
805     *
806     * @param element the current XML element
807     * @param currentTrim the current trim flag
808     * @return a flag whether the content of this element should be trimmed
809     */
810    private static boolean shouldTrim(final Element element, final boolean currentTrim)
811    {
812        final Attr attr = element.getAttributeNode(ATTR_SPACE);
813
814        if (attr == null)
815        {
816            return currentTrim;
817        }
818        return !VALUE_PRESERVE.equals(attr.getValue());
819    }
820
821    /**
822     * Creates the {@code DocumentBuilder} to be used for loading files.
823     * This implementation checks whether a specific
824     * {@code DocumentBuilder} has been set. If this is the case, this
825     * one is used. Otherwise a default builder is created. Depending on the
826     * value of the validating flag this builder will be a validating or a non
827     * validating {@code DocumentBuilder}.
828     *
829     * @return the {@code DocumentBuilder} for loading configuration
830     * files
831     * @throws ParserConfigurationException if an error occurs
832     * @since 1.2
833     */
834    protected DocumentBuilder createDocumentBuilder()
835            throws ParserConfigurationException
836    {
837        if (getDocumentBuilder() != null)
838        {
839            return getDocumentBuilder();
840        }
841        final DocumentBuilderFactory factory = DocumentBuilderFactory
842                .newInstance();
843        if (isValidating())
844        {
845            factory.setValidating(true);
846            if (isSchemaValidation())
847            {
848                factory.setNamespaceAware(true);
849                factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
850            }
851        }
852
853        final DocumentBuilder result = factory.newDocumentBuilder();
854        result.setEntityResolver(this.entityResolver);
855
856        if (isValidating())
857        {
858            // register an error handler which detects validation errors
859            result.setErrorHandler(new DefaultHandler()
860            {
861                @Override
862                public void error(final SAXParseException ex) throws SAXException
863                {
864                    throw ex;
865                }
866            });
867        }
868        return result;
869    }
870
871    /**
872     * Creates and initializes the transformer used for save operations. This
873     * base implementation initializes all of the default settings like
874     * indention mode and the DOCTYPE. Derived classes may overload this method
875     * if they have specific needs.
876     *
877     * @return the transformer to use for a save operation
878     * @throws ConfigurationException if an error occurs
879     * @since 1.3
880     */
881    protected Transformer createTransformer() throws ConfigurationException
882    {
883        final Transformer transformer = XMLDocumentHelper.createTransformer();
884
885        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
886        if (locator.getEncoding() != null)
887        {
888            transformer.setOutputProperty(OutputKeys.ENCODING, locator.getEncoding());
889        }
890        if (publicID != null)
891        {
892            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
893                    publicID);
894        }
895        if (systemID != null)
896        {
897            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
898                    systemID);
899        }
900
901        return transformer;
902    }
903
904    /**
905     * Creates a DOM document from the internal tree of configuration nodes.
906     *
907     * @return the new document
908     * @throws ConfigurationException if an error occurs
909     */
910    private Document createDocument() throws ConfigurationException
911    {
912        final ReferenceNodeHandler handler = getReferenceHandler();
913        final XMLDocumentHelper docHelper =
914                (XMLDocumentHelper) handler.getReference(handler.getRootNode());
915        final XMLDocumentHelper newHelper =
916                (docHelper == null) ? XMLDocumentHelper
917                        .forNewDocument(getRootElementName()) : docHelper
918                        .createCopy();
919
920        final XMLBuilderVisitor builder =
921                new XMLBuilderVisitor(newHelper, getListDelimiterHandler());
922        builder.handleRemovedNodes(handler);
923        builder.processDocument(handler);
924        initRootElementText(newHelper.getDocument(), getModel()
925                .getNodeHandler().getRootNode().getValue());
926        return newHelper.getDocument();
927    }
928
929    /**
930     * Sets the text of the root element of a newly created XML Document.
931     *
932     * @param doc the document
933     * @param value the new text to be set
934     */
935    private void initRootElementText(final Document doc, final Object value)
936    {
937        final Element elem = doc.getDocumentElement();
938        final NodeList children = elem.getChildNodes();
939
940        // Remove all existing text nodes
941        for (int i = 0; i < children.getLength(); i++)
942        {
943            final org.w3c.dom.Node nd = children.item(i);
944            if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
945            {
946                elem.removeChild(nd);
947            }
948        }
949
950        if (value != null)
951        {
952            // Add a new text node
953            elem.appendChild(doc.createTextNode(String.valueOf(value)));
954        }
955    }
956
957    /**
958     * {@inheritDoc} Stores the passed in locator for the upcoming IO operation.
959     */
960    @Override
961    public void initFileLocator(final FileLocator loc)
962    {
963        locator = loc;
964    }
965
966    /**
967     * Loads the configuration from the given reader.
968     * Note that the {@code clear()} method is not called, so
969     * the properties contained in the loaded file will be added to the
970     * current set of properties.
971     *
972     * @param in the reader
973     * @throws ConfigurationException if an error occurs
974     * @throws IOException if an IO error occurs
975     */
976    @Override
977    public void read(final Reader in) throws ConfigurationException, IOException
978    {
979        load(new InputSource(in));
980    }
981
982    /**
983     * Loads the configuration from the given input stream. This is analogous to
984     * {@link #read(Reader)}, but data is read from a stream. Note that this
985     * method will be called most time when reading an XML configuration source.
986     * By reading XML documents directly from an input stream, the file's
987     * encoding can be correctly dealt with.
988     *
989     * @param in the input stream
990     * @throws ConfigurationException if an error occurs
991     * @throws IOException if an IO error occurs
992     */
993    @Override
994    public void read(final InputStream in) throws ConfigurationException, IOException
995    {
996        load(new InputSource(in));
997    }
998
999    /**
1000     * Loads a configuration file from the specified input source.
1001     *
1002     * @param source the input source
1003     * @throws ConfigurationException if an error occurs
1004     */
1005    private void load(final InputSource source) throws ConfigurationException
1006    {
1007        if (locator == null)
1008        {
1009            throw new ConfigurationException("Load operation not properly "
1010                    + "initialized! Do not call read(InputStream) directly,"
1011                    + " but use a FileHandler to load a configuration.");
1012        }
1013
1014        try
1015        {
1016            final URL sourceURL = locator.getSourceURL();
1017            if (sourceURL != null)
1018            {
1019                source.setSystemId(sourceURL.toString());
1020            }
1021
1022            final DocumentBuilder builder = createDocumentBuilder();
1023            final Document newDocument = builder.parse(source);
1024            final Document oldDocument = getDocument();
1025            initProperties(XMLDocumentHelper.forSourceDocument(newDocument),
1026                    oldDocument == null);
1027        }
1028        catch (final SAXParseException spe)
1029        {
1030            throw new ConfigurationException("Error parsing " + source.getSystemId(), spe);
1031        }
1032        catch (final Exception e)
1033        {
1034            this.getLogger().debug("Unable to load the configuration: " + e);
1035            throw new ConfigurationException("Unable to load the configuration", e);
1036        }
1037    }
1038
1039    /**
1040     * Saves the configuration to the specified writer.
1041     *
1042     * @param writer the writer used to save the configuration
1043     * @throws ConfigurationException if an error occurs
1044     * @throws IOException if an IO error occurs
1045     */
1046    @Override
1047    public void write(final Writer writer) throws ConfigurationException, IOException
1048    {
1049        final Transformer transformer = createTransformer();
1050        final Source source = new DOMSource(createDocument());
1051        final Result result = new StreamResult(writer);
1052        XMLDocumentHelper.transform(transformer, source, result);
1053    }
1054
1055    /**
1056     * Validate the document against the Schema.
1057     * @throws ConfigurationException if the validation fails.
1058     */
1059    public void validate() throws ConfigurationException
1060    {
1061        beginWrite(false);
1062        try
1063        {
1064            final Transformer transformer = createTransformer();
1065            final Source source = new DOMSource(createDocument());
1066            final StringWriter writer = new StringWriter();
1067            final Result result = new StreamResult(writer);
1068            XMLDocumentHelper.transform(transformer, source, result);
1069            final Reader reader = new StringReader(writer.getBuffer().toString());
1070            final DocumentBuilder builder = createDocumentBuilder();
1071            builder.parse(new InputSource(reader));
1072        }
1073        catch (final SAXException e)
1074        {
1075            throw new ConfigurationException("Validation failed", e);
1076        }
1077        catch (final IOException e)
1078        {
1079            throw new ConfigurationException("Validation failed", e);
1080        }
1081        catch (final ParserConfigurationException pce)
1082        {
1083            throw new ConfigurationException("Validation failed", pce);
1084        }
1085        finally
1086        {
1087            endWrite();
1088        }
1089    }
1090
1091    /**
1092     * A concrete {@code BuilderVisitor} that can construct XML
1093     * documents.
1094     */
1095    static class XMLBuilderVisitor extends BuilderVisitor
1096    {
1097        /** Stores the document to be constructed. */
1098        private final Document document;
1099
1100        /** The element mapping. */
1101        private final Map<Node, Node> elementMapping;
1102
1103        /** A mapping for the references for new nodes. */
1104        private final Map<ImmutableNode, Element> newElements;
1105
1106        /** Stores the list delimiter handler .*/
1107        private final ListDelimiterHandler listDelimiterHandler;
1108
1109        /**
1110         * Creates a new instance of {@code XMLBuilderVisitor}.
1111         *
1112         * @param docHelper the document helper
1113         * @param handler the delimiter handler for properties with multiple
1114         *        values
1115         */
1116        public XMLBuilderVisitor(final XMLDocumentHelper docHelper,
1117                final ListDelimiterHandler handler)
1118        {
1119            document = docHelper.getDocument();
1120            elementMapping = docHelper.getElementMapping();
1121            listDelimiterHandler = handler;
1122            newElements = new HashMap<>();
1123        }
1124
1125        /**
1126         * Processes the specified document, updates element values, and adds
1127         * new nodes to the hierarchy.
1128         *
1129         * @param refHandler the {@code ReferenceNodeHandler}
1130         */
1131        public void processDocument(final ReferenceNodeHandler refHandler)
1132        {
1133            updateAttributes(refHandler.getRootNode(), document.getDocumentElement());
1134            NodeTreeWalker.INSTANCE.walkDFS(refHandler.getRootNode(), this,
1135                    refHandler);
1136        }
1137
1138        /**
1139         * Updates the current XML document regarding removed nodes. The
1140         * elements associated with removed nodes are removed from the document.
1141         *
1142         * @param refHandler the {@code ReferenceNodeHandler}
1143         */
1144        public void handleRemovedNodes(final ReferenceNodeHandler refHandler)
1145        {
1146            for (final Object ref : refHandler.removedReferences())
1147            {
1148                if (ref instanceof Node)
1149                {
1150                    final Node removedElem = (Node) ref;
1151                    removeReference((Element) elementMapping.get(removedElem));
1152                }
1153            }
1154        }
1155
1156        /**
1157         * {@inheritDoc} This implementation ensures that the correct XML
1158         * element is created and inserted between the given siblings.
1159         */
1160        @Override
1161        protected void insert(final ImmutableNode newNode, final ImmutableNode parent,
1162                final ImmutableNode sibling1, final ImmutableNode sibling2,
1163                final ReferenceNodeHandler refHandler)
1164        {
1165            if (XMLListReference.isListNode(newNode, refHandler))
1166            {
1167                return;
1168            }
1169
1170            final Element elem = document.createElement(newNode.getNodeName());
1171            newElements.put(newNode, elem);
1172            updateAttributes(newNode, elem);
1173            if (newNode.getValue() != null)
1174            {
1175                final String txt =
1176                        String.valueOf(listDelimiterHandler.escape(
1177                                newNode.getValue(),
1178                                ListDelimiterHandler.NOOP_TRANSFORMER));
1179                elem.appendChild(document.createTextNode(txt));
1180            }
1181            if (sibling2 == null)
1182            {
1183                getElement(parent, refHandler).appendChild(elem);
1184            }
1185            else if (sibling1 != null)
1186            {
1187                getElement(parent, refHandler).insertBefore(elem,
1188                        getElement(sibling1, refHandler).getNextSibling());
1189            }
1190            else
1191            {
1192                getElement(parent, refHandler).insertBefore(elem,
1193                        getElement(parent, refHandler).getFirstChild());
1194            }
1195        }
1196
1197        /**
1198         * {@inheritDoc} This implementation determines the XML element
1199         * associated with the given node. Then this element's value and
1200         * attributes are set accordingly.
1201         */
1202        @Override
1203        protected void update(final ImmutableNode node, final Object reference,
1204                final ReferenceNodeHandler refHandler)
1205        {
1206            if (XMLListReference.isListNode(node, refHandler))
1207            {
1208                if (XMLListReference.isFirstListItem(node, refHandler))
1209                {
1210                    final String value = XMLListReference.listValue(node, refHandler, listDelimiterHandler);
1211                    updateElement(node, refHandler, value);
1212                }
1213            }
1214            else
1215            {
1216                final Object value = listDelimiterHandler.escape(refHandler.getValue(node),
1217                        ListDelimiterHandler.NOOP_TRANSFORMER);
1218                updateElement(node, refHandler, value);
1219            }
1220        }
1221
1222        private void updateElement(final ImmutableNode node, final ReferenceNodeHandler refHandler,
1223                                   final Object value)
1224        {
1225            final Element element = getElement(node, refHandler);
1226            updateElement(element, value);
1227            updateAttributes(node, element);
1228        }
1229
1230        /**
1231         * Updates the node's value if it represents an element node.
1232         *
1233         * @param element the element
1234         * @param value the new value
1235         */
1236        private void updateElement(final Element element, final Object value)
1237        {
1238            Text txtNode = findTextNodeForUpdate(element);
1239            if (value == null)
1240            {
1241                // remove text
1242                if (txtNode != null)
1243                {
1244                    element.removeChild(txtNode);
1245                }
1246            }
1247            else
1248            {
1249                final String newValue = String.valueOf(value);
1250                if (txtNode == null)
1251                {
1252                    txtNode = document.createTextNode(newValue);
1253                    if (element.getFirstChild() != null)
1254                    {
1255                        element.insertBefore(txtNode, element.getFirstChild());
1256                    }
1257                    else
1258                    {
1259                        element.appendChild(txtNode);
1260                    }
1261                }
1262                else
1263                {
1264                    txtNode.setNodeValue(newValue);
1265                }
1266            }
1267        }
1268
1269        /**
1270         * Updates the associated XML elements when a node is removed.
1271         * @param element the element to be removed
1272         */
1273        private void removeReference(final Element element)
1274        {
1275            final org.w3c.dom.Node parentElem = element.getParentNode();
1276            if (parentElem != null)
1277            {
1278                parentElem.removeChild(element);
1279            }
1280        }
1281
1282        /**
1283         * Helper method for accessing the element of the specified node.
1284         *
1285         * @param node the node
1286         * @param refHandler the {@code ReferenceNodeHandler}
1287         * @return the element of this node
1288         */
1289        private Element getElement(final ImmutableNode node,
1290                final ReferenceNodeHandler refHandler)
1291        {
1292            final Element elementNew = newElements.get(node);
1293            if (elementNew != null)
1294            {
1295                return elementNew;
1296            }
1297
1298            // special treatment for root node of the hierarchy
1299            final Object reference = refHandler.getReference(node);
1300            Node element;
1301            if (reference instanceof XMLDocumentHelper)
1302            {
1303                element =
1304                        ((XMLDocumentHelper) reference).getDocument()
1305                                .getDocumentElement();
1306            }
1307            else if (reference instanceof XMLListReference)
1308            {
1309                element = ((XMLListReference) reference).getElement();
1310            }
1311            else
1312            {
1313                element = (Node) reference;
1314            }
1315            return (element != null) ? (Element) elementMapping.get(element)
1316                    : document.getDocumentElement();
1317        }
1318
1319        /**
1320         * Helper method for updating the values of all attributes of the
1321         * specified node.
1322         *
1323         * @param node the affected node
1324         * @param elem the element that is associated with this node
1325         */
1326        private static void updateAttributes(final ImmutableNode node, final Element elem)
1327        {
1328            if (node != null && elem != null)
1329            {
1330                clearAttributes(elem);
1331                for (final Map.Entry<String, Object> e : node.getAttributes()
1332                        .entrySet())
1333                {
1334                    if (e.getValue() != null)
1335                    {
1336                        elem.setAttribute(e.getKey(), e.getValue().toString());
1337                    }
1338                }
1339            }
1340        }
1341
1342        /**
1343         * Removes all attributes of the given element.
1344         *
1345         * @param elem the element
1346         */
1347        private static void clearAttributes(final Element elem)
1348        {
1349            final NamedNodeMap attributes = elem.getAttributes();
1350            for (int i = 0; i < attributes.getLength(); i++)
1351            {
1352                elem.removeAttribute(attributes.item(i).getNodeName());
1353            }
1354        }
1355
1356        /**
1357         * Returns the only text node of an element for update. This method is
1358         * called when the element's text changes. Then all text nodes except
1359         * for the first are removed. A reference to the first is returned or
1360         * <b>null</b> if there is no text node at all.
1361         *
1362         * @param elem the element
1363         * @return the first and only text node
1364         */
1365        private static Text findTextNodeForUpdate(final Element elem)
1366        {
1367            Text result = null;
1368            // Find all Text nodes
1369            final NodeList children = elem.getChildNodes();
1370            final Collection<org.w3c.dom.Node> textNodes =
1371                    new ArrayList<>();
1372            for (int i = 0; i < children.getLength(); i++)
1373            {
1374                final org.w3c.dom.Node nd = children.item(i);
1375                if (nd instanceof Text)
1376                {
1377                    if (result == null)
1378                    {
1379                        result = (Text) nd;
1380                    }
1381                    else
1382                    {
1383                        textNodes.add(nd);
1384                    }
1385                }
1386            }
1387
1388            // We don't want CDATAs
1389            if (result instanceof CDATASection)
1390            {
1391                textNodes.add(result);
1392                result = null;
1393            }
1394
1395            // Remove all but the first Text node
1396            for (final org.w3c.dom.Node tn : textNodes)
1397            {
1398                elem.removeChild(tn);
1399            }
1400            return result;
1401        }
1402    }
1403}