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