You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/03/24 08:51:19 UTC

[ignite-3] branch main updated: IGNITE-14371 JSON representation for configuration & partial code for JSON update requests parsing. (#72)

This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 2e6ea88  IGNITE-14371 JSON representation for configuration & partial code for JSON update requests parsing. (#72)
2e6ea88 is described below

commit 2e6ea8850509a59a686abac44f0be2c5918cd3f8
Author: ibessonov <be...@gmail.com>
AuthorDate: Wed Mar 24 11:51:14 2021 +0300

    IGNITE-14371 JSON representation for configuration & partial code for JSON update requests parsing. (#72)
---
 .../ignite/configuration/ConfigurationChanger.java |  10 +
 .../configuration/ConfigurationRegistry.java       |  42 ++++
 .../apache/ignite/configuration/Configurator.java  |  28 ---
 .../configuration/annotation/ConfigValue.java      |   4 -
 .../ignite/configuration/internal/SuperRoot.java   |   6 +
 .../internal/util/ConfigurationUtil.java           |  17 +-
 .../configuration/tree/ConfigurationVisitor.java   |   2 +-
 .../ignite/configuration/tree/NamedListNode.java   |  12 +-
 modules/rest/pom.xml                               |   1 +
 .../java/org/apache/ignite/rest/RestModule.java    |  31 +--
 .../configuration/RestConfigurationSchema.java     |  15 +-
 .../rest/presentation/json/JsonConverter.java      | 236 ++++++++++++++++++++-
 .../rest/presentation/json/JsonPresentation.java   |  14 +-
 .../rest/presentation/json/JsonConverterTest.java  | 211 ++++++++++++++++++
 .../json/TestConfigurationStorage.java             |  63 ++++++
 .../ignite/configuration/ConfigurationModule.java  |   2 +-
 16 files changed, 624 insertions(+), 70 deletions(-)

diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
index 67196ee..7990151 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
@@ -217,6 +217,16 @@ public class ConfigurationChanger {
     }
 
     /** */
+    public SuperRoot mergedSuperRoot() {
+        SuperRoot mergedSuperRoot = new SuperRoot(rootKeys);
+
+        for (StorageRoots storageRoots : storagesRootsMap.values())
+            mergedSuperRoot.append(storageRoots.roots);
+
+        return mergedSuperRoot;
+    }
+
+    /** */
     private CompletableFuture<Void> change(SuperRoot changes, Class<? extends ConfigurationStorage> storageType) {
         ConfigurationStorage storage = storageInstances.get(storageType);
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
index 8d4bcc5..5770249 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
@@ -17,9 +17,12 @@
 
 package org.apache.ignite.configuration;
 
+import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
 import javax.validation.constraints.Max;
@@ -27,10 +30,16 @@ import javax.validation.constraints.Min;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.internal.DynamicConfiguration;
 import org.apache.ignite.configuration.internal.RootKeyImpl;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
+import org.apache.ignite.configuration.internal.util.KeyNotFoundException;
 import org.apache.ignite.configuration.internal.validation.MaxValidator;
 import org.apache.ignite.configuration.internal.validation.MinValidator;
 import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
 import org.apache.ignite.configuration.validation.Validator;
 
 /** */
@@ -72,6 +81,39 @@ public class ConfigurationRegistry {
     }
 
     /**
+     * Convert configuration subtree into a user-defined representation.
+     *
+     * @param path Path to configuration subtree. Can be empty, can't be {@code null}.
+     * @param visitor Visitor that will be applied to the subtree and build the representation.
+     * @param <T> Type of the representation.
+     * @return User-defined representation constructed by {@code visitor}.
+     * @throws IllegalArgumentException If {@code path} is not found in current configuration.
+     */
+    public <T> T represent(List<String> path, ConfigurationVisitor<T> visitor) throws IllegalArgumentException {
+        SuperRoot mergedSuperRoot = changer.mergedSuperRoot();
+
+        Object node;
+        try {
+            node = ConfigurationUtil.find(path, mergedSuperRoot);
+        }
+        catch (KeyNotFoundException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+
+        if (node instanceof TraversableTreeNode)
+            return ((TraversableTreeNode)node).accept(null, visitor);
+
+        assert node == null || node instanceof Serializable;
+
+        return visitor.visitLeafNode(null, (Serializable)node);
+    }
+
+    /** */
+    public CompletableFuture<?> change(List<String> path, ConfigurationSource changesSource) {
+        throw new UnsupportedOperationException("IGNITE-14372 Not implemented yet.");
+    }
+
+    /**
      * Method to instantiate a new {@link RootKey} for your configuration root. Invoked in generated code only.
      * Does not register this root anywhere, used for static object initialization only.
      *
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
deleted file mode 100644
index 05c1ebe..0000000
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.ignite.configuration;
-
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-
-/**
- * Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions
- * on configuration such as initialized, change and view.
- * @param <T> Type of configuration root.
- */
-public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
index b593674..e4fbc42 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
@@ -40,8 +40,4 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
 @Retention(SOURCE)
 @Documented
 public @interface ConfigValue {
-    /**
-     * @return The name of the configuration.
-     */
-    String value() default "";
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
index a83852b..326c3cd 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
@@ -63,6 +63,12 @@ public final class SuperRoot extends InnerNode {
     }
 
     /** */
+    public void append(SuperRoot otherRoot) {
+        //TODO IGNITE-14372 Revisit API of the super root.
+        roots.putAll(otherRoot.roots);
+    }
+
+    /** */
     public InnerNode getRoot(RootKey<?, ?> rootKey) {
         return roots.get(rootKey.key());
     }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
index 53fd2f2..e92659c 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
@@ -255,8 +255,16 @@ public class ConfigurationUtil {
 
                     assert val == null || val instanceof Map || val instanceof Serializable;
 
-                    if (val == null)
-                        node.construct(key, null);
+                    if (val == null) {
+                        if (node instanceof NamedListNode) {
+                            // Given that this particular method is applied to modify existing trees rather than
+                            // creating new trees, a "hack" is required in this place. "construct" is designed to create
+                            // "change" objects, thus it would just nullify named list element instead of deleting it.
+                            ((NamedListNode<?>)node).forceDelete(key);
+                        }
+                        else
+                            node.construct(key, null);
+                    }
                     else if (val instanceof Map)
                         node.construct(key, new InnerConfigurationSource((Map<String, ?>)val));
                     else {
@@ -595,7 +603,10 @@ public class ConfigurationUtil {
             for (String key : srcNode.namedListKeys()) {
                 InnerNode node = srcNode.get(key);
 
-                dstNode.construct(key, node == null ? null : new PatchInnerConfigurationSource(node));
+                if (node == null)
+                    ((NamedListNode<?>)dstNode).forceDelete(key); // Same as in fillFromPrefixMap.
+                else
+                    dstNode.construct(key, new PatchInnerConfigurationSource(node));
             }
         }
     }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java
index bf2e697..646c649 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java
@@ -39,7 +39,7 @@ public interface ConfigurationVisitor<T> {
      * @param key Name of the node retrieved from its holder object.
      * @param node Inner configuration node.
      */
-    default T visitInnerNode(String key, InnerNode node) {
+    default T visitInnerNode(String key, InnerNode node) { //TODO IGNITE-14372 Pass interface, not implementation.
         return null;
     }
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
index 530544c..718e0b0 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
@@ -95,6 +95,16 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
         return this;
     }
 
+    /**
+     * Deletes named list element.
+     *
+     * @param key Element's key.
+     */
+    public void forceDelete(String key) {
+        map.remove(key);
+    }
+
+    /** {@inheritDoc} */
     @Override public NamedListChange<N, N> create(String key, Consumer<N> valConsumer) {
         Objects.requireNonNull(valConsumer, "valConsumer");
 
@@ -111,7 +121,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
     /** {@inheritDoc} */
     @Override public void construct(String key, ConfigurationSource src) {
         if (src == null)
-            map.remove(key);
+            map.put(key, null);
         else {
             N val = map.get(key);
 
diff --git a/modules/rest/pom.xml b/modules/rest/pom.xml
index 8cf8376..3af59e5 100644
--- a/modules/rest/pom.xml
+++ b/modules/rest/pom.xml
@@ -83,6 +83,7 @@
             <artifactId>netty-codec-http</artifactId>
         </dependency>
 
+        <!-- Test dependencies. -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
index 6124abd..a434eaa 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.rest;
 
+import com.google.gson.JsonElement;
 import com.google.gson.JsonSyntaxException;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.Channel;
@@ -29,13 +30,16 @@ import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 import java.io.Reader;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import org.apache.ignite.configuration.ConfigurationRegistry;
+import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.rest.configuration.RestConfiguration;
+import org.apache.ignite.rest.configuration.RestView;
 import org.apache.ignite.rest.netty.RestApiInitializer;
 import org.apache.ignite.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.rest.presentation.json.JsonConverter;
 import org.apache.ignite.rest.presentation.json.JsonPresentation;
 import org.apache.ignite.rest.routes.Router;
 import org.slf4j.Logger;
@@ -50,7 +54,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
  */
 public class RestModule {
     /** */
-    private static final int DFLT_PORT = 10300;
+    public static final int DFLT_PORT = 10300;
 
     /** */
     private static final String CONF_URL = "/management/v1/configuration/";
@@ -76,7 +80,7 @@ public class RestModule {
     public void prepareStart(ConfigurationRegistry sysCfg, Reader moduleConfReader) {
         sysConf = sysCfg;
 
-        presentation = new JsonPresentation(Collections.emptyMap());
+        presentation = new JsonPresentation();
 
 //        FormatConverter converter = new JsonConverter();
 //
@@ -98,7 +102,11 @@ public class RestModule {
             .get(CONF_URL + ":" + PATH_PARAM, (req, resp) -> {
                 String cfgPath = req.queryParams().get(PATH_PARAM);
                 try {
-                    resp.json(presentation.representByPath(cfgPath));
+                    List<String> path = ConfigurationUtil.split(cfgPath);
+
+                    JsonElement json = sysConf.represent(path, JsonConverter.jsonVisitor());
+
+                    resp.json(json);
                 }
                 catch (IllegalArgumentException pathE) {
                     ErrorResult eRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage());
@@ -149,14 +157,16 @@ public class RestModule {
 
     /** */
     private void startRestEndpoint(Router router) throws InterruptedException {
-        Integer desiredPort = sysConf.getConfiguration(RestConfiguration.KEY).port().value();
-        Integer portRange = sysConf.getConfiguration(RestConfiguration.KEY).portRange().value();
+        RestView restConfigurationView = sysConf.getConfiguration(RestConfiguration.KEY).value();
+
+        int desiredPort = restConfigurationView.port();
+        int portRange = restConfigurationView.portRange();
 
         int port = 0;
 
-        if (portRange == null || portRange == 0) {
+        if (portRange == 0) {
             try {
-                port = (desiredPort != null ? desiredPort : DFLT_PORT);
+                port = desiredPort;
             }
             catch (RuntimeException e) {
                 log.warn("Failed to start REST endpoint: ", e);
@@ -209,9 +219,4 @@ public class RestModule {
             workerGrp.shutdownGracefully();
         }
     }
-
-    /** */
-    public String configRootKey() {
-        return "rest";
-    }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
index 5cd7bc1..0003eae 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
@@ -17,19 +17,26 @@
 
 package org.apache.ignite.rest.configuration;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.Value;
 
+import static org.apache.ignite.rest.RestModule.DFLT_PORT;
+
 /**
  * Configuration schema for REST endpoint subtree.
  */
 @ConfigurationRoot(rootName = "rest")
 public class RestConfigurationSchema {
     /** */
-    @Value
-    public int port;
+    @Min(1024)
+    @Max(0xFFFF)
+    @Value(hasDefault = true)
+    public final int port = DFLT_PORT;
 
     /** */
-    @Value
-    public int portRange;
+    @Min(0)
+    @Value(hasDefault = true)
+    public final int portRange = 0;
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java
index 555e406..abe070f 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java
@@ -17,13 +17,30 @@
 
 package org.apache.ignite.rest.presentation.json;
 
-import java.io.Reader;
-
 import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.io.Reader;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Optional;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
 import org.apache.ignite.rest.presentation.FormatConverter;
+import org.jetbrains.annotations.NotNull;
 
 /** */
 public class JsonConverter implements FormatConverter {
@@ -35,6 +52,221 @@ public class JsonConverter implements FormatConverter {
         return gson.toJson(obj);
     }
 
+    /** */
+    public static ConfigurationVisitor<JsonElement> jsonVisitor() {
+        return new ConfigurationVisitor<JsonElement>() {
+            /** */
+            private final Deque<JsonObject> jsonObjectsStack = new ArrayDeque<>();
+
+            /** {@inheritDoc} */
+            @Override public JsonElement visitLeafNode(String key, Serializable val) {
+                JsonElement jsonLeaf = toJsonLeaf(val);
+
+                addToParent(key, jsonLeaf);
+
+                return jsonLeaf;
+            }
+
+            /** {@inheritDoc} */
+            @Override public JsonElement visitInnerNode(String key, InnerNode node) {
+                JsonObject innerJsonNode = new JsonObject();
+
+                jsonObjectsStack.push(innerJsonNode);
+
+                node.traverseChildren(this);
+
+                jsonObjectsStack.pop();
+
+                addToParent(key, innerJsonNode);
+
+                return innerJsonNode;
+            }
+
+            /** {@inheritDoc} */
+            @Override public <N extends InnerNode> JsonElement visitNamedListNode(String key, NamedListNode<N> node) {
+                JsonObject namedListJsonNode = new JsonObject();
+
+                jsonObjectsStack.push(namedListJsonNode);
+
+                for (String subkey : node.namedListKeys())
+                    node.get(subkey).accept(subkey, this);
+
+                jsonObjectsStack.pop();
+
+                addToParent(key, namedListJsonNode);
+
+                return namedListJsonNode;
+            }
+
+            /** */
+            @NotNull private JsonElement toJsonLeaf(Serializable val) {
+                if (val == null)
+                    return JsonNull.INSTANCE;
+
+                Class<? extends Serializable> valClass = val.getClass();
+
+                if (!valClass.isArray())
+                    return toJsonPrimitive(val);
+
+                JsonArray jsonArray = new JsonArray();
+
+                for (int i = 0; i < Array.getLength(val); i++)
+                    jsonArray.add(toJsonPrimitive(Array.get(val, i)));
+
+                return jsonArray;
+            }
+
+            /** */
+            @NotNull private JsonElement toJsonPrimitive(Object val) {
+                if (val == null)
+                    return JsonNull.INSTANCE;
+
+                if (val instanceof Boolean)
+                    return new JsonPrimitive((Boolean)val);
+
+                if (val instanceof String)
+                    return new JsonPrimitive((String)val);
+
+                if (val instanceof Number)
+                    return new JsonPrimitive((Number)val);
+
+                assert false : val;
+
+                throw new IllegalArgumentException(val.getClass().getCanonicalName());
+            }
+
+            /**
+             * Add subelement to the paretn JSON object if it exists.
+             *
+             * @param key Key for the passed JSON element.
+             * @param jsonElement JSON element to add to the parent.
+             */
+            private void addToParent(String key, JsonElement jsonElement) {
+                if (!jsonObjectsStack.isEmpty())
+                    jsonObjectsStack.peek().add(key, jsonElement);
+            }
+        };
+    }
+
+    /** */
+    public static ConfigurationSource jsonSource(JsonElement jsonElement) {
+        //TODO IGNITE-14372 Finish this implementation.
+        return null;
+    }
+
+    private static class JsonObjectConfigurationSource implements ConfigurationSource {
+        /** Shared. */
+        private final List<String> path;
+
+        /** */
+        private final JsonObject jsonObject;
+
+        private JsonObjectConfigurationSource(List<String> path, JsonObject jsonObject) {
+            this.path = path;
+            this.jsonObject = jsonObject;
+        }
+
+        @Override public <T> T unwrap(Class<T> clazz) {
+            throw new IllegalArgumentException(""); //TODO IGNITE-14372 Implement.
+        }
+
+        @Override public void descend(ConstructableTreeNode node) {
+            for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+                String key = entry.getKey();
+
+                path.add(key);
+
+                JsonElement jsonElement = entry.getValue();
+
+                try {
+                    if (jsonElement.isJsonArray() || jsonElement.isJsonPrimitive())
+                        node.construct(key, new JsonPrimitiveConfigurationSource(path, jsonElement));
+                    else if (jsonElement.isJsonNull()) {
+                        node.construct(key, null);
+                    }
+                    else {
+                        assert jsonElement.isJsonObject();
+
+                        List<String> path = new ArrayList<>(this.path.size() + 1);
+                        path.addAll(this.path);
+                        path.add(key);
+
+                        node.construct(key, new JsonObjectConfigurationSource(path, jsonElement.getAsJsonObject()));
+                    }
+                }
+                catch (NoSuchElementException e) {
+                    throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+                }
+
+                path.remove(path.size() - 1);
+            }
+        }
+    }
+
+    private static class JsonPrimitiveConfigurationSource implements ConfigurationSource {
+        private final List<String> path;
+
+        private final JsonElement jsonLeaf;
+
+        private JsonPrimitiveConfigurationSource(List<String> path, JsonElement jsonLeaf) {
+            this.path = path;
+            this.jsonLeaf = jsonLeaf;
+        }
+
+        @Override public <T> T unwrap(Class<T> clazz) {
+            if (clazz.isArray() != jsonLeaf.isJsonArray())
+                throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+
+            return null;
+        }
+
+        @Override public void descend(ConstructableTreeNode node) {
+            throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+        }
+
+        private <T> T unwrap(JsonPrimitive jsonPrimitive, Class<T> clazz) {
+            assert !clazz.isArray();
+
+            if (clazz == String.class) {
+                if (!jsonPrimitive.isString())
+                    throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+
+                return clazz.cast(jsonPrimitive.getAsString());
+            }
+
+            if (Number.class.isAssignableFrom(clazz)) {
+                if (!jsonPrimitive.isNumber())
+                    throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+
+                if (clazz == Double.class)
+                    return clazz.cast(jsonPrimitive.getAsDouble());
+
+                if (clazz == Long.class)
+                    return clazz.cast(jsonPrimitive.getAsLong());
+
+                if (clazz == Integer.class) {
+                    long longValue = jsonPrimitive.getAsLong();
+
+                    if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE)
+                        throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+
+                    return clazz.cast((int)longValue);
+                }
+
+                throw new AssertionError(clazz);
+            }
+
+            if (clazz == Boolean.class) {
+                if (!jsonPrimitive.isBoolean())
+                    throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+
+                return clazz.cast(jsonPrimitive.getAsBoolean());
+            }
+
+            throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment.
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public String convertTo(String rootName, Object src) {
         Map<String, Object> res = new HashMap<>();
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java
index 46de08b..db5020d 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java
@@ -19,8 +19,6 @@ package org.apache.ignite.rest.presentation.json;
 
 import java.util.Collections;
 import java.util.Map;
-import org.apache.ignite.configuration.Configurator;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
 import org.apache.ignite.rest.presentation.ConfigurationPresentation;
 
 /** */
@@ -29,11 +27,7 @@ public class JsonPresentation implements ConfigurationPresentation<String> {
     private final JsonConverter converter = new JsonConverter();
 
     /** */
-    private final Map<String, Configurator<? extends DynamicConfiguration<?, ?, ?>>> configsMap;
-
-    /** */
-    public JsonPresentation(Map<String, Configurator<? extends DynamicConfiguration<?, ?, ?>>> configsMap) {
-        this.configsMap = configsMap;
+    public JsonPresentation() {
     }
 
     /** {@inheritDoc} */
@@ -70,12 +64,6 @@ public class JsonPresentation implements ConfigurationPresentation<String> {
             throw new IllegalArgumentException("Invalid request, no root in request: " + configUpdate);
         }
 
-        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator = configsMap.get(root);
-
-        if (configurator == null) {
-            throw new IllegalArgumentException("Invalid request, configuration root not found: " + configUpdate);
-        }
-
 //        Object updateObj = converter.convertFrom(configUpdate, root, configurator.getChangeType());
 
 //        configurator.set(BaseSelectors.find(root), updateObj);
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java
new file mode 100644
index 0000000..f855ec8
--- /dev/null
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.rest.presentation.json;
+
+import com.google.gson.JsonNull;
+import java.util.List;
+import org.apache.ignite.configuration.ConfigurationRegistry;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static com.google.gson.JsonParser.parseString;
+import static java.util.Collections.emptyList;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.rest.presentation.json.JsonConverter.jsonVisitor;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/** */
+public class JsonConverterTest {
+    /** */
+    @ConfigurationRoot(rootName = "root", storage = TestConfigurationStorage.class)
+    public static class JsonRootConfigurationSchema {
+        /** */
+        @NamedConfigValue
+        public JsonArraysConfigurationSchema arraysList;
+
+        /** */
+        @NamedConfigValue
+        public JsonPrimitivesConfigurationSchema primitivesList;
+    }
+
+    /** */
+    @Config
+    public static class JsonArraysConfigurationSchema {
+        /** */
+        @Value(hasDefault = true)
+        public boolean[] booleans = {false};
+
+        /** */
+        @Value(hasDefault = true)
+        public int[] ints = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public long[] longs = {0L};
+
+        /** */
+        @Value(hasDefault = true)
+        public double[] doubles = {0d};
+
+        /** */
+        @Value(hasDefault = true)
+        public String[] strings = {""};
+    }
+
+    /** */
+    @Config
+    public static class JsonPrimitivesConfigurationSchema {
+        /** */
+        @Value(hasDefault = true)
+        public boolean booleanVal = false;
+
+        /** */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public long longVal = 0L;
+
+        /** */
+        @Value(hasDefault = true)
+        public double doubleVal = 0d;
+
+        /** */
+        @Value(hasDefault = true)
+        public String stringVal = "";
+    }
+
+    /** */
+    private final ConfigurationRegistry registry = new ConfigurationRegistry();
+
+    /** */
+    private JsonRootConfiguration configuration;
+
+    /** */
+    @BeforeEach
+    public void before() {
+        registry.registerRootKey(JsonRootConfiguration.KEY);
+
+        registry.registerStorage(new TestConfigurationStorage());
+
+        configuration = registry.getConfiguration(JsonRootConfiguration.KEY);
+    }
+
+    /** */
+    @Test
+    public void toJson() throws Exception {
+        assertEquals(
+            parseString("{'root':{'arraysList':{},'primitivesList':{}}}"),
+            registry.represent(emptyList(), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("{'arraysList':{},'primitivesList':{}}"),
+            registry.represent(List.of("root"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("{}"),
+            registry.represent(List.of("root", "arraysList"), jsonVisitor())
+        );
+
+        configuration.change(cfg -> cfg
+            .changeArraysList(arraysList -> arraysList
+                .create("name", arrays -> {})
+            )
+            .changePrimitivesList(primitivesList -> primitivesList
+                .create("name", primitives -> {})
+            )
+        ).get(1, SECONDS);
+
+        assertEquals(
+            parseString("{'name':{'booleans':[false],'ints':[0],'longs':[0],'doubles':[0.0],'strings':['']}}"),
+            registry.represent(List.of("root", "arraysList"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("{'booleanVal':false,'intVal':0,'longVal':0,'doubleVal':0.0,'stringVal':''}"),
+            registry.represent(List.of("root", "primitivesList", "name"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("[false]"),
+            registry.represent(List.of("root", "arraysList", "name", "booleans"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("[0]"),
+            registry.represent(List.of("root", "arraysList", "name", "ints"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("[0]"),
+            registry.represent(List.of("root", "arraysList", "name", "longs"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("[0.0]"),
+            registry.represent(List.of("root", "arraysList", "name", "doubles"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("['']"),
+            registry.represent(List.of("root", "arraysList", "name", "strings"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("false"),
+            registry.represent(List.of("root", "primitivesList", "name", "booleanVal"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("0"),
+            registry.represent(List.of("root", "primitivesList", "name", "intVal"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("0"),
+            registry.represent(List.of("root", "primitivesList", "name", "longVal"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("0.0"),
+            registry.represent(List.of("root", "primitivesList", "name", "doubleVal"), jsonVisitor())
+        );
+
+        assertEquals(
+            parseString("''"),
+            registry.represent(List.of("root", "primitivesList", "name", "stringVal"), jsonVisitor())
+        );
+
+        assertThrows(IllegalArgumentException.class, () -> registry.represent(List.of("doot"), jsonVisitor()));
+
+        assertThrows(IllegalArgumentException.class, () -> registry.represent(List.of("root", "x"), jsonVisitor()));
+
+        assertEquals(
+            JsonNull.INSTANCE,
+            registry.represent(List.of("root", "primitivesList", "foo"), jsonVisitor())
+        );
+    }
+}
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java
new file mode 100644
index 0000000..e175d54
--- /dev/null
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.rest.presentation.json;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.ConfigurationStorageListener;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.storage.StorageException;
+
+/** */
+public class TestConfigurationStorage implements ConfigurationStorage {
+    /** */
+    private final Set<ConfigurationStorageListener> listeners = new HashSet<>();
+
+    /** {@inheritDoc} */
+    @Override public Data readAll() throws StorageException {
+        return new Data(Collections.emptyMap(), 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version) {
+        for (ConfigurationStorageListener listener : listeners)
+            listener.onEntriesChanged(new Data(newValues, version + 1));
+
+        return CompletableFuture.completedFuture(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Set<String> keys() throws StorageException {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addListener(ConfigurationStorageListener listener) {
+        listeners.add(listener);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void removeListener(ConfigurationStorageListener listener) {
+        listeners.remove(listener);
+    }
+}
diff --git a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
index 1847d6f..d6c9195 100644
--- a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
+++ b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java
@@ -23,7 +23,7 @@ import java.io.Reader;
  * Module is responsible for preparing configuration when module is started.
  *
  * Preparing configuration includes reading it from configuration file, parsing it and initializing
- * {@link Configurator} object.
+ * {@link ConfigurationRegistry} object.
  */
 public class ConfigurationModule {
 //    /** */