2019-11-17

Tamaya Collections Support (Extension Module)

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

What functionality this module provides ?

All configuration in Tamaya is expressed as simple key, value pairs. Nevertheless this concept allows similarly the modelling of collection typed values such as lists, sets, maps or simple collections of things. The Tamaya Collections extension adds this functionality to the Tamaya eco-system.

Compatibility

The module requires Java 8.

Installation

To use Tamaya collection support you only must add the corresponding dependency to your module:

<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-collections</artifactId>
  <version>0.5-incubating-SNAPSHOT</version>
</dependency>

Overview

Tamaya Collections adds PropertyConverter implementations that are able to access configuration data as lists, maps or sets. By default this works out of the box as easy as accessing any other type of configuration data, e.g.

Configuration config = ConfigurationProvider.getConfiguration();

// Without any content specification, a list of String is returned.
List<String> simpleList = config.get("my.list.config.entry", List.class);

// Using a TypeLiteral allows to use every convertible sub type supported by the system.
List<Integer> intList = config.get("my.list.config.entry", new TypeLiteral<List<Integer>>(){});

Configuration in that case, by default, is a simple comma-separated list of entries, e.g.

my.list.config.entry=1,34454,23,344545,3445

Additionally the module allows adding additional meta-entries, which allows to tweak some of the inner-workings, e.g.

  • item-converter: using a custom PropertyConverter implementation for parsing collection entries.

  • entry-separator: specifying a custom separator to split the list and map items (default is {{','}}.

  • map-entry-separator: specifying a custom separator to split key/value pairs when parsing map entries.

  • collection-type: specifying the implementation type of the collection to be returned.

  • mapping-type: specifying the implementation type of the collection item to be returned.

Supported Types

This module currently supports the following types:

  • java.util.Iterable

  • java.util.Collection

  • java.util.List

  • java.util.ArrayList

  • java.util.ArrayList

  • java.util.LinkedList

  • java.util.Set

  • java.util.SortedSet

  • java.util.TreeSet

  • java.util.HashSet

  • java.util.Map

  • java.util.SortedMap

  • java.util.HashMap

  • java.util.TreeMap

Default Collection Type Mapping

The collection type is determined by the parameter type accessed, e.g. config.get("mylist", ArrayList.class) will always return an ArrayList as result. * if you have a generic type: an ArrayList is returned for Collection, List, ArrayList, Iterable a HashSet is returned for Set, HashSet a HashMap is returned for Map, HashMap a TreeMap is returned for SortedMap, TreeMap ** a TreeSet is returned for SortedSet, TreeSet * in all other cases you have to explicitly define the collection type. Hereby you may omit the java.util package. Collection types not contained in java.util require adding an additional converter implementation.

Note
This means that depending on your use case you can access different collection types based on the same configuration values, as long as their is a PropertyConverter that can convert the raw configuration value to the required target type.
Caution
If you do NOT use TypeLiteral to define your target type, the item target type will not be available to Tamaya due to Java’s type erasure. In this case it will always return String as item type.

Configuring Collection Type Mapping

Tamaya Collections allows you to configure the default target collection type by adding the following meta-configuration entry (shown for the mylist entry). Hereby the package part java.util. can be ommitted:

[ source, properties]

mylist=a,b,c
[META]mylist.collection-type=LinkedList

When calling config.get("mylist", ArrayList.class) this parameter does not have any effect, so you will still get an ArrayList as a result. However when you call config.get("mylist", List.class) you will get a LinkedList as implementation type.

This mechanism similarly applies to all kind of collections, so you can use it similarly to define the implementation type returned when accessing List, Map or Collection.

Evaluating Collection Entries

Tamaya’s internal representation of configuration is a modelled by a PropertyValue. This class actually supports different models how configuration data can be represented:

  1. as simple literal key-value pair, or

  2. as map-like value object, or

  3. as list-like list object.

Tamaya’s ConversionContext actually provides all values matching a given target key. As a consequence there are four basic algorithms, how a collection can be mapped from entries given:

  • value_all: The list values are identiified by parsing the node value(s) into items, hereby the items of all values are combined.

  • value: The list values are identiified by parsing the node value(s) into items. Hereby only the items of the most significant config entry are considered.

  • node: The list values are identiified by using the node’s child value(s) as items. Hereby only the items of the most significant config entry are considered.

  • node_all: The list values are identiified by using the node’s child value(s) as items. Hereby the items of all values are combined.

  • override: This policy will try to guess the best value and node evaluation policy for most significant PropertyValue.

  • combine: This policy will try to guess the best value and node evaluation policy for each PropertyValue and combine the values to one collection. This is the default behaviour.

As an example consider the following configuration for my.list:

# from PropertSource 1
my.list=1,2,3

# from PropertSource 2, with higher precedence
my.list=4,5,6

Using the value evaluation policy this would result in the following final property:

my.list=4,5,6

Using the value_all evaluation policy this would result in the following final property:

my.list=1,2,3,4,5,6

For the _node_based evaluation policies consider the following YAML input data:

# from PropertSource 1
my.list:
  - 1
  - 2
  - 3

# from PropertSource 2, with higher precedence
my.list:
  - 4
  - 5
  - 6

In this case the entries are mapped to ListValue instances with multiple children. Similar mappings would apply using JSON or XML configuration formats. In this case it is more useful to collect the child nodes, instead the values (which basically are null on the parent node level). This is excatly what the node and node_all evaluaion policies are doing.

With Tamaya Collections you can now configure the evaluation policy using metadata, e.g. when using the default metadata format of Tamaya:

# use one of the policies: node, node_all, value, value_all, override or collect
[META]my.list.collection-mapping=collect

So declaring the collect policy the resulting raw output of the entry looks as illustrated below. Hereby it is even possible to mix different representations. E.g. it is possible to add additional values in a simple property files, whereas other values are configured in YAML or other formats:

# result when applying the collect policy:
my.list=1,2,3,4,5,6

Item Value Splitting

When evaluating collections from literal values, these values have to be tokenized into individual parts using a defined item-separator (by default ','). So a given configuration entry of 1,2,3 is mapped to "1","2","3". If the target context type is something different than String the same conversion logic is used as when mapping configuration parameters directly to non-String target types (implemented as +PropertyConverter classes). The procedure is identical for all collection types, including Map types, with the difference that each token for a map is parsed additionally for separating it into a key and a value parts. The default separator for map entries hereby is "=". Map keys, as of now, are always of type String, whereas for values all convertible types are supported. All separator characters can be masked by prefixing them with a \ character.

# a list, using the default format
list=1,2,3,4,5,6

# a map, using the default format
map=a=b, c=d

Trimming

By default all tokens parsed are trimmed before adding them to the final collection. In case of map entries this is also the case for key/value entries. So the following configuration results in the identical values for list1,list2 and map1,map2:

# a list, using the default format
list1=1, 2 ,3,4 , 5,6
list2=1, 2, 3, 4, 5, 6

# a map, using the default format
map1=a =b, c= d
map2=a=b, c = d

Nevertheless trimming/truncation can be controlled by the usage of brackets, e.g. the last list or map entry will have a single space character as value:

# a list, with a ' ' value at the end
list3=1, 2, 3, 4, 5, [ ]

# a map, with a ' ' value for key '0'
map3=1 = a, 2 = b, 0=[ ]

Hereby \[ escapes the sequence.

Customizing the format

The item and entry separators (by default ',' and "=") can be customized by setting corresponding meta-data entries, resulting in the same values as in the previous listing:

# a list, with a ' ' value at the end
list3=1__2__3__ 4__ 5__[ ]
[META]list3.item-separator=__

# a map, with a ' ' value for key '0'
map3=1->a, 2->b, 0->[ ]
[META]map3.map-entry-separator=->

Of course these settings also can be combined:

# a reformatted map
redefined-map=0==none | 1==single | 2==any
[META]redefined-map.map-entry-separator===
[META]redefined-map.item-separator=|

Using a custom Converter

If configuring of item and map item separators as shown above is not sufficient, you still have an option: you can configure a custom converter to use for converting the given keys. This can be also configured by addinf a corresponding meta-configuration entry item-converter. As you would expect the converter must implement Tamaya’s PropertyConverter interface:

# a map using a custom converter
server.0=server1, localhost:8001
server.1.name=server2, localhost:8000, master=true
server.2=server3, localhost:8003
[META]server.item-converter=com.mycompany.config.ServerConverter