Usage
Create config interface
Write an interface with accessor methods for each config entry your application supports. The accessor methods can return the concrete types you want your config entry to be. There is a number of types that are supported by default, but custom types can be registered to support additional types.
Config values that are optional, must be of type java.util.Optional
or the
more specialized variants OptionalInt
, OptionalLong
or OptionalDouble
.
All other config values are considered mandatory. Missing mandatory values
will fail validation on construction time and therefore raise an exception
(with details about the validation failures).
The interface must be annotated with the @Coat.Config
annotation for
the annotation processor to recognize it.
Each accessor may be annotated with the @Coat.Param
annotation
to tell the processor the corresponding key in the config file (if it
should be different than what Coat would infer otherwise) or a default
value in case the key is missing in the config file.
Both annotations have some possible attributes that can be set which are described in Annotations.
For example:
1package com.example;
2
3import de.poiu.coat.annotation.Coat;
4
5@Coat.Config
6public interface AppConfig {
7 public String appName();
8
9 public InetAddress remoteIP();
10
11 @Coat.Param(default = "5044")
12 public int remotePort();
13
14 @Coat.Param(key = "long_description")
15 public Optional<String> description();
16}
Generate the builder for the config class
When compiling the project the annotation processor will produce a builder to
build a concrete implementation of the interface in the same package and (by
default) the same name with Builder
appended to it. Therefore the above
example interface would produce an com.example.AppConfigBuilder
class.
Use the generated builder
The generated builder can be instantiated with a number of different config sources. Since version 1.0.0 Coat also allows instantiation from multiple sources at the same time.
Config Sources
The main source of config entries for Coat are java.util.Map<String, String>
s.
For common config sources some shortcut methods exist that provide
direct support, but in all other cases everything that can be represented as
a map of String keys to String values can be used as a config source.
- java.util.Map<String, String>
A map with key-value mappings can directly be given to Coat to instantiate a Config class with these entries.
- java.io.File
The traditional approach of specifying config entries in Java is via Property-Files. Coat explicitly supports this use case by allowing a
File
object as config source which in then read via Javas loading mechanism for Property-Files.- java.util.Properties
Instead of specifying a file, a
Properties
object can be directly fed into Coat as config source. This allows, for example, the usage of Java System Properties as config source by reading them viaSystem#getProperties()
.While
java.util.Properties
can theoretically contain non-String keys or values, Coat does not allow these and will drop such entries (generating a warning message).- Environment variables
Special support is provided for reading config entries from environment variables. This is a common approach for applications running in containers.
Unfortunately the allowed character set for environment variable keys is much stricter than the character set in Coat config (and therefore Java Properties) files. For that reason a relaxed mapping is applied to match environment variables to Coat config keys.
- All dots and hyphens are treated as underscores.
- All uppercase characters in Coat config keys are preceded by an underscore (to convert camelCase to UPPER_CASE).
- The comparison between the environment variables and the Coat config keys is done case insensitively.
For example the environment variable
SERVER_MQTT_HOST
will match the config keyserver.mqttHost
.
Instantiation via static factory methods
Static factory methods are provided for reading config values from a single config source:
from(java.util.Map)
from(java.util.Properties)
from(java.io.File)
fromEnvVars()
Example:
1final MyConfig config= MyConfigBuilder.from(System.getProperties());
Instantiation with multiple config sources
The generated builder allows for using multiple config sources by using
different add
methods.
The order in which the config sources are read is defined by the order in which they are added to the builder. Later config sources overwrite values with the same keys of earlier config sources.
For example for reading the basic config from a config file, but allow overriding some entries via environment variables:
1final MyConfig config= MyConfigBuilder.create()
2 .add(new File("myConfig.properties"))
3 .addEnvVars()
4 .build();
Validation of config objets
Since version 2.0.0 config objects are always validated at construction time.
Therefore all the static factory methods as well as the #build()
method of
the generated Builder throw a ConfigValidationException
in case this validation
fails.
See Validation for a more
detailled description of the validation process.
1public class MyApp {
2 public static void main(String[] args) {
3 try {
4 final AppConfigBuilder config= AppConfigBuilder.from(
5 new File("/path/to/config.properties"));
6
7 System.out.println("Starting " + config.appName());
8 config.description().ifPresent(System.out::println);
9
10 final Socket s= new Socket(config.remoteIP(), config.remotePort());
11
12 …
13 } catch (final ConfigValidationException ex) {
14 System.err.println("Error in config file:\n"
15 + ex.getValidationResult().toString());
16 System.exit(1);
17 }
18 }
19}