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