Skip to content

Commit

Permalink
docs: add several pages.
Browse files Browse the repository at this point in the history
  • Loading branch information
credmond-git committed Oct 7, 2024
1 parent 68a5641 commit 4e908a2
Show file tree
Hide file tree
Showing 34 changed files with 785 additions and 551 deletions.
8 changes: 8 additions & 0 deletions docs/gestalt-static/docs/annotations/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Annotating - Configurations",
"position": 4,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Gestalt concepts."
}
}
41 changes: 41 additions & 0 deletions docs/gestalt-static/docs/annotations/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
sidebar_position: 1
---

# Annotations
When decoding a Java Bean style class, a record, an interface or a Kotlin Data Class you can provide a custom annotation to override the path for the field as well as provide a default.
The field annotation `@Config` takes priority if both the field and method are annotated.
The class annotation `@ConfigPrefix` allows the user to define the prefix for the config object as part of the class instead of the `getConfig()` call. If you provide both the resulting prefix is first the path in getConfig then the prefix in the `@ConfigPrefix` annotation.
For example using `@ConfigPrefix(prefix = "connection")` with `DBInfo pool = gestalt.getConfig("db", DBInfo.class);` the resulting path would be `db.connection`.

```java
@ConfigPrefix(prefix = "db")
public class DBInfo {
@Config(path = "channel.port", defaultVal = "1234")
private int port;

public int getPort() {
return port;
}
}

DBInfo pool = gestalt.getConfig("", DBInfo.class);


public class DBInfo {
private int port;

@Config(path = "channel.port", defaultVal = "1234")
public int getPort() {
return port;
}
}

DBInfo pool = gestalt.getConfig("db.connection", DBInfo.class);
```

The path provided in the annotation is used to find the configuration from the base path provided in the call to Gestalt getConfig.

So if the base path from gestalt.getConfig is `db.connection` and the annotation is `channel.port` the path the configuration will look for is `db.connection.channel.port`

The default accepts a string type and will be decoded into the property type using the gestalt decoders. For example if the property is an Integer and the default is "100" the integer value will be 100.
165 changes: 165 additions & 0 deletions docs/gestalt-static/docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
sidebar_position: 1
---

# Getting Started

## Introduction

Gestalt is a powerful Java configuration library that merges multiple sources of configuration data into a type-safe and structured format. It supports configuration files, environment variables, in-memory maps, and more.

### 1. Add the Bintray repository:

Versions 0.1.0 through version 0.11.0 require Java 8. Versions 0.12.0 plus require Java 11.

```kotlin
repositories {
mavenCentral()
}
```
### 2. Import gestalt-core, and the specific modules you need to support your use cases.

Gradle example:
```groovy
implementation 'com.github.gestalt-config:gestalt-core:${version}'
implementation 'com.github.gestalt-config:gestalt-kotlin:${version}'
```
Or with the kotlin DSL:
```kotlin
implementation("com.github.gestalt-config:gestalt-core:$version")
implementation("com.github.gestalt-config:gestalt-kotlin:$version")
```
Maven Example:
```xml
<dependency>
<groupId>com.github.gestalt-config</groupId>
<artifactId>gestalt-core</artifactId>
<version>${version}</version>
</dependency>
```

### 3. Setup your configuration files

Multiple types of configurations are supported from multiple sources.
Here is an example of the `default.properties`:
```properties
db.hosts[0].user=credmond
db.hosts[0].url=jdbc:postgresql://localhost:5432/mydb1
db.hosts[1].user=credmond
db.hosts[1].url=jdbc:postgresql://localhost:5432/mydb2
db.hosts[2].user=credmond
db.hosts[2].url=jdbc:postgresql://localhost:5432/mydb3
db.connectionTimeout=6000
db.idleTimeout=600
db.maxLifetime=60000.0

http.pool.maxTotal=100
http.pool.maxPerRoute=10
http.pool.validateAfterInactivity=6000
http.pool.keepAliveTimeoutMs=60000
http.pool.idleTimeoutSec=25
```
Here is an example of the `dev.properties`:
```properties
db.hosts[0].url=jdbc:postgresql://dev.host.name1:5432/mydb
db.hosts[1].url=jdbc:postgresql://dev.host.name2:5432/mydb
db.hosts[2].url=jdbc:postgresql://dev.host.name3:5432/mydb
db.connectionTimeout=600

http.pool.maxTotal=1000
http.pool.maxPerRoute=50
```

### 4. Construct Gestalt using the builder.

Use the builder to construct the Gestalt library. It is possible to do this manually, but the builder greatly simplifies the construction of the library. It uses the service loader to automatically load all the default dependencies.
```java
// Load the default property files from resources.
URL devFileURL = GestaltSample.class.getClassLoader().getResource("dev.properties");
File devFile = new File(devFileURL.getFile());

// using the builder to layer on the configuration files.
// The later ones layer on and over write any values in the previous
Gestalt gestalt = new GestaltBuilder()
.addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.properties").build())
.addSource(FileConfigSourceBuilder.builder().setFile(devFile).build())
.build();

// Loads and parses the configurations, this will throw exceptions if there are any errors.
gestalt.loadConfigs();
```

### 5. Retrieve configurations from Gestalt

Using the Gestalt Interface you can load sub nodes with dot notation into a wide variety of classes.
For non-generic classes you can pass in the class with `getConfig("db.port", Integer.class)` or for classes with generic types we need to use a special TypeCapture wrapper that captures the generic type at runtime. This allows us to construct generic classes with such as `List<String>` using `new TypeCapture<List<String>>() {}`

```java
Short myShortWrapper = gestalt.getConfig("http.pool.maxTotal", Short.class);
HttpPool pool = gestalt.getConfig("http.pool", HttpPool.class);
List<HttpPool> httpPoolList = gestalt.getConfig("http.pools", new TypeCapture<>() { });
var httpPoolList = gestalt.getConfig("http.pools", new TypeCapture<List<HttpPool>>() { });
```

The API to retrieve configurations:
```java
/**
* Get a config for a path and a given class.
* If the config is missing or there are any errors it will throw a GestaltException
*/
<T> T getConfig(String path, Class<T> klass) throws GestaltException;

/**
* Get a config for a path and a given class.
* If the config is missing, invalid or there was an exception it will return the default value.
*/
<T> T getConfig(String path, T defaultVal, Class<T> klass);

/**
* Get a config Optional for a path and a given class.
* If the config is missing, invalid or there was an exception it will return an Optional.empty()
*/
<T> Optional<T> getConfigOptional(String path, Class<T> klass);
```

#### Example
Example of how to create and load a configuration objects using Gestalt:
```java
public static class HttpPool {
public short maxTotal;
public long maxPerRoute;
public int validateAfterInactivity;
public double keepAliveTimeoutMs = 6000; // has a default value if not found in configurations
public OptionalInt idleTimeoutSec = 10; // has a default value if not found in configurations
public float defaultWait = 33.0F; // has a default value if not found in configurations

public HttpPool() {

}
}

public static class Host {
private String user;
private String url;
private String password;
private Optional<Integer> port;

public Host() {
}

// getter and setters ...
}

...
// load a whole class, this works best with pojo's
HttpPool pool = gestalt.getConfig("http.pool", HttpPool.class);
// or get a spcific config value
short maxTotal = gestalt.getConfig("http.pool.maxTotal", Short.class);
// get with a default if you want a fallback from code
long maxConnectionsPerRoute = gestalt.getConfig("http.pool.maxPerRoute", 24, Long.class);


// get a list of objects, or an PlaceHolder collection if there is no hosts found.
List<Host> hosts = gestalt.getConfig("db.hosts", Collections.emptyList(),
new TypeCapture<List<Host>>() {});
```
47 changes: 0 additions & 47 deletions docs/gestalt-static/docs/intro.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Loading - Configurations",
"position": 2,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Gestalt concepts."
}
}
51 changes: 51 additions & 0 deletions docs/gestalt-static/docs/loading-configuration/config-loader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
sidebar_position: 4
---

# Config Loader
Each config loader understands how to load a specific type of config. Often this is associated with a specific ConfigSource. For example the EnvironmentVarsLoader only loads the EnvironmentConfigSource. However, some loaders expect a format of the config, but accept it from multiple sources. For example the PropertyLoader expects the typical java property file, but it can come from any source as long as it is an input stream. It may be the system properties, local file, github, or S3.

| Config Loader | Formats supported | details | module |
|-----------------------|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
| EnvironmentVarsLoader | envVars | Loads Environment Variables from the EnvironmentConfigSource, it expects a list not a InputStream. By default, it splits the paths using a "_". You can also disable failOnErrors if you are receiving errors from the environment variables, as you can not always control what is present. By treating Errors as warnings it will not fail if it finds a configuration the parser doesn't understand. Instead it will ignore the specific config. | core |
| MapConfigLoader | mapConfig | Loads a user provided Map from the MapConfigSource, it expects a list not a InputStream. By default, it splits the paths using a "." and tokenizes arrays with a numeric index as "[0]". | core |
| PropertyLoader | properties, props, and systemProperties | Loads a standard property file from an InputStream. By default, it splits the paths using a "." and tokenizes arrays with a numeric index as "[0]". | core |
| JsonLoader | json | Leverages Jackson to load json files and convert them into a ConfigNode tree. | [`gestalt-json`](https://search.maven.org/search?q=gestalt-json) |
| TomlLoader | toml | Leverages Jackson to load toml files and convert them into a ConfigNode tree. | [`gestalt-toml`](https://search.maven.org/search?q=gestalt-toml) |
| YamlLoader | yml and yaml | Leverages Jackson to load yaml files and convert them into a ConfigNode tree. | [`gestalt-yaml`](https://search.maven.org/search?q=gestalt-yaml) |
| HoconLoader | config | Leverages com.typesafe:config to load hocon files, supports substitutions. | [`gestalt-hocon`](https://search.maven.org/search?q=gestalt-hocon) |

If you didn't manually add any ConfigLoaders as part of the GestaltBuilder, it will add the defaults. The GestaltBuilder uses the service loader to create instances of the Config loaders. It will configure them by passing in the GestaltConfig to applyConfig.
To register your own default ConfigLoaders add them to the builder, or add it to a file in META-INF\services\org.github.gestalt.config.loader.ConfigLoader and add the full path to your ConfigLoader

By default, Gestalt expects Environment Variables to be screaming snake case, but you can configure it to have a different case.

By registering a `EnvironmentVarsLoaderModuleConfig` with the `GestaltBuilder` you can customize the Environment Loader.

In this example it will expect double `__` as delimiter.
```java
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
.addSource(EnvironmentConfigSourceBuilder.builder().build())
.addModuleConfig(EnvironmentVarsLoaderModuleConfigBuilder
.builder()
.setLexer(new PathLexer("__"))
.build())
.build();

gestalt.loadConfigs();
```

You can also customize many of the Loaders such as the `YamlLoader`, `TomlLoader`, `JsonLoader` and `HoconLoader` by registering the Module Configs with the builder.

```java
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
.addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.yaml").build())
.addModuleConfig(YamlModuleConfigBuilder.builder()
.setObjectMapper(customObjectmapper)
.build())
.build();

gestalt.loadConfigs();
```
18 changes: 18 additions & 0 deletions docs/gestalt-static/docs/loading-configuration/config-sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
sidebar_position: 1
---

# Config Sources

Adding a ConfigSource to the builder is the minimum step needed to build the Gestalt Library.
You can add several ConfigSources to the builder and Gestalt, and they will be loaded in the order they are added. Where each new source will be merged with the existing source and where applicable overwrite the values of the previous sources. Each Config Source can be a diffrent format such as json, properties or Snake Case Env Vars, then internally they are converted into a common config tree.

```java
Gestalt gestalt = builder
.addSource(FileConfigSourceBuilder.builder().setFile(defaults).build())
.addSource(FileConfigSourceBuilder.builder().setFile(devFile).build())
.addSource(EnvironmentConfigSourceBuilder.builder().setPrefix("MY_APP_CONFIG").build())
.build();
```
In the above example we first load a file defaults, then load a file devFile and overwrite any defaults, then overwrite any values from the Environment Variables.
The priority will be Env Vars > devFile > defaults.
33 changes: 33 additions & 0 deletions docs/gestalt-static/docs/loading-configuration/config-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
sidebar_position: 2
---

# Config Tree
The config files are loaded and merged into a config tree. While loading into the config tree all node names and paths are converted to lower case and for environment variables we convert screaming snake case into dot notation. However, we do not convert other cases such as camel case into dot notation. So if your configs use a mix of dot notation and camel case, the nodes will not be merged. You can configure this conversion by providing your own `Sentence Lexer` in the `GestaltBuilder`. The config tree has a structure (sort of like json) where the root has one or more nodes or leafs. A node can have one or more nodes or leafs. A leaf can have a value but no further nodes or leafs. As we traverse the tree each node or leaf has a name and in combination it is called a path. A path can not have two leafs or both a node and a leaf at the same place. If this is detected Gestalt will throw an exception on loading with details on the path.

Valid:
```properties

db.connectionTimeout=6000
db.idleTimeout=600
db.maxLifetime=60000.0

http.pool.maxTotal=1000
http.pool.maxPerRoute=50
```

Invalid:
```properties

db.connectionTimeout=6000
db.idleTimeout=600
db=userTable #invalid the path db is both a node and a leaf.

http.pool.maxTotal=1000
http.pool.maxPerRoute=50
HTTP.pool.maxPerRoute=75 #invalid duplicate nodes at the same path.
```

All paths are converted to lower case as different sources have different naming conventions, Env Vars are typically Screaming Snake Case, properties are dot notation, json is camelCase. By normalizing them to lowercase it is easier to merge. However, we do not convert other cases such as camel case into dot notation. It is best to use a consistent case for your configurations.

This is configurable if you desire to keep case or split on different tokens.
Loading

0 comments on commit 4e908a2

Please sign in to comment.