Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

tomas-langer
Copy link

@tomas-langer tomas-langer commented Aug 26, 2021

This PR shows how tree structure could be supported using MP config.
I have updated the proposal with a bit simplified version.

API changes

Config now has a method get(String name) to obtain a child node of the current node.
It also has several typed methods to get the current node as a typed value

SPI changes

Config source can now provide the full tree instead of a single node
Converter can convert from a node (object node, list node, value node) to a type

This PR was created as a result of discussion on Jakarta Config meeting, so we have a vehicle to discuss tree structure in configuration.

Some decisions that I made and explanation:

  • there are two new methods (isMutable and changeListener) on ConfigSource. Unfortunately the tree structure is connected with mutability, as if a source provides the full tree, it is no longer queried for each node separately, and so the existing approach of asking each source in sequence (and thus supporting some kind of "maybe mutability") is no longer possible. The new approach is allowing a config source to declare its support for mutations, and an event support to notify configuration of such a change. The configuration may or may not be interested in changes - if not, method changeListener is not called...
  • instead of creating three interfaces, I have added a method getType() and default methods to the config source that support each type. This is for backward compatibility (and also to make it easy for config source implementation - you just need to know about a single interface). This is a bit more complicated for config implementation, as it needs to check for source type, and then call the correct method to get values

@eclipse-microprofile-bot
Copy link
Contributor

Can one of the admins verify this patch?

/*
* Shortcut helper methods
*/
default Optional<String> asString() {
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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)

/**
* Object node with named members and a possible direct value.
*/
OBJECT,
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Author

Choose a reason for hiding this comment

The 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).
Leaf is OK. Empty vs. Missing - I think missing is more spot on (as this config node is missing from configuration). Empty may mean something different (e.g. the node exists, but is empty).
The values should still be all upper case (best practice for naming enum values)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed value to LEAF

*/
enum Type {
/**
* This is for backward compatibility - request each property separately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be FLAT to state all properties are visible with a flat structure?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming could be anything, I just wanted to have an option that is backward compatible. If FLAT sounds better, no problem.

@@ -150,4 +152,18 @@
* if the given value was {@code null}
*/
T convert(String value) throws IllegalArgumentException, NullPointerException;

/**
* Convert the given config node (may be an object, list or value) to the specified type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this config node is a node instead of leaf, I assume an IllegalArugumentException should be thrown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A converter may be capable of converting object nodes or list nodes as well. If the converter converts to a POJO for example, accepting an Object node is absolutely fine

*
* @param changeRunnable runnable to call when this config source changes
*/
default void changeListener(Runnable changeRunnable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think onChange is a better name.

*
* @return node content or empty optional if the underlying config source does not exist
*/
default Optional<ConfigNode> load() {
Copy link
Member

@Emily-Jiang Emily-Jiang Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove this method if we update the following method load(String key) to state that if the key is null, it will load the whole tree. Otherwise, load the tree with the specified root. I would change the signature load(String root) while null means the whole root.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am against using null as a valid parameter value (anywhere, anytime).
I can see two options here:

  1. use empty string instead (as the root node does not have a name, so the key could be an empty string)
  2. use a dedicated Key class that would have the concept of a root and use that

/**
* An object (complex structure), optionally may have a value.
*/
OBJECT,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still find this confusing. LIST and VALUE are all OBJECT. How about STRUCTURE or COMPLEX to differentiate them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is paritally taken from JSON, where JsonObject is the specific type (something that contains other nodes) and JsonStructure is the superset.
I am using List instead of Array, as I don't like arrays much in general. But otherwise this copies JSON-P naming

JsonObject -> OBJECT
JsonArray -> LIST (because collections are better than arrays ;) )
JsonValue -> VALUE

Copy link
Member

Choose a reason for hiding this comment

The 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.

/**
* Config node type.
*/
enum Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have NodeType under ConfigNode and this Type again for config node type. Can we list one out and refer to it in both Config and ConfigNode?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because these are different scopes and different meanings.
One is intended for SPI (ConfigNode.Type) that supports just three types.
The other is for API (Config.Type) that supports also MISSING.
User's do not see ConfigNode unless they implement ConfigSource; SPI does not use Config.
So separation of concerns.

*
* @return a detached config instance
*/
Config detach();
Copy link
Member

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?

Copy link
Author

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

import java.util.Optional;

/**
* Marker interface identifying a config node implementation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't "marker interface" usually refer to interfaces without methods?

*
* @return NodeType this node represents
*/
NodeType nodeType();

Choose a reason for hiding this comment

The 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 sealed in a future version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants