All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.config.hierarchical-features.adoc Maven / Gradle / Ivy

///////////////////////////////////////////////////////////////////////////////

    Copyright (c) 2018, 2024 Oracle and/or its affiliates.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

///////////////////////////////////////////////////////////////////////////////

= Hierarchical Features
:description: Helidon hierarchical features
:keywords: helidon, config
:feature-name: Config
:rootdir: {docdir}/../..
:imagesdir: {rootdir}/images

include::{rootdir}/includes/se.adoc[]

== Contents

- <>
- <>
- <>
- <>
- <>
- <>
- <>


== Overview

The config system represents configuration as a tree in memory. Many developers
will choose to work directly with config values -- values from
the leaves in the tree -- accessing them by their keys. You can also navigate
explicitly among the nodes of the tree without using keys.
This section describes what the tree looks like and how you can traverse
it.

== Configuration Node Types
The config system represents configuration in memory using three types of nodes,
each a different interface
defined within the link:{config-javadoc-base-url}/io/helidon/config/spi/ConfigNode.html[`ConfigNode`] interface.

.`ConfigNode` Types
|===
|Type | Java Interface | Usage

|object |`ConfigNode.ObjectNode` |Represents complex structure (a subtree). Its child nodes can be of
any type.
|list |`ConfigNode.ListNode`|Represents a list of nodes. Its components can be of any type.
|value |`ConfigNode.ValueNode`| Represents a leaf node.
|===

A node of any type can have a `String` value.

Each config tree in memory will have an object node as its root with
child nodes as dictated by the source config data from which the config system
built the tree.

[NOTE]
.Missing Config Nodes
====
If your application attempts to access a non-existent node, for example using
[source,java]
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_1, indent=0]
----
the config system returns a `Config` node object with
type `MISSING`. The in-memory config tree contains nodes only of types `OBJECT`, `LIST`,
and `VALUE`.
====

== Configuration Key
Each config node (except the root) has a non-null key. Here is the formal
definition of what keys can be:
[source,abnf]
.The ABNF syntax of config key
----
config-key = *1( key-token *( "." key-token ) )
 key-token = *( unescaped / escaped )
 unescaped = %x00-2D / %x2F-7D / %x7F-10FFFF
           ; %x2E ('.') and %x7E ('~') are excluded from 'unescaped'
   escaped = "~" ( "0" / "1" )
           ; representing '~' and '.', respectively
----

[IMPORTANT]
=========
To emphasize, the dot character ("`.`") has special meaning as a name separator
in keys. To include a dot as a character in a key escape it as
"`~1`". To include a tilda escape it as "`~0`".
=========

== In-memory Representation of Configuration
The following example is in link:https://github.com/lightbend/config/blob/master/HOCON.md[HOCON]
(human-optimized config object notation) format.
The config system supports HOCON as an
xref:supported-formats.adoc#Config-ModuleHocon[extension module].

[source,hocon]
.HOCON `application.conf` file
----
app {
    greeting = "Hello"
    page-size = 20
    basic-range = [ -20, 20 ]
}
data {
    providers: [
        {
            name = "Provider1"
            class = "this.is.my.Provider1"
        },
        {
            name = "Provider2"
            class = "this.is.my.Provider2"
        }
    ]
}

----

The diagram below illustrates the in-memory tree for that configuration.

.Config Nodes structure of `application.conf` file
image::config/application_conf-nodes.png["Loaded Config Nodes structure",align="center"]

====
Notes

1. Each non-root node has a name which distinguishes it from other nodes with
the same parent. The interpretation of the name depends on the node type.
+
|===
|Node Type |Name

|object +
value |member name of the node within its parent
|list |element index of the node within the containing list
//|value |member name of the node within its parent
|===

2. Each node's key is the fully-qualified path using dotted names from the root to that node.
3. The root has an empty key, empty name, and no value.
====

The `Config` object exposes methods to return the
link:{config-javadoc-base-url}/io/helidon/config/Config.html#name--[`name`],
 link:{config-javadoc-base-url}/io/helidon/config/Config.html#key--[`key`], and
 link:{config-javadoc-base-url}/io/helidon/config/Config.html#type--[`type`] of the
 node.

== Access by Key [[accessByKey]]
For many applications, accessing configuration values by key will be the simplest approach.
If you write the code with a specific configuration structure in mind, your code can retrieve
the value from a specific configuration node very easily.

Your application can specify the entire navigation path as the key to a single
`get` invocation, using dotted
notation to separate the names of the nodes along the path. The code can
navigate one level at a time using chained `get` invocations, each specifying
one level of the path to the expected node. Or, you can mix the two styles.

All the following lines retrieve the same `Config` node.
[source,java]
.Equivalent Config Retrievals
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_2, indent=0]
----
<1> using a single key
<2> mixed style (composite key and single key)
<3> navigating one level with each `get` invocation

The `Config.get(key)` method always returns a `Config` object without throwing an
exception. If the specified key does not exist the method returns a `Config` node
of type `MISSING`. There are several ways your application can tell whether a given
config value exists.

|===
|Method |Usage

| `exists` |Returns `true` or `false`
| `ifExists` | Execute functional operations for present nodes
| `type` | Returns enum value for the `Config.Type`; `Config.Type.MISSING` if the node
represents a config value that _does not_ exist
| `as` | Returns the `ConfigValue` with the correct type that has all methods of `Optional`
    and a few additional ones - see link:{config-javadoc-base-url}/io/helidon/config/ConfigValue.html[`ConfigValue`] interface.
|===

The config system throws a `MissingValueException` if the application tries to
access the value of a missing node by invoking the `ConfigValue.get()` method.

== Access by General Navigation
Some applications might need to work with configuration without knowing its
structure or key names ahead of time, and such applications can use various
methods on the `Config` class to do this.

.General Config Node Methods
|===
|Method |Usage

|`asNodeList()` |Returns a ConfigValue>. For nodes of type `OBJECT` contains child nodes as a `List`.
|`hasValue()` |For any node reports if the node has a value. This can be true for
any node type except `MISSING`.
|`isLeaf()` |Reports whether the node has no child nodes. Leaf nodes have no children
and has a single value.
|`key()` |Returns the fully-qualified path of the node using dotted notation.
|`name()` |Returns the name of the node (the last part of the key).
|`asNode()` |Returns a `ConfigValue` wrapped around the node
| `traverse()` +
`traverse(Predicate)` | Returns a `Stream` as an iterative
deepening depth-first traversal of the subtree
|`type()` |Returns the `Type` enum value for the node: `OBJECT`, `LIST`, `VALUE`,
or `MISSING`
|===

[source,java]
.List names of child nodes of an _object_ node
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_3, indent=0]
----
<1> Get the ConfigValue with child `Config` instances.
<2> Map the node list to names using the Java Stream API (if present)
<3> Use an empty list if the "app" node does not exist
<4> Check that the list contains the expected child names: `basic-range`, `greeting` and `page-size`.

[source,java]
.List child nodes of a _list_ node
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_4, indent=0]
----
<1> Get child nodes of the `data.providers` _list_ node as a `List` of `Config` instances.
<2> Check that the list contains the expected child nodes with keys
`data.providers.0` and `data.providers.1`.

The `traverse()` method returns a stream of the nodes in the subtree that is rooted
at the current configuration node.
Depending on the structure of the loaded configuration the stream contains a mix of object, list or
 leaf value nodes.

[source,java]
.Traverse subtree below a _list_ node
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_5, indent=0]
----
<1> Visit the subtree rooted at the `data.providers` _list_ node.
<2> Prints out following list of nodes (type and key):

[source,text]
----
OBJECT 	data.providers.0
VALUE 	data.providers.0.name
VALUE 	data.providers.0.class
OBJECT 	data.providers.1
VALUE 	data.providers.1.name
VALUE 	data.providers.1.class
----

The optional `Predicate` argument to the `traverse` methods allows the
application to prune the traversal of a subtree at any point.

[source,java]
.Traverse _root_ (_object_) node, skipping the entire `data` subtree
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_6, indent=0]
----
<1> Visit all _root_ sub-nodes, excluding whole `data` tree structure but including
others.
<2> Prints out following list of nodes (type and key):

[source,listing]
----
OBJECT 	app
VALUE 	app.page-size
VALUE 	app.greeting
LIST 	app.basic-range
VALUE 	app.basic-range.0
VALUE 	app.basic-range.1
----

== Detaching a Config Subtree
Sometimes it can be convenient to write part of your application to deal with
configuration without it knowing if or where the relevant configuration is plugged into
a larger config tree.

For example, the xref:introduction.adoc#create-simple-config-props[`application.properties`]
from the introduction section contains several settings prefixed with `web` such as `web.page-size`.
Perhaps in another config source the same information might be stored as
`server.web.page-size`:
[source,java]
.Alternate Structure for Web Config
server.web.page-size: 40
server.web.debug = true
server.web.ratio = 1.4

You might want to write the web portion of your app to work with a config subtree
with keys that are independent of the subtree's position in a larger tree. This
would allow you to reuse the web portion of your application without change, regardless
of which structure a config source used.

One easy way to do this is to _detach_ a subtree from a larger config tree. When
your application invokes the
link:{config-javadoc-base-url}/io/helidon/config/Config.html#detach--[`Config.detach`] method it gets back
a _copy_ of the config node but with no parent. The copy and the original node both
point to the same objects for their child nodes (if any). The original node is
unchanged.

[source,java]
.Detaching a Subtree
----
include::{sourcedir}/se/config/HierarchicalFeaturesSnippets.java[tag=snippet_7, indent=0]
----
<1> Navigation depends on knowing the full structure of the config
and so is different for the two cases.
<2> Detaching so the `web` node is the root can use the same key
regardless of where the config subtree came from.





© 2015 - 2025 Weber Informatics LLC | Privacy Policy