All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration2.XMLPropertiesConfiguration Maven / Gradle / Ivy

Go to download

Tools to assist in the reading of configuration/preferences files in various formats

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration2;

import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.configuration2.convert.ListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileLocator;
import org.apache.commons.configuration2.io.FileLocatorAware;
import org.apache.commons.text.StringEscapeUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * This configuration implements the XML properties format introduced in Java, see
 * https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html. An XML properties file looks like this:
 *
 * 
 * <?xml version="1.0"?>
 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
 * <properties>
 *   <comment>Description of the property list</comment>
 *   <entry key="key1">value1</entry>
 *   <entry key="key2">value2</entry>
 *   <entry key="key3">value3</entry>
 * </properties>
 * 
* * The Java runtime is not required to use this class. The default encoding for this configuration format is UTF-8. * Note that unlike {@code PropertiesConfiguration}, {@code XMLPropertiesConfiguration} does not support includes. * * Note:Configuration objects of this type can be read concurrently by multiple threads. However if one of * these threads modifies the object, synchronization has to be performed manually. * * @since 1.1 */ public class XMLPropertiesConfiguration extends BaseConfiguration implements FileBasedConfiguration, FileLocatorAware { /** * SAX Handler to parse a XML properties file. * * @since 1.2 */ private final class XMLPropertiesHandler extends DefaultHandler { /** The key of the current entry being parsed. */ private String key; /** The value of the current entry being parsed. */ private StringBuilder value = new StringBuilder(); /** Indicates that a comment is being parsed. */ private boolean inCommentElement; /** Indicates that an entry is being parsed. */ private boolean inEntryElement; @Override public void characters(final char[] chars, final int start, final int length) { /** * We're currently processing an element. All character data from now until the next endElement() call will be the data * for this element. */ value.append(chars, start, length); } @Override public void endElement(final String uri, final String localName, final String qName) { if (inCommentElement) { // We've just finished a element so set the header setHeader(value.toString()); inCommentElement = false; } if (inEntryElement) { // We've just finished an element, so add the key/value pair addProperty(key, value.toString()); inEntryElement = false; } // Clear the element value buffer value = new StringBuilder(); } @Override public void startElement(final String uri, final String localName, final String qName, final Attributes attrs) { if ("comment".equals(qName)) { inCommentElement = true; } if ("entry".equals(qName)) { key = attrs.getValue("key"); inEntryElement = true; } } } /** * The default encoding (UTF-8 as specified by https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html) */ public static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); /** * Default string used when the XML is malformed */ private static final String MALFORMED_XML_EXCEPTION = "Malformed XML"; /** The temporary file locator. */ private FileLocator locator; /** Stores a header comment. */ private String header; /** * Creates an empty XMLPropertyConfiguration object which can be used to synthesize a new Properties file by adding * values and then saving(). An object constructed by this C'tor can not be tickled into loading included files because * it cannot supply a base for relative includes. */ public XMLPropertiesConfiguration() { } /** * Creates and loads the XML properties from the specified DOM node. * * @param element The non-null DOM element. * @throws ConfigurationException Error while loading the Element. * @since 2.0 */ public XMLPropertiesConfiguration(final Element element) throws ConfigurationException { load(Objects.requireNonNull(element, "element")); } /** * Escapes a property value before it is written to disk. * * @param value the value to be escaped * @return the escaped value */ private String escapeValue(final Object value) { final String v = StringEscapeUtils.escapeXml10(String.valueOf(value)); return String.valueOf(getListDelimiterHandler().escape(v, ListDelimiterHandler.NOOP_TRANSFORMER)); } /** * Gets the header comment of this configuration. * * @return the header comment */ public String getHeader() { return header; } /** * Initializes this object with a {@code FileLocator}. The locator is accessed during load and save operations. * * @param locator the associated {@code FileLocator} */ @Override public void initFileLocator(final FileLocator locator) { this.locator = locator; } /** * Parses a DOM element containing the properties. The DOM element has to follow the XML properties format introduced in * Java, see https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html * * @param element The DOM element * @throws ConfigurationException Error while interpreting the DOM * @since 2.0 */ public void load(final Element element) throws ConfigurationException { if (!element.getNodeName().equals("properties")) { throw new ConfigurationException(MALFORMED_XML_EXCEPTION); } final NodeList childNodes = element.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { final Node item = childNodes.item(i); if (item instanceof Element) { if (item.getNodeName().equals("comment")) { setHeader(item.getTextContent()); } else if (item.getNodeName().equals("entry")) { final String key = ((Element) item).getAttribute("key"); addProperty(key, item.getTextContent()); } else { throw new ConfigurationException(MALFORMED_XML_EXCEPTION); } } } } @Override public void read(final Reader in) throws ConfigurationException { final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(false); factory.setValidating(true); try { final SAXParser parser = factory.newSAXParser(); final XMLReader xmlReader = parser.getXMLReader(); xmlReader.setEntityResolver((publicId, systemId) -> new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"))); xmlReader.setContentHandler(new XMLPropertiesHandler()); xmlReader.parse(new InputSource(in)); } catch (final Exception e) { throw new ConfigurationException("Unable to parse the configuration file", e); } // todo: support included properties ? } /** * Writes the configuration as child to the given DOM node * * @param document The DOM document to add the configuration to * @param parent The DOM parent node * @since 2.0 */ public void save(final Document document, final Node parent) { final Element properties = document.createElement("properties"); parent.appendChild(properties); if (getHeader() != null) { final Element comment = document.createElement("comment"); properties.appendChild(comment); comment.setTextContent(StringEscapeUtils.escapeXml10(getHeader())); } final Iterator keys = getKeys(); while (keys.hasNext()) { final String key = keys.next(); final Object value = getProperty(key); if (value instanceof List) { writeProperty(document, properties, key, (List) value); } else { writeProperty(document, properties, key, value); } } } /** * Sets the header comment of this configuration. * * @param header the header comment */ public void setHeader(final String header) { this.header = header; } @Override public void write(final Writer out) throws ConfigurationException { final PrintWriter writer = new PrintWriter(out); String encoding = locator != null ? locator.getEncoding() : null; if (encoding == null) { encoding = DEFAULT_ENCODING; } writer.println(""); writer.println(""); writer.println(""); if (getHeader() != null) { writer.println(" " + StringEscapeUtils.escapeXml10(getHeader()) + ""); } final Iterator keys = getKeys(); while (keys.hasNext()) { final String key = keys.next(); final Object value = getProperty(key); if (value instanceof List) { writeProperty(writer, key, (List) value); } else { writeProperty(writer, key, value); } } writer.println(""); writer.flush(); } private void writeProperty(final Document document, final Node properties, final String key, final List values) { values.forEach(value -> writeProperty(document, properties, key, value)); } private void writeProperty(final Document document, final Node properties, final String key, final Object value) { final Element entry = document.createElement("entry"); properties.appendChild(entry); // escape the key final String k = StringEscapeUtils.escapeXml10(key); entry.setAttribute("key", k); if (value != null) { final String v = escapeValue(value); entry.setTextContent(v); } } /** * Write a list property. * * @param out the output stream * @param key the key of the property * @param values a list with all property values */ private void writeProperty(final PrintWriter out, final String key, final List values) { values.forEach(value -> writeProperty(out, key, value)); } /** * Write a property. * * @param out the output stream * @param key the key of the property * @param value the value of the property */ private void writeProperty(final PrintWriter out, final String key, final Object value) { // escape the key final String k = StringEscapeUtils.escapeXml10(key); if (value != null) { final String v = escapeValue(value); out.println(" " + v + ""); } else { out.println(" "); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy