Apache Tamaya — Extension: Formats
Tamaya Formats (Extension Module)
Overview
Tamaya Formats is an extension module. Refer to the extensions documentation for further details.
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 7, so it can be used with Java 7 and beyond.
Installation
To benefit from dynamic value resolution you only must add the corresponding dependency to your module:
<dependency> <groupId>org.apache.tamaya.ext</groupId> <artifactId>tamaya-formats</artifactId> <version>{tamayaVersion}</version> </dependency>
The module automatically registers an according PropertyFilter that is automatically called, whenever a value is accessed.
The Idea
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).
ConfigurationData
Configuration formats can be very different. Some are simpley 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:
ConfigurationDatapublic final class ConfigurationData { public ConfigurationFormat getFormat(); public String getResource(); public Set<String> getSectionNames(); public Map<String,String> getSection(String name); public Map<String,Map<String,String>> getSections(); public boolean hasDefaultProperties(); public Map<String,String> getDefaultProperties(); public Map<String,String> getCombinedProperties(); public boolean hasCombinedProperties(); public boolean isEmpty(); }
In detail the data read from a file is organized into sections as follows:
-
with getResource() and getFormat() the underlying resource and the format that read this data can be accessed.
-
properties can be owned by
-
named sections
-
an (unnamed) default section
-
-
each section section contains a map of properties. Hereby the same key can be part of the default section and multiple named sections, depending on the configuration format.
-
The method getSectionNames() returns a set of all section names.
-
With getSection(String name) a named section can be accessed.
-
With getDefaultSection() the default section can be accessed.
-
With getCombinedProperties() a flattened entry map can be accessed built up (by default) out of
-
all entries from the default section, without any changes.
-
all entries from named sections, where the key for each entry is prefix with the section name and a dot separator.
-
-
The configuration format used determines the mapping of configuration data read into this structure. The format implementation can as well provide alternate implementations of how the data read should be mapped into the combined properties map.
Now for the conversion of ConfigurationData into a PropertySource different default approaches are used:
-
The ConfigurationFormat that read the data can provide the (combined) properties accessible from getProperties() explcitly, which can be used to initialize a single PropertySource containing the data read.
-
If the format did not set the final properties, but only a default section is present this default section can be directly returned as combined properties.
-
In all other cases a properties can be uniquely mapped into one single properties Map, by prefixing all keys of each section present with the (unique) section name and a '.' separator.
Nevertheless, depending on the context, where a configuration source was read (classloader, time, source etc.) the resulting PropertySource can have different semnatics, especially for the PropertySources ordinal. 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 more complex or custom cases no useful default mapping can be defined. In such cases this functionality must be implemented in a mapData method, which converts the normalized ConfigData read to the appropriate collection of PropertySource instances:
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 { public String getName(); boolean accepts(URL url); ConfigurationData readConfiguration(String resource, InputStream inputStream); }
Normally you need to map the resulting ConfigurationData to one or multiple PropertySources. In case, where the properties provided match exactly the extected properties a FlattenedDefaultPropertySource is provided out-of-the-box. If the exact mapping must be overridden, you can simply override the property source’s initialize method to adapt the mapping:
ConfigurationData data = ...; FlattenedDefaultPropertySource ps = new FlattenedDefaultPropertySource(data){ protected Map<String, String> populateData(ConfigurationData data) { ... } };
How to tranform ConfigurationData into a PropertySource
The Tamaya main building block for configuration properties is the PropertySource interface. You have several options to implement this tranformation:
-
You can simply map the properties returned by getCombinedProperties() and use them as properties returned by a wrapping property source. Since this use case is common for all kind of non hierarchic configuration formats it is directly supported by the FlattenedDefaultPropertySource class.
-
When the ConfigurationFormat is more complex, multiple 'sections' are common. What a section exactly is depends on the concrete format only. The ConfigurationFormat should provide detailed information how the data read is mapped to default properties and sections and how it is assembled into the combinedProperties map. Also here the FlattenedDefaultPropertySource class can help you with its default mapping. Nevertheless in some cases it is necessary to write an explicit mapping, e.g. when
-
different sections must be mapped to multiple PropertySources, with optionally fixed ordinals.
-
sections must be cross-checked and combined into new properties, or into several PropertySources.
-
other complex mapping requirements apply.
Examples
Mapping ini-Files
Consider the following ini-file:
Example.inia=valA a.b=valB [section1] aa=sectionValA aa.b.c=SectionValC [section2] a=val2Section2
This file content coud be mapped to the following structure:
Mapping of Example.inia=valA a.b=valB section1.valA=sectionValA section1.a.b.c=SectionValC section2.a=val2Section2
Nevertheless from the ConfigurationData instance a more complex algorithm can access all the different parts:
-
the_default_ properties (a, a.b)
-
the section section1, with properties aa, aa.b.c
-
the section section2, qith properties a
Mapping xml-Files
The same concept can also be applied to xml-files. Consider the following configuration file:
Example.conf<config> <default> <a>valA</a> <a.b>valB</a.B> </default> <section id="section1"> <param id="aa">sectionValA</aa> <param id="aa.b.c">SectionValC</aa.b.c> </section> <section id="section2"> <param id="a">val2Section2</aa> </section> </config>
This file basically describes the same configuration as the ini-based version we have seen before. The formats module hereby ships with 3 format classes:
-
PropertiesFormat providing support for .properties files.
-
PropertiesXmlFormat providing support for xml.property files.
-
IniConfiguratonFormat providing support for xml.property files.