:jbake-type: page
:jbake-status: published

= Apache Tamaya - Extension: Formats

toc::[]


[[Formats]]
== Tamaya Formats (Extension Module)

Tamaya _Formats_ is an extension module. Refer to the link:../extensions.html[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:

[source, xml]
-----------------------------------------------
<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+:

[source,java]
.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.

[source,java]
-------------------------------------------------------
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:

[source,java]
-------------------------------------------------------
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:

[source,java]
-------------------------------------------------------
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:

[source,java]
-------------------------------------------------------
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:

[source,java]
-------------------------------------------------------
/**
 * 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:

[source,listing]
-------------------------------------------------------
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:

[source,listing]
-------------------------------------------------------
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)`.
