blob: 6cfc846d19d48c7da1900a125ed058db09538bf6 [file] [log] [blame]
:jbake-type: page
:jbake-status: published
= Apache Tamaya - Extension: Collection Support
toc::[]
[[Collections]]
== Tamaya Collections Support (Extension Module)
Tamaya _Collections_ is an extension module. Refer to the link:../extensions.html[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:
[source, xml, subs=attributes+]
-----------------------------------------------
<dependency>
<groupId>org.apache.tamaya.ext</groupId>
<artifactId>tamaya-collections</artifactId>
<version>{tamaya_version}</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.
[source, java]
-----------------------------------------------
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.
[source, properties]
-----------------------------------------------
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:
. as simple literal key-value pair, or
. as map-like value object, or
. 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+:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
my.list=4,5,6
-----------------------------------------------
Using the _value_all_ evaluation policy this would result in the following final property:
[source, properties]
-----------------------------------------------
my.list=1,2,3,4,5,6
-----------------------------------------------
For the _node_based evaluation policies consider the following YAML input data:
[source, yaml]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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.
[source, properties]
-----------------------------------------------
# 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+:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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:
[source, properties]
-----------------------------------------------
# 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
-----------------------------------------------