-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tree node support. #711
base: main
Are you sure you want to change the base?
Tree node support. #711
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,9 @@ | |
import java.lang.reflect.Array; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.eclipse.microprofile.config.spi.Converter; | ||
|
@@ -129,7 +131,9 @@ public interface Config { | |
* if the property cannot be converted to the specified type | ||
* @throws java.util.NoSuchElementException | ||
* if the property is not defined or is defined as an empty string or the converter returns {@code null} | ||
* @deprecated use {@link #get(String)} and {@link #as(Class)} instead | ||
*/ | ||
@Deprecated | ||
<T> T getValue(String propertyName, Class<T> propertyType); | ||
|
||
/** | ||
|
@@ -148,7 +152,9 @@ public interface Config { | |
* @param propertyName | ||
* The configuration property name | ||
* @return the resolved property value as a {@link ConfigValue} | ||
* @deprecated use {@link #get(String)} and methods on the config node | ||
*/ | ||
@Deprecated | ||
ConfigValue getConfigValue(String propertyName); | ||
|
||
/** | ||
|
@@ -171,7 +177,9 @@ public interface Config { | |
* @throws java.util.NoSuchElementException | ||
* if the property isn't present in the configuration or is defined as an empty string or the converter | ||
* returns {@code null} | ||
* @deprecated use {@link #get(String)} and #asList(Class) | ||
*/ | ||
@Deprecated | ||
default <T> List<T> getValues(String propertyName, Class<T> propertyType) { | ||
@SuppressWarnings("unchecked") | ||
Class<T[]> arrayType = (Class<T[]>) Array.newInstance(propertyType, 0).getClass(); | ||
|
@@ -198,7 +206,9 @@ default <T> List<T> getValues(String propertyName, Class<T> propertyType) { | |
* | ||
* @throws IllegalArgumentException | ||
* if the property cannot be converted to the specified type | ||
* @deprecated use {@link #get(String)} and {@link #as(Class)} instead | ||
*/ | ||
@Deprecated | ||
<T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType); | ||
|
||
/** | ||
|
@@ -219,7 +229,9 @@ default <T> List<T> getValues(String propertyName, Class<T> propertyType) { | |
* | ||
* @throws java.lang.IllegalArgumentException | ||
* if the property cannot be converted to the specified type | ||
* @deprecated use {@link #get(String)} and #asList(Class) | ||
*/ | ||
@Deprecated | ||
default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> propertyType) { | ||
@SuppressWarnings("unchecked") | ||
Class<T[]> arrayType = (Class<T[]>) Array.newInstance(propertyType, 0).getClass(); | ||
|
@@ -257,7 +269,7 @@ default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> pr | |
* The returned sources will be sorted by descending ordinal value and name, which can be iterated in a thread-safe | ||
* manner. The {@link java.lang.Iterable Iterable} contains a fixed number of {@linkplain ConfigSource configuration | ||
* sources}, determined at application start time, and the config sources themselves may be static or dynamic. | ||
* | ||
* | ||
* @return the configuration sources | ||
*/ | ||
Iterable<ConfigSource> getConfigSources(); | ||
|
@@ -291,4 +303,181 @@ default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> pr | |
* If the current provider does not support unwrapping to the given type | ||
*/ | ||
<T> T unwrap(Class<T> type); | ||
|
||
/* | ||
* Tree handling methods | ||
*/ | ||
|
||
/** | ||
* Fully qualified key of this config node (such as {@code server.port}). | ||
* Returns an empty String for root config. | ||
* | ||
* @return key of this config | ||
*/ | ||
String key(); | ||
|
||
/** | ||
* Name of this node - the last element of a fully qualified key. | ||
* <p> | ||
* For example for key {@code server.port} this method would return {@code port}. | ||
* | ||
* @return name of this node | ||
*/ | ||
String name(); | ||
|
||
/** | ||
* Single sub-node for the specified name. | ||
* For example if requested for key {@code server}, this method would return a config | ||
* representing the {@code server} node, which would have for example a child {@code port}. | ||
* | ||
* @param name name of the nested node to retrieve | ||
* @return sub node, never null | ||
*/ | ||
Config get(String name); | ||
|
||
/** | ||
* A detached node removes prefixes of each sub-node of the current node. | ||
* <p> | ||
* Let's assume this node is {@code server} and contains {@code host} and {@code port}. | ||
* The method {@link #key()} for {@code host} would return {@code server.host}. | ||
* If we call a method {@link #key()} on a detached instance, it would return just {@code host}. | ||
* | ||
* @return a detached config instance | ||
*/ | ||
Config detach(); | ||
|
||
/** | ||
* Type of this node. | ||
* | ||
* @return type | ||
*/ | ||
Type type(); | ||
|
||
/** | ||
* Returns {@code true} if the node exists, whether an object, a list, or a | ||
* value node. | ||
* | ||
* @return {@code true} if the node exists | ||
*/ | ||
default boolean exists() { | ||
return type() != Type.MISSING; | ||
} | ||
|
||
/** | ||
* Returns {@code true} if this configuration node has a direct value. | ||
* <p> | ||
* Example (using properties files) for each node type: | ||
* <p> | ||
* {@link Type#OBJECT} - the node {@code server.tls} is an object node with direct value: | ||
* <pre> | ||
* # this is not recommended, yet it is possible: | ||
* server.tls=true | ||
* server.tls.version=1.2 | ||
* server.tls.keystore=abc.p12 | ||
* </pre> | ||
* <p> | ||
* {@link Type#LIST} - the node {@code server.ports} is a list node with direct value: | ||
* TODO this may actually not be supported by the spec, as it can only be achieved through properties | ||
* <pre> | ||
* # this is not recommended, yet it is possible: | ||
* server.ports=8080 | ||
* server.ports.0=8081 | ||
* server.ports.1=8082 | ||
* </pre> | ||
* <p> | ||
* {@link Type#LEAF} - the nodes {@code server.port} and {@code server.host} are values | ||
* <pre> | ||
* server.port=8080 | ||
* server.host=localhost | ||
* </pre> | ||
* | ||
* @return {@code true} if the node has direct value, {@code false} otherwise. | ||
*/ | ||
boolean hasValue(); | ||
|
||
/** | ||
* Typed value created using a converter function. | ||
* The converter is called only if this config node exists. | ||
* | ||
* @param converter to create an instance from config node | ||
* @param <T> type of the object | ||
* @return converted value of this node, or an empty optional if this node does not exist | ||
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type | ||
*/ | ||
<T> Optional<T> as(Function<Config, T> converter); | ||
|
||
/** | ||
* Typed value created using a configured converter. | ||
* | ||
* @param type class to convert to | ||
* @param <T> type of the object | ||
* @return converted value of this node, or empty optional if this node does not exist | ||
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type | ||
*/ | ||
<T> Optional<T> as(Class<T> type); | ||
|
||
/** | ||
* Map to a list of typed values. | ||
* This method is only available if the current node is a {@link org.eclipse.microprofile.config.Config.Type#LIST} | ||
* or if it has a direct value. In case a direct value of type String exists, it expects comma separated elements. | ||
* | ||
* @param type class to convert each element to | ||
* @param <T> type of the object | ||
* @return list of typed values | ||
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type | ||
*/ | ||
<T> Optional<List<T>> asList(Class<T> type); | ||
|
||
/** | ||
* Contains the (known) config values as a map of key->value pairs. | ||
* | ||
* @return map of sub keys of this config node, or empty if this node does not exist | ||
*/ | ||
Optional<Map<String, String>> asMap(); | ||
|
||
/** | ||
* A list of child nodes. | ||
* In case this node is {@link org.eclipse.microprofile.config.Config.Type#LIST} returns the list in the correct order. | ||
* In case this node is {@link org.eclipse.microprofile.config.Config.Type#OBJECT} returns all direct child nodes | ||
* (in an unknown order) | ||
* | ||
* @return list of child nodes, or empty if this node does not exist | ||
*/ | ||
Optional<List<Config>> asNodeList(); | ||
|
||
/* | ||
* Shortcut helper methods | ||
*/ | ||
default Optional<String> asString() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only callable if the hasValue() returns true. I am wondering whether we layer this further and move some of the methods to LeafNode. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is practically not true - there can be a converter from config node to string that could work even for list nodes (e.g. converting a list to a comma separated string) |
||
return as(String.class); | ||
} | ||
|
||
default Optional<Integer> asInt() { | ||
return as(Integer.class); | ||
} | ||
|
||
/** | ||
* Config node type. | ||
*/ | ||
enum Type { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because these are different scopes and different meanings. |
||
/** | ||
* Object node with named members and a possible direct value. | ||
*/ | ||
OBJECT, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the Type structure, but not the enum value names. I think it is pretty much: Parent, List, Leaf, Empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parent is not a very good name, as it can have a value (it is basically a tree node that may contain child nodes and a value). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed value to |
||
/** | ||
* List node with a list of indexed parameters. | ||
* Note that a list node can also be accessed as an object node - child elements | ||
* have indexed keys starting from {@code 0}. | ||
* List nodes may also have a direct value. | ||
*/ | ||
LIST, | ||
/** | ||
* Value node is a leaf node - it does not have any child nodes, only direct value. | ||
*/ | ||
LEAF, | ||
/** | ||
* Node is missing, it will return only empty values. | ||
*/ | ||
MISSING | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright (c) 2017, 2021 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. | ||
*/ | ||
|
||
package org.eclipse.microprofile.config.spi; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
/** | ||
* Marker interface identifying a config node implementation. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't "marker interface" usually refer to interfaces without methods? |
||
*/ | ||
public interface ConfigNode { | ||
/** | ||
* Key of this config node. | ||
* | ||
* @return key of this node | ||
*/ | ||
String key(); | ||
|
||
/** | ||
* Get the type of this node. | ||
* | ||
* @return NodeType this node represents | ||
*/ | ||
NodeType nodeType(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some time, in the far, far future, the spec will require a minimum of Java 17. Should we prepare for this day by declaring the type enum to be a temporary workaround until this interface can be |
||
|
||
/** | ||
* Get the direct value of this config node. Any node type can have a direct value. | ||
* | ||
* @return a value if present, {@code empty} otherwise | ||
*/ | ||
Optional<String> value(); | ||
|
||
/** | ||
* Each node may have a direct value, and in addition may be an object node or a list node. | ||
* This method returns true for any node with direct value. | ||
* | ||
* @return true if this node contains a value | ||
*/ | ||
default boolean hasValue() { | ||
return value().isPresent(); | ||
} | ||
|
||
/** | ||
* Config source that provided this value. | ||
* | ||
* @return config source | ||
*/ | ||
ConfigSource configSource(); | ||
|
||
/** | ||
* The actual priority of the config source that provided this value. | ||
* | ||
* @return config source priority | ||
* @see #configSource() | ||
*/ | ||
Integer sourcePriority(); | ||
|
||
/** | ||
* Base types of config nodes. | ||
*/ | ||
enum NodeType { | ||
/** | ||
* An object (complex structure), optionally may have a value. | ||
*/ | ||
OBJECT, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still find this confusing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is paritally taken from JSON, where
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't make to the correlation with Json before you mentioned. I think if we prefix with Config, it might be less confusing, but refering to ConfigNode.OBJECT as ConfigNode.ConfigOBJECT is mouthful. It is not ideal, but I have not got a better name as yet:(. Maybe someone else has. |
||
/** | ||
* A list of values, optionally may have a value. | ||
*/ | ||
LIST, | ||
/** | ||
* Only has value. | ||
*/ | ||
VALUE | ||
} | ||
|
||
/** | ||
* Single string-based configuration value. | ||
*/ | ||
interface ValueNode extends ConfigNode { | ||
@Override | ||
default NodeType nodeType() { | ||
return NodeType.VALUE; | ||
} | ||
|
||
/** | ||
* Get the value of this value node. | ||
* @return string with the node value | ||
*/ | ||
String get(); | ||
} | ||
|
||
/** | ||
* ConfigNode-based list of configuration values. | ||
* <p> | ||
* List may contains instances of | ||
* {@link ValueNode}, {@link ListNode} as well as {@link ObjectNode}. | ||
*/ | ||
interface ListNode extends ConfigNode, List<ConfigNode> { | ||
@Override | ||
default NodeType nodeType() { | ||
return NodeType.LIST; | ||
} | ||
} | ||
|
||
/** | ||
* Configuration node representing a hierarchical structure. | ||
* <p> | ||
* In the map exposed by this interface, the map keys are {@code String}s | ||
* containing the fully-qualified dotted names of the config keys and the | ||
* map values are the corresponding {@link ValueNode} or {@link ListNode} | ||
* instances. The map never contains {@link ObjectNode} values because the | ||
* {@link ObjectNode} is implemented as a flat map. | ||
*/ | ||
interface ObjectNode extends ConfigNode, Map<String, ConfigNode> { | ||
@Override | ||
default NodeType nodeType() { | ||
return NodeType.OBJECT; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about the use case here. When do you need to do this? Is this purly about shorter the key string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is to create a config that "cuts" the prefix from all of its nodes. Use case is when you need to configure a component that expects a map (such as JSON-B, CDI).
If you would not have such a method, you would need to create a map, and then rebuild the map by doing a substring on each key