2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.commons.configuration;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.io.Reader;
24
import java.io.StringReader;
25
import java.io.StringWriter;
26
import java.io.Writer;
28
import java.util.ArrayList;
29
import java.util.Collection;
30
import java.util.Collections;
31
import java.util.HashMap;
32
import java.util.Iterator;
33
import java.util.List;
36
import javax.xml.parsers.DocumentBuilder;
37
import javax.xml.parsers.DocumentBuilderFactory;
38
import javax.xml.parsers.ParserConfigurationException;
39
import javax.xml.transform.OutputKeys;
40
import javax.xml.transform.Result;
41
import javax.xml.transform.Source;
42
import javax.xml.transform.Transformer;
43
import javax.xml.transform.TransformerException;
44
import javax.xml.transform.TransformerFactory;
45
import javax.xml.transform.TransformerFactoryConfigurationError;
46
import javax.xml.transform.dom.DOMSource;
47
import javax.xml.transform.stream.StreamResult;
49
import org.apache.commons.configuration.resolver.DefaultEntityResolver;
50
import org.apache.commons.configuration.resolver.EntityRegistry;
51
import org.apache.commons.configuration.tree.ConfigurationNode;
52
import org.apache.commons.logging.LogFactory;
53
import org.w3c.dom.Attr;
54
import org.w3c.dom.CDATASection;
55
import org.w3c.dom.DOMException;
56
import org.w3c.dom.Document;
57
import org.w3c.dom.Element;
58
import org.w3c.dom.NamedNodeMap;
59
import org.w3c.dom.NodeList;
60
import org.w3c.dom.Text;
61
import org.xml.sax.EntityResolver;
62
import org.xml.sax.InputSource;
63
import org.xml.sax.SAXException;
64
import org.xml.sax.SAXParseException;
65
import org.xml.sax.helpers.DefaultHandler;
68
* <p>A specialized hierarchical configuration class that is able to parse XML
71
* <p>The parsed document will be stored keeping its structure. The class also
72
* tries to preserve as much information from the loaded XML document as
73
* possible, including comments and processing instructions. These will be
74
* contained in documents created by the {@code save()} methods, too.</p>
76
* <p>Like other file based configuration classes this class maintains the name
77
* and path to the loaded configuration file. These properties can be altered
78
* using several setter methods, but they are not modified by {@code save()}
79
* and {@code load()} methods. If XML documents contain relative paths to
80
* other documents (e.g. to a DTD), these references are resolved based on the
81
* path set for this configuration.</p>
83
* <p>By inheriting from {@link AbstractConfiguration} this class
84
* provides some extended functionality, e.g. interpolation of property values.
85
* Like in {@link PropertiesConfiguration} property values can
86
* contain delimiter characters (the comma ',' per default) and are then split
87
* into multiple values. This works for XML attributes and text content of
88
* elements as well. The delimiter can be escaped by a backslash. As an example
89
* consider the following XML fragment:</p>
94
* <array>10,20,30,40</array>
95
* <scalar>3\,1415</scalar>
96
* <cite text="To be or not to be\, this is the question!"/>
100
* <p>Here the content of the {@code array} element will be split at
101
* the commas, so the {@code array} key will be assigned 4 values. In the
102
* {@code scalar} property and the {@code text} attribute of the
103
* {@code cite} element the comma is escaped, so that no splitting is
106
* <p>The configuration API allows setting multiple values for a single attribute,
107
* e.g. something like the following is legal (assuming that the default
108
* expression engine is used):
110
* XMLConfiguration config = new XMLConfiguration();
111
* config.addProperty("test.dir[@name]", "C:\\Temp\\");
112
* config.addProperty("test.dir[@name]", "D:\\Data\\");
115
* <p>Because in XML such a constellation is not directly supported (an attribute
116
* can appear only once for a single element), the values are concatenated to a
117
* single value. If delimiter parsing is enabled (refer to the
118
* {@link #setDelimiterParsingDisabled(boolean)} method), the
119
* current list delimiter character will be used as separator. Otherwise the
120
* pipe symbol ("|") will be used for this purpose. No matter which character is
121
* used as delimiter, it can always be escaped with a backslash. A backslash
122
* itself can also be escaped with another backslash. Consider the following
123
* example fragment from a configuration file:
125
* <directories names="C:\Temp\\|D:\Data\"/>
127
* Here the backslash after Temp is escaped. This is necessary because it
128
* would escape the list delimiter (the pipe symbol assuming that list delimiter
129
* parsing is disabled) otherwise. So this attribute would have two values.</p>
131
* <p>Note: You should ensure that the <em>delimiter parsing disabled</em>
132
* property is always consistent when you load and save a configuration file.
133
* Otherwise the values of properties can become corrupted.</p>
135
* <p>Whitespace in the content of XML documents is trimmed per default. In most
136
* cases this is desired. However, sometimes whitespace is indeed important and
137
* should be treated as part of the value of a property as in the following
140
* <indent> </indent>
143
* <p>Per default the spaces in the {@code indent} element will be trimmed
144
* resulting in an empty element. To tell {@code XMLConfiguration} that
145
* spaces are relevant the {@code xml:space} attribute can be used, which is
146
* defined in the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
147
* specification</a>. This will look as follows:
149
* <indent <strong>xml:space="preserve"</strong>> </indent>
151
* The value of the {@code indent} property will now contain the spaces.</p>
153
* <p>{@code XMLConfiguration} implements the {@link FileConfiguration}
154
* interface and thus provides full support for loading XML documents from
155
* different sources like files, URLs, or streams. A full description of these
156
* features can be found in the documentation of
157
* {@link AbstractFileConfiguration}.</p>
159
* <p><em>Note:</em>Configuration objects of this type can be read concurrently
160
* by multiple threads. However if one of these threads modifies the object,
161
* synchronization has to be performed manually.</p>
163
* @since commons-configuration 1.0
165
* @author Jörg Schaible
166
* @version $Id: XMLConfiguration.java 1301998 2012-03-17 20:34:13Z sebb $
168
public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
169
implements EntityResolver, EntityRegistry
172
* The serial version UID.
174
private static final long serialVersionUID = 2453781111653383552L;
176
/** Constant for the default root element name. */
177
private static final String DEFAULT_ROOT_NAME = "configuration";
179
/** Constant for the name of the space attribute.*/
180
private static final String ATTR_SPACE = "xml:space";
182
/** Constant for the xml:space value for preserving whitespace.*/
183
private static final String VALUE_PRESERVE = "preserve";
185
/** Constant for the delimiter for multiple attribute values.*/
186
private static final char ATTR_VALUE_DELIMITER = '|';
188
/** Schema Langauge key for the parser */
189
private static final String JAXP_SCHEMA_LANGUAGE =
190
"http://java.sun.com/xml/jaxp/properties/schemaLanguage";
192
/** Schema Language for the parser */
193
private static final String W3C_XML_SCHEMA =
194
"http://www.w3.org/2001/XMLSchema";
196
/** The document from this configuration's data source. */
197
private Document document;
199
/** Stores the name of the root element. */
200
private String rootElementName;
202
/** Stores the public ID from the DOCTYPE.*/
203
private String publicID;
205
/** Stores the system ID from the DOCTYPE.*/
206
private String systemID;
208
/** Stores the document builder that should be used for loading.*/
209
private DocumentBuilder documentBuilder;
211
/** Stores a flag whether DTD or Schema validation should be performed.*/
212
private boolean validating;
214
/** Stores a flag whether DTD or Schema validation is used */
215
private boolean schemaValidation;
217
/** A flag whether attribute splitting is disabled.*/
218
private boolean attributeSplittingDisabled;
220
/** The EntityResolver to use */
221
private EntityResolver entityResolver = new DefaultEntityResolver();
224
* Creates a new instance of {@code XMLConfiguration}.
226
public XMLConfiguration()
229
setLogger(LogFactory.getLog(XMLConfiguration.class));
233
* Creates a new instance of {@code XMLConfiguration} and copies the
234
* content of the passed in configuration into this object. Note that only
235
* the data of the passed in configuration will be copied. If, for instance,
236
* the other configuration is a {@code XMLConfiguration}, too,
237
* things like comments or processing instructions will be lost.
239
* @param c the configuration to copy
242
public XMLConfiguration(HierarchicalConfiguration c)
245
clearReferences(getRootNode());
246
setRootElementName(getRootNode().getName());
247
setLogger(LogFactory.getLog(XMLConfiguration.class));
251
* Creates a new instance of{@code XMLConfiguration}. The
252
* configuration is loaded from the specified file
254
* @param fileName the name of the file to load
255
* @throws ConfigurationException if the file cannot be loaded
257
public XMLConfiguration(String fileName) throws ConfigurationException
260
setLogger(LogFactory.getLog(XMLConfiguration.class));
264
* Creates a new instance of {@code XMLConfiguration}.
265
* The configuration is loaded from the specified file.
267
* @param file the file
268
* @throws ConfigurationException if an error occurs while loading the file
270
public XMLConfiguration(File file) throws ConfigurationException
273
setLogger(LogFactory.getLog(XMLConfiguration.class));
277
* Creates a new instance of {@code XMLConfiguration}.
278
* The configuration is loaded from the specified URL.
281
* @throws ConfigurationException if loading causes an error
283
public XMLConfiguration(URL url) throws ConfigurationException
286
setLogger(LogFactory.getLog(XMLConfiguration.class));
290
* Returns the name of the root element. If this configuration was loaded
291
* from a XML document, the name of this document's root element is
292
* returned. Otherwise it is possible to set a name for the root element
293
* that will be used when this configuration is stored.
295
* @return the name of the root element
297
public String getRootElementName()
299
if (getDocument() == null)
301
return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
305
return getDocument().getDocumentElement().getNodeName();
310
* Sets the name of the root element. This name is used when this
311
* configuration object is stored in an XML file. Note that setting the name
312
* of the root element works only if this configuration has been newly
313
* created. If the configuration was loaded from an XML file, the name
314
* cannot be changed and an {@code UnsupportedOperationException}
315
* exception is thrown. Whether this configuration has been loaded from an
316
* XML document or not can be found out using the {@code getDocument()}
319
* @param name the name of the root element
321
public void setRootElementName(String name)
323
if (getDocument() != null)
325
throw new UnsupportedOperationException("The name of the root element "
326
+ "cannot be changed when loaded from an XML document!");
328
rootElementName = name;
329
getRootNode().setName(name);
333
* Returns the {@code DocumentBuilder} object that is used for
334
* loading documents. If no specific builder has been set, this method
335
* returns <b>null</b>.
337
* @return the {@code DocumentBuilder} for loading new documents
340
public DocumentBuilder getDocumentBuilder()
342
return documentBuilder;
346
* Sets the {@code DocumentBuilder} object to be used for loading
347
* documents. This method makes it possible to specify the exact document
348
* builder. So an application can create a builder, configure it for its
349
* special needs, and then pass it to this method.
351
* @param documentBuilder the document builder to be used; if undefined, a
352
* default builder will be used
355
public void setDocumentBuilder(DocumentBuilder documentBuilder)
357
this.documentBuilder = documentBuilder;
361
* Returns the public ID of the DOCTYPE declaration from the loaded XML
362
* document. This is <b>null</b> if no document has been loaded yet or if
363
* the document does not contain a DOCTYPE declaration with a public ID.
365
* @return the public ID
368
public String getPublicID()
374
* Sets the public ID of the DOCTYPE declaration. When this configuration is
375
* saved, a DOCTYPE declaration will be constructed that contains this
378
* @param publicID the public ID
381
public void setPublicID(String publicID)
383
this.publicID = publicID;
387
* Returns the system ID of the DOCTYPE declaration from the loaded XML
388
* document. This is <b>null</b> if no document has been loaded yet or if
389
* the document does not contain a DOCTYPE declaration with a system ID.
391
* @return the system ID
394
public String getSystemID()
400
* Sets the system ID of the DOCTYPE declaration. When this configuration is
401
* saved, a DOCTYPE declaration will be constructed that contains this
404
* @param systemID the system ID
407
public void setSystemID(String systemID)
409
this.systemID = systemID;
413
* Returns the value of the validating flag.
415
* @return the validating flag
418
public boolean isValidating()
424
* Sets the value of the validating flag. This flag determines whether
425
* DTD/Schema validation should be performed when loading XML documents. This
426
* flag is evaluated only if no custom {@code DocumentBuilder} was set.
428
* @param validating the validating flag
431
public void setValidating(boolean validating)
433
if (!schemaValidation)
435
this.validating = validating;
441
* Returns the value of the schemaValidation flag.
443
* @return the schemaValidation flag
446
public boolean isSchemaValidation()
448
return schemaValidation;
452
* Sets the value of the schemaValidation flag. This flag determines whether
453
* DTD or Schema validation should be used. This
454
* flag is evaluated only if no custom {@code DocumentBuilder} was set.
455
* If set to true the XML document must contain a schemaLocation definition
456
* that provides resolvable hints to the required schemas.
458
* @param schemaValidation the validating flag
461
public void setSchemaValidation(boolean schemaValidation)
463
this.schemaValidation = schemaValidation;
464
if (schemaValidation)
466
this.validating = true;
471
* Sets a new EntityResolver. Setting this will cause RegisterEntityId to have no
473
* @param resolver The EntityResolver to use.
476
public void setEntityResolver(EntityResolver resolver)
478
this.entityResolver = resolver;
482
* Returns the EntityResolver.
483
* @return The EntityResolver.
486
public EntityResolver getEntityResolver()
488
return this.entityResolver;
492
* Returns the flag whether attribute splitting is disabled.
494
* @return the flag whether attribute splitting is disabled
495
* @see #setAttributeSplittingDisabled(boolean)
498
public boolean isAttributeSplittingDisabled()
500
return attributeSplittingDisabled;
505
* Sets a flag whether attribute splitting is disabled.
508
* The Configuration API allows adding multiple values to an attribute. This
509
* is problematic when storing the configuration because in XML an attribute
510
* can appear only once with a single value. To solve this problem, per
511
* default multiple attribute values are concatenated using a special
512
* separator character and split again when the configuration is loaded. The
513
* separator character is either the list delimiter character (see
514
* {@link #setListDelimiter(char)}) or the pipe symbol ("|") if
515
* list delimiter parsing is disabled.
518
* In some constellations the splitting of attribute values can have
519
* undesired effects, especially if list delimiter parsing is disabled and
520
* attributes may contain the "|" character. In these cases it is
521
* possible to disable the attribute splitting mechanism by calling this
522
* method with a boolean value set to <b>false</b>. If attribute splitting
523
* is disabled, the values of attributes will not be processed, but stored
524
* as configuration properties exactly as they are returned by the XML
528
* Note that in this mode multiple attribute values cannot be handled
529
* correctly. It is possible to create a {@code XMLConfiguration}
530
* object, add multiple values to an attribute and save it. When the
531
* configuration is loaded again and attribute splitting is disabled, the
532
* attribute will only have a single value, which is the concatenation of
533
* all values set before. So it lies in the responsibility of the
534
* application to carefully set the values of attributes.
537
* As is true for the {@link #setDelimiterParsingDisabled(boolean)} method,
538
* this method must be called before the configuration is loaded. So it
539
* can't be used together with one of the constructors expecting the
540
* specification of the file to load. Instead the default constructor has to
541
* be used, then {@code setAttributeSplittingDisabled(false)} has to be
542
* called, and finally the configuration can be loaded using one of its
543
* {@code load()} methods.
546
* @param attributeSplittingDisabled <b>true</b> for disabling attribute
547
* splitting, <b>false</b> for enabling it
548
* @see #setDelimiterParsingDisabled(boolean)
551
public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
553
this.attributeSplittingDisabled = attributeSplittingDisabled;
557
* Returns the XML document this configuration was loaded from. The return
558
* value is <b>null</b> if this configuration was not loaded from a XML
561
* @return the XML document this configuration was loaded from
563
public Document getDocument()
569
* Removes all properties from this configuration. If this configuration
570
* was loaded from a file, the associated DOM document is also cleared.
581
* Initializes this configuration from an XML document.
583
* @param document the document to be parsed
584
* @param elemRefs a flag whether references to the XML elements should be set
586
public void initProperties(Document document, boolean elemRefs)
588
if (document.getDoctype() != null)
590
setPublicID(document.getDoctype().getPublicId());
591
setSystemID(document.getDoctype().getSystemId());
594
constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs, true);
595
getRootNode().setName(document.getDocumentElement().getNodeName());
598
getRoot().setReference(document.getDocumentElement());
603
* Helper method for building the internal storage hierarchy. The XML
604
* elements are transformed into node objects.
606
* @param node the actual node
607
* @param element the actual XML element
608
* @param elemRefs a flag whether references to the XML elements should be set
609
* @param trim a flag whether the text content of elements should be trimmed;
610
* this controls the whitespace handling
612
private void constructHierarchy(Node node, Element element, boolean elemRefs, boolean trim)
614
boolean trimFlag = shouldTrim(element, trim);
615
processAttributes(node, element, elemRefs);
616
StringBuilder buffer = new StringBuilder();
617
NodeList list = element.getChildNodes();
618
for (int i = 0; i < list.getLength(); i++)
620
org.w3c.dom.Node w3cNode = list.item(i);
621
if (w3cNode instanceof Element)
623
Element child = (Element) w3cNode;
624
Node childNode = new XMLNode(child.getTagName(),
625
elemRefs ? child : null);
626
constructHierarchy(childNode, child, elemRefs, trimFlag);
627
node.addChild(childNode);
628
handleDelimiters(node, childNode, trimFlag);
630
else if (w3cNode instanceof Text)
632
Text data = (Text) w3cNode;
633
buffer.append(data.getData());
637
String text = buffer.toString();
642
if (text.length() > 0 || (!node.hasChildren() && node != getRoot()))
649
* Helper method for constructing node objects for the attributes of the
652
* @param node the actual node
653
* @param element the actual XML element
654
* @param elemRefs a flag whether references to the XML elements should be set
656
private void processAttributes(Node node, Element element, boolean elemRefs)
658
NamedNodeMap attributes = element.getAttributes();
659
for (int i = 0; i < attributes.getLength(); ++i)
661
org.w3c.dom.Node w3cNode = attributes.item(i);
662
if (w3cNode instanceof Attr)
664
Attr attr = (Attr) w3cNode;
666
if (isAttributeSplittingDisabled())
668
values = Collections.singletonList(attr.getValue());
672
values = PropertyConverter.split(attr.getValue(),
673
isDelimiterParsingDisabled() ? ATTR_VALUE_DELIMITER
674
: getListDelimiter());
677
for (String value : values)
679
Node child = new XMLNode(attr.getName(), elemRefs ? element
681
child.setValue(value);
682
node.addAttribute(child);
689
* Deals with elements whose value is a list. In this case multiple child
690
* elements must be added.
692
* @param parent the parent element
693
* @param child the child element
694
* @param trim flag whether texts of elements should be trimmed
696
private void handleDelimiters(Node parent, Node child, boolean trim)
698
if (child.getValue() != null)
701
if (isDelimiterParsingDisabled())
703
values = new ArrayList<String>();
704
values.add(child.getValue().toString());
708
values = PropertyConverter.split(child.getValue().toString(),
709
getListDelimiter(), trim);
712
if (values.size() > 1)
714
Iterator<String> it = values.iterator();
715
// Create new node for the original child's first value
716
Node c = createNode(child.getName());
717
c.setValue(it.next());
718
// Copy original attributes to the new node
719
for (ConfigurationNode ndAttr : child.getAttributes())
721
ndAttr.setReference(null);
722
c.addAttribute(ndAttr);
724
parent.remove(child);
727
// add multiple new children
730
c = new XMLNode(child.getName(), null);
731
c.setValue(it.next());
735
else if (values.size() == 1)
737
// we will have to replace the value because it might
738
// contain escaped delimiters
739
child.setValue(values.get(0));
745
* Checks whether the content of the current XML element should be trimmed.
746
* This method checks whether a {@code xml:space} attribute is
747
* present and evaluates its value. See <a
748
* href="http://www.w3.org/TR/REC-xml/#sec-white-space">
749
* http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
751
* @param element the current XML element
752
* @param currentTrim the current trim flag
753
* @return a flag whether the content of this element should be trimmed
755
private boolean shouldTrim(Element element, boolean currentTrim)
757
Attr attr = element.getAttributeNode(ATTR_SPACE);
765
return !VALUE_PRESERVE.equals(attr.getValue());
770
* Creates the {@code DocumentBuilder} to be used for loading files.
771
* This implementation checks whether a specific
772
* {@code DocumentBuilder} has been set. If this is the case, this
773
* one is used. Otherwise a default builder is created. Depending on the
774
* value of the validating flag this builder will be a validating or a non
775
* validating {@code DocumentBuilder}.
777
* @return the {@code DocumentBuilder} for loading configuration
779
* @throws ParserConfigurationException if an error occurs
782
protected DocumentBuilder createDocumentBuilder()
783
throws ParserConfigurationException
785
if (getDocumentBuilder() != null)
787
return getDocumentBuilder();
791
DocumentBuilderFactory factory = DocumentBuilderFactory
795
factory.setValidating(true);
796
if (isSchemaValidation())
798
factory.setNamespaceAware(true);
799
factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
803
DocumentBuilder result = factory.newDocumentBuilder();
804
result.setEntityResolver(this.entityResolver);
808
// register an error handler which detects validation errors
809
result.setErrorHandler(new DefaultHandler()
812
public void error(SAXParseException ex) throws SAXException
823
* Creates a DOM document from the internal tree of configuration nodes.
825
* @return the new document
826
* @throws ConfigurationException if an error occurs
828
protected Document createDocument() throws ConfigurationException
832
if (document == null)
834
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
835
Document newDocument = builder.newDocument();
836
Element rootElem = newDocument.createElement(getRootElementName());
837
newDocument.appendChild(rootElem);
838
document = newDocument;
841
XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
842
isDelimiterParsingDisabled() ? (char) 0 : getListDelimiter(),
843
isAttributeSplittingDisabled());
844
builder.processDocument(getRoot());
845
initRootElementText(document, getRootNode().getValue());
848
catch (DOMException domEx)
850
throw new ConfigurationException(domEx);
852
catch (ParserConfigurationException pex)
854
throw new ConfigurationException(pex);
859
* Sets the text of the root element of a newly created XML Document.
861
* @param doc the document
862
* @param value the new text to be set
864
private void initRootElementText(Document doc, Object value)
866
Element elem = doc.getDocumentElement();
867
NodeList children = elem.getChildNodes();
869
// Remove all existing text nodes
870
for (int i = 0; i < children.getLength(); i++)
872
org.w3c.dom.Node nd = children.item(i);
873
if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
875
elem.removeChild(nd);
881
// Add a new text node
882
elem.appendChild(doc.createTextNode(String.valueOf(value)));
887
* Creates a new node object. This implementation returns an instance of the
888
* {@code XMLNode} class.
890
* @param name the node's name
891
* @return the new node
894
protected Node createNode(String name)
896
return new XMLNode(name, null);
900
* Loads the configuration from the given input stream.
902
* @param in the input stream
903
* @throws ConfigurationException if an error occurs
906
public void load(InputStream in) throws ConfigurationException
908
load(new InputSource(in));
912
* Load the configuration from the given reader.
913
* Note that the {@code clear()} method is not called, so
914
* the properties contained in the loaded file will be added to the
915
* actual set of properties.
917
* @param in An InputStream.
919
* @throws ConfigurationException if an error occurs
921
public void load(Reader in) throws ConfigurationException
923
load(new InputSource(in));
927
* Loads a configuration file from the specified input source.
928
* @param source the input source
929
* @throws ConfigurationException if an error occurs
931
private void load(InputSource source) throws ConfigurationException
935
URL sourceURL = getDelegate().getURL();
936
if (sourceURL != null)
938
source.setSystemId(sourceURL.toString());
941
DocumentBuilder builder = createDocumentBuilder();
942
Document newDocument = builder.parse(source);
943
Document oldDocument = document;
945
initProperties(newDocument, oldDocument == null);
946
document = (oldDocument == null) ? newDocument : oldDocument;
948
catch (SAXParseException spe)
950
throw new ConfigurationException("Error parsing " + source.getSystemId(), spe);
954
this.getLogger().debug("Unable to load the configuraton", e);
955
throw new ConfigurationException("Unable to load the configuration", e);
960
* Saves the configuration to the specified writer.
962
* @param writer the writer used to save the configuration
963
* @throws ConfigurationException if an error occurs
965
public void save(Writer writer) throws ConfigurationException
969
Transformer transformer = createTransformer();
970
Source source = new DOMSource(createDocument());
971
Result result = new StreamResult(writer);
972
transformer.transform(source, result);
974
catch (TransformerException e)
976
throw new ConfigurationException("Unable to save the configuration", e);
978
catch (TransformerFactoryConfigurationError e)
980
throw new ConfigurationException("Unable to save the configuration", e);
985
* Validate the document against the Schema.
986
* @throws ConfigurationException if the validation fails.
988
public void validate() throws ConfigurationException
992
Transformer transformer = createTransformer();
993
Source source = new DOMSource(createDocument());
994
StringWriter writer = new StringWriter();
995
Result result = new StreamResult(writer);
996
transformer.transform(source, result);
997
Reader reader = new StringReader(writer.getBuffer().toString());
998
DocumentBuilder builder = createDocumentBuilder();
999
builder.parse(new InputSource(reader));
1001
catch (SAXException e)
1003
throw new ConfigurationException("Validation failed", e);
1005
catch (IOException e)
1007
throw new ConfigurationException("Validation failed", e);
1009
catch (TransformerException e)
1011
throw new ConfigurationException("Validation failed", e);
1013
catch (ParserConfigurationException pce)
1015
throw new ConfigurationException("Validation failed", pce);
1020
* Creates and initializes the transformer used for save operations. This
1021
* base implementation initializes all of the default settings like
1022
* indention mode and the DOCTYPE. Derived classes may overload this method
1023
* if they have specific needs.
1025
* @return the transformer to use for a save operation
1026
* @throws TransformerException if an error occurs
1029
protected Transformer createTransformer() throws TransformerException
1031
Transformer transformer = TransformerFactory.newInstance()
1034
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1035
if (getEncoding() != null)
1037
transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
1039
if (getPublicID() != null)
1041
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
1044
if (getSystemID() != null)
1046
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
1054
* Creates a copy of this object. The new configuration object will contain
1055
* the same properties as the original, but it will lose any connection to a
1056
* source document (if one exists). This is to avoid race conditions if both
1057
* the original and the copy are modified and then saved.
1062
public Object clone()
1064
XMLConfiguration copy = (XMLConfiguration) super.clone();
1066
// clear document related properties
1067
copy.document = null;
1068
copy.setDelegate(copy.createDelegate());
1069
// clear all references in the nodes, too
1070
clearReferences(copy.getRootNode());
1076
* Creates the file configuration delegate for this object. This implementation
1077
* will return an instance of a class derived from {@code FileConfigurationDelegate}
1078
* that deals with some specialties of {@code XMLConfiguration}.
1079
* @return the delegate for this object
1082
protected FileConfigurationDelegate createDelegate()
1084
return new XMLFileConfigurationDelegate();
1088
* Adds a collection of nodes directly to this configuration. This
1089
* implementation ensures that the nodes to be added are of the correct node
1090
* type (they have to be converted to {@code XMLNode} if necessary).
1092
* @param key the key where the nodes are to be added
1093
* @param nodes the collection with the new nodes
1097
public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
1099
if (nodes != null && !nodes.isEmpty())
1101
Collection<XMLNode> xmlNodes;
1102
xmlNodes = new ArrayList<XMLNode>(nodes.size());
1103
for (ConfigurationNode node : nodes)
1105
xmlNodes.add(convertToXMLNode(node));
1107
super.addNodes(key, xmlNodes);
1111
super.addNodes(key, nodes);
1116
* Converts the specified node into a {@code XMLNode} if necessary.
1117
* This is required for nodes that are directly added, e.g. by
1118
* {@code addNodes()}. If the passed in node is already an instance
1119
* of {@code XMLNode}, it is directly returned, and conversion
1120
* stops. Otherwise a new {@code XMLNode} is created, and the
1121
* children are also converted.
1123
* @param node the node to be converted
1124
* @return the converted node
1126
private XMLNode convertToXMLNode(ConfigurationNode node)
1128
if (node instanceof XMLNode)
1130
return (XMLNode) node;
1133
XMLNode nd = (XMLNode) createNode(node.getName());
1134
nd.setValue(node.getValue());
1135
nd.setAttribute(node.isAttribute());
1136
for (ConfigurationNode child : node.getChildren())
1138
nd.addChild(convertToXMLNode(child));
1140
for (ConfigurationNode attr : node.getAttributes())
1142
nd.addAttribute(convertToXMLNode(attr));
1149
* Registers the specified DTD URL for the specified public identifier.
1152
* {@code XMLConfiguration} contains an internal
1153
* {@code EntityResolver} implementation. This maps
1154
* {@code PUBLICID}'s to URLs (from which the resource will be
1155
* loaded). A common use case for this method is to register local URLs
1156
* (possibly computed at runtime by a class loader) for DTDs. This allows
1157
* the performance advantage of using a local version without having to
1158
* ensure every {@code SYSTEM} URI on every processed XML document is
1159
* local. This implementation provides only basic functionality. If more
1160
* sophisticated features are required, using
1161
* {@link #setDocumentBuilder(DocumentBuilder)} to set a custom
1162
* {@code DocumentBuilder} (which also can be initialized with a
1163
* custom {@code EntityResolver}) is recommended.
1166
* <strong>Note:</strong> This method will have no effect when a custom
1167
* {@code DocumentBuilder} has been set. (Setting a custom
1168
* {@code DocumentBuilder} overrides the internal implementation.)
1171
* <strong>Note:</strong> This method must be called before the
1172
* configuration is loaded. So the default constructor of
1173
* {@code XMLConfiguration} should be used, the location of the
1174
* configuration file set, {@code registerEntityId()} called, and
1175
* finally the {@code load()} method can be invoked.
1178
* @param publicId Public identifier of the DTD to be resolved
1179
* @param entityURL The URL to use for reading this DTD
1180
* @throws IllegalArgumentException if the public ID is undefined
1183
public void registerEntityId(String publicId, URL entityURL)
1185
if (entityResolver instanceof EntityRegistry)
1187
((EntityRegistry) entityResolver).registerEntityId(publicId, entityURL);
1192
* Resolves the requested external entity. This is the default
1193
* implementation of the {@code EntityResolver} interface. It checks
1194
* the passed in public ID against the registered entity IDs and uses a
1195
* local URL if possible.
1197
* @param publicId the public identifier of the entity being referenced
1198
* @param systemId the system identifier of the entity being referenced
1199
* @return an input source for the specified entity
1200
* @throws SAXException if a parsing exception occurs
1202
* @deprecated Use getEntityResolver().resolveEntity()
1205
public InputSource resolveEntity(String publicId, String systemId)
1210
return entityResolver.resolveEntity(publicId, systemId);
1212
catch (IOException e)
1214
throw new SAXException(e);
1219
* Returns a map with the entity IDs that have been registered using the
1220
* {@code registerEntityId()} method.
1222
* @return a map with the registered entity IDs
1224
public Map<String, URL> getRegisteredEntities()
1226
if (entityResolver instanceof EntityRegistry)
1228
return ((EntityRegistry) entityResolver).getRegisteredEntities();
1230
return new HashMap<String, URL>();
1234
* A specialized {@code Node} class that is connected with an XML
1235
* element. Changes on a node are also performed on the associated element.
1237
class XMLNode extends Node
1240
* The serial version UID.
1242
private static final long serialVersionUID = -4133988932174596562L;
1245
* Creates a new instance of {@code XMLNode} and initializes it
1246
* with a name and the corresponding XML element.
1248
* @param name the node's name
1249
* @param elem the XML element
1251
public XMLNode(String name, Element elem)
1258
* Sets the value of this node. If this node is associated with an XML
1259
* element, this element will be updated, too.
1261
* @param value the node's new value
1264
public void setValue(Object value)
1266
super.setValue(value);
1268
if (getReference() != null && document != null)
1276
updateElement(value);
1282
* Updates the associated XML elements when a node is removed.
1285
protected void removeReference()
1287
if (getReference() != null)
1289
Element element = (Element) getReference();
1296
org.w3c.dom.Node parentElem = element.getParentNode();
1297
if (parentElem != null)
1299
parentElem.removeChild(element);
1306
* Updates the node's value if it represents an element node.
1308
* @param value the new value
1310
private void updateElement(Object value)
1312
Text txtNode = findTextNodeForUpdate();
1316
if (txtNode != null)
1318
((Element) getReference()).removeChild(txtNode);
1323
if (txtNode == null)
1325
String newValue = isDelimiterParsingDisabled() ? value.toString()
1326
: PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
1327
txtNode = document.createTextNode(newValue);
1328
if (((Element) getReference()).getFirstChild() != null)
1330
((Element) getReference()).insertBefore(txtNode,
1331
((Element) getReference()).getFirstChild());
1335
((Element) getReference()).appendChild(txtNode);
1340
String newValue = isDelimiterParsingDisabled() ? value.toString()
1341
: PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
1342
txtNode.setNodeValue(newValue);
1348
* Updates the node's value if it represents an attribute.
1351
private void updateAttribute()
1353
XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter(),
1354
isAttributeSplittingDisabled());
1358
* Returns the only text node of this element for update. This method is
1359
* called when the element's text changes. Then all text nodes except
1360
* for the first are removed. A reference to the first is returned or
1361
* <b>null </b> if there is no text node at all.
1363
* @return the first and only text node
1365
private Text findTextNodeForUpdate()
1368
Element elem = (Element) getReference();
1369
// Find all Text nodes
1370
NodeList children = elem.getChildNodes();
1371
Collection<org.w3c.dom.Node> textNodes = new ArrayList<org.w3c.dom.Node>();
1372
for (int i = 0; i < children.getLength(); i++)
1374
org.w3c.dom.Node nd = children.item(i);
1375
if (nd instanceof Text)
1388
// We don't want CDATAs
1389
if (result instanceof CDATASection)
1391
textNodes.add(result);
1395
// Remove all but the first Text node
1396
for (org.w3c.dom.Node tn : textNodes)
1398
elem.removeChild(tn);
1405
* A concrete {@code BuilderVisitor} that can construct XML
1408
static class XMLBuilderVisitor extends BuilderVisitor
1410
/** Stores the document to be constructed. */
1411
private Document document;
1413
/** Stores the list delimiter.*/
1414
private final char listDelimiter;
1416
/** True if attributes should not be split */
1417
private boolean isAttributeSplittingDisabled;
1420
* Creates a new instance of {@code XMLBuilderVisitor}.
1422
* @param doc the document to be created
1423
* @param listDelimiter the delimiter for attribute properties with multiple values
1424
* @param isAttributeSplittingDisabled true if attribute splitting is disabled.
1426
public XMLBuilderVisitor(Document doc, char listDelimiter, boolean isAttributeSplittingDisabled)
1429
this.listDelimiter = listDelimiter;
1430
this.isAttributeSplittingDisabled = isAttributeSplittingDisabled;
1434
* Processes the node hierarchy and adds new nodes to the document.
1436
* @param rootNode the root node
1438
public void processDocument(Node rootNode)
1440
rootNode.visit(this, null);
1444
* Inserts a new node. This implementation ensures that the correct
1445
* XML element is created and inserted between the given siblings.
1447
* @param newNode the node to insert
1448
* @param parent the parent node
1449
* @param sibling1 the first sibling
1450
* @param sibling2 the second sibling
1451
* @return the new node
1454
protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
1456
if (newNode.isAttribute())
1458
updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter,
1459
isAttributeSplittingDisabled);
1465
Element elem = document.createElement(newNode.getName());
1466
if (newNode.getValue() != null)
1468
String txt = newNode.getValue().toString();
1469
if (listDelimiter != 0)
1471
txt = PropertyConverter.escapeListDelimiter(txt, listDelimiter);
1473
elem.appendChild(document.createTextNode(txt));
1475
if (sibling2 == null)
1477
getElement(parent).appendChild(elem);
1479
else if (sibling1 != null)
1481
getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
1485
getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
1492
* Helper method for updating the value of the specified node's
1493
* attribute with the given name.
1495
* @param node the affected node
1496
* @param elem the element that is associated with this node
1497
* @param name the name of the affected attribute
1498
* @param listDelimiter the delimiter for attributes with multiple values
1499
* @param isAttributeSplittingDisabled true if attribute splitting is disabled.
1501
private static void updateAttribute(Node node, Element elem, String name, char listDelimiter,
1502
boolean isAttributeSplittingDisabled)
1504
if (node != null && elem != null)
1506
boolean hasAttribute = false;
1507
List<ConfigurationNode> attrs = node.getAttributes(name);
1508
StringBuilder buf = new StringBuilder();
1509
char delimiter = (listDelimiter != 0) ? listDelimiter : ATTR_VALUE_DELIMITER;
1510
for (ConfigurationNode attr : attrs)
1512
if (attr.getValue() != null)
1514
hasAttribute = true;
1515
if (buf.length() > 0)
1517
buf.append(delimiter);
1519
String value = isAttributeSplittingDisabled ? attr.getValue().toString()
1520
: PropertyConverter.escapeDelimiters(attr.getValue().toString(),
1524
attr.setReference(elem);
1529
elem.removeAttribute(name);
1533
elem.setAttribute(name, buf.toString());
1539
* Updates the value of the specified attribute of the given node.
1540
* Because there can be multiple child nodes representing this attribute
1541
* the new value is determined by iterating over all those child nodes.
1543
* @param node the affected node
1544
* @param name the name of the attribute
1545
* @param listDelimiter the delimiter for attributes with multiple values
1546
* @param isAttributeSplittingDisabled true if attributes splitting is disabled.
1548
static void updateAttribute(Node node, String name, char listDelimiter,
1549
boolean isAttributeSplittingDisabled)
1553
updateAttribute(node, (Element) node.getReference(), name, listDelimiter,
1554
isAttributeSplittingDisabled);
1559
* Helper method for accessing the element of the specified node.
1561
* @param node the node
1562
* @return the element of this node
1564
private Element getElement(Node node)
1566
// special treatment for root node of the hierarchy
1567
return (node.getName() != null && node.getReference() != null) ? (Element) node
1569
: document.getDocumentElement();
1574
* A special implementation of the {@code FileConfiguration} interface that is
1575
* used internally to implement the {@code FileConfiguration} methods
1576
* for {@code XMLConfiguration}, too.
1578
private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
1581
public void load(InputStream in) throws ConfigurationException
1583
XMLConfiguration.this.load(in);