2019-01-09

Tamaya Formats (Extension Module)

Tamaya Formats is an extension module. Refer to the extensions documentation for further details.

What functionality this module provides ?

Tamaya Formats provides an abstraction for configuration formats provding the following benefits:

  • Parsing of resources in can be implemented separately from interpreting the different aspects/parts parsed. As an example a file format can define different sections. Depending on the company specific semantics of the sections a different set of PropertySource instances must be created.

  • Similarly the configuration abstraction can also be used as an interface for integrating Tamaya with alternate frameworks that provide logic for reading configuration files, such as Apache commons.configuration.

Compatibility

The module is based on Java 8.

Installation

To use the formats module you only must add the corresponding dependency to your module:

<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-formats</artifactId>
  <version>{tamaya_version}</version>
</dependency>

Basic Concept

Formats should be reusable, meaning you should have to write a format parser only once and then be able to map the data read into whatever data structure (in our cases: property sources). So it is useful to separate concerns into

  • an arbitrary configuration format (textual or binary)

  • a parser (ConfigurationFormat) that transfers a given format into an intermediate representation (ConfigurationData).

  • an optional customization, implemented by a factory method pattern to adapt the mapping of ConfigurationData read to a collection of PropertySources (they can have different ordinal semantics).

ConfigurationData

Configuration formats can be very different. Some are simple key/value pairs, whereas other also consist of multiple sections (e.g. ini-files) or hierarchical data (e.g. yaml, xml). This is solved in Tamaya by mapping the configuration read into a normalized intermediary format called ConfigurationData:

ConfigurationData
public final class ConfigurationData {

    public ConfigurationFormat getFormat();
    public String getResource();

    public List<PropertyValue> getData();

    public boolean isEmpty();
}
  • with getResource() and getFormat() the underlying resource and the format that read this data can be accessed.

  • getData() allows access to the data read. Hereby this data can be mapped in different ways:

    • as (multiple) simple key-value literal pairs, or

    • as field-mapped object type values, or as

    • list values

    • the children of list and object values can be of any of the three types described above. As a consequence a simple PropertyValue can be a simple literal value, or a complex tree/list structure, similar to common configuration formats.

ConfigurationFormat

A ConfigurationFormat is basically an abstraction that reads a configuration resource (modelled by an InputStream) and creates a corresponding ConfigurationData instance.

public interface ConfigurationFormat {

    String getName();
    boolean accepts(URL url);
    ConfigurationData readConfiguration(String resource, InputStream inputStream);
}

Creating a default PropertySource for a ConfigurationFormat

The module defines a singleton ConfigurationFormats which provides an easy to use API for creating ConfigurationData and PropertySources using abstract ConfigurationFormat implementations:

public final class ConfigurationFormats {

    public static ConfigurationFormats getInstance();
    public static ConfigurationFormats getInstance(ClassLoader classLoader);

    public List<ConfigurationFormat> getFormats();
    public List<ConfigurationFormat> getFormats(String... formatNames);
    public List<ConfigurationFormat> getFormats(final URL url);

    public ConfigurationData readConfigurationData(final URL url)
    throws IOException;
    public ConfigurationData readConfigurationData(URL url, ConfigurationFormat... formats)
    throws IOException;
    public ConfigurationData readConfigurationData(URL url, Collection<ConfigurationFormat> formats)
    throws IOException;
    public Collection<ConfigurationData> readConfigurationData(Collection<URL> urls, ConfigurationFormat... formats);
    public Collection<ConfigurationData> readConfigurationData(Collection<URL> urls, Collection<ConfigurationFormat> formats);
    public ConfigurationData readConfigurationData(String resource, InputStream inputStream,
                                                          ConfigurationFormat... formats)
    throws IOException;
    public ConfigurationData readConfigurationData(String resource, InputStream inputStream,
                                                          Collection<ConfigurationFormat> formats)
    throws IOException;

    public PropertySource createPropertySource(URL url, ConfigurationFormat... formats)
    throws IOException;
    public PropertySource createPropertySource(URL url, Collection<ConfigurationFormat> formats)
    throws IOException;
    public PropertySource createPropertySource(String resource, InputStream inputStream,
                                                      ConfigurationFormat... formats);
    public PropertySource createPropertySource(String resource, InputStream inputStream,
                                                       Collection<ConfigurationFormat> formats);
}
  • getFormats() returns all registered formats.

  • getFormats(String...) allows to access all formats with a given name.

  • getFormats(URL url) allows to access all formats that declare that can optionally read an input from a given URL.

  • readConfigurationData(...) reads data from an input and creates a corresponding ConfigurationData, either trying all known formats that declare its compatibility with the given input or the formats passed explicitly.

  • createPropertySource(...) allows to create a PropertySource reading a given input and the formats to be used or known. Hereby a default property mapping is applied.

So creating a PropertySource from a resource is basically a one liner:

URL url = ...;
PropertySource propertySource = ConfigurationFormats.getInstance().createPropertySource(url);

// constraining the formats to be used (assumption: json and yaml extensions are loaded)
PropertySource propertySource = ConfigurationFormats.getInstance().reatePropertySource(
                                    url,
                                    ConfigurationFormats.getFormats("json", "yaml"));

Customize how ConfigurationData maps to PropertySource

For for the conversion of ConfigurationData into a PropertySource different approaches can be useful. In most cases the usage of a MappedConfigurationDataPropertySource, is a good choice to start. This class provides a convenient default mapping and also allows to customized the mapping easily: it simply iterates over all values recursively and adds them using their fully qualified name as single value properties.

This behaviour can be easily adapted by overriding the popoulateData method:

ConfigurationData data = ...;
MappedConfigurationDataPropertySource ps =
  new MappedConfigurationDataPropertySource(data){
    protected Map<String, PropertyValue> populateData(ConfigurationData data) {
      ...
    }
  };

Nevertheless, depending on the context, where a configuration source was read (classloader, time, source etc.) the resulting properties can have different semnatics, especially different priorities. Also section names may be mapped into different ordinals instead of using them as key prefixes (e.g. imagine configuration formats with a 'default', 'main', and 'overrides' sections). For such use cases no simple mapping can be defined. Consequently the functionality mapping the normalized ConfigurationData read to the appropriate collection of PropertySource instances must be implemented by the client code.

For this scenario the BaseFormatPropertySourceProvider can be used, defining the following mapping function that mus be implemented:

/**
 * Method to create a {@link org.apache.tamaya.spi.PropertySource} based on the given entries read.
 *
 * @param data the configuration data, not null.
 * @return the {@link org.apache.tamaya.spi.PropertySource} instance ready to be registered.
 */
protected abstract Collection<PropertySource> getPropertySources(ConfigurationData data);

When using Java 8 these mappings can be asily passed as parameters to the createPropertySource methods.

Predefined formats

The formats module ships with 3 predefined formats:

  • .ini files, commonly known from Microsoft based systems, registered as ini.

  • .properties files, as defined by java.util.Properties, registered as properties.

  • .xml properties files, as defined by java.util.Properties, registered as xml-properties.

ini Configuration File Mapping

This module implements the ini file format with the class org.apache.tamaya.format.formats.IniConfigurationFormat.

The default mapping is bext illustrated by a small example, so consider the following .ini file:

a=valA
a.b=valB

[section1]
aa=sectionValA
aa.b.c=SectionValC

[section2]
a=val2Section2

This file content by default is mapped to the following Tamaya properties:

a=valA
a.b=valB
section1.valA=sectionValA
section1.a.b.c=SectionValC
section2.a=val2Section2

Summarizing

  • entries without a section are mapped to the default section.

  • entries with a section are mapped to a corresponding section, hereby everything between the brackets is used as section name (trimmed).

  • section names are separated using a double colon (.), they are modelled as simple parent PropertyValue nodes.

ConfigurationData allows to access all the different parts:

  • the default properties (a, a.b)

  • the section section1, with properties aa, aa.b.c

  • the section section2, with properties a

XML Property and ordinary Property Files

This module also ships with ConfigurationFormat implementations that reuse the parsing functionality provided with java.util.Properties:

  • org.apache.tamaya.format.formats.PropertiesFormat uses Properties.read(InputStream).

  • org.apache.tamaya.format.formats.PropertiesXmlFormat uses Properties.readFromXml(InputStream).