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

apkhmv 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 b48ddcba7c IGNITE-19152 Use schema information in 
LocalFileConfigurationStorage (#1988)
b48ddcba7c is described below

commit b48ddcba7cd2bd3b9a053ae131c25b44a0400e27
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Wed Apr 26 20:02:34 2023 +0400

    IGNITE-19152 Use schema information in LocalFileConfigurationStorage (#1988)
    
    ---------
    
    Co-authored-by: Ivan Bessonov <[email protected]>
---
 .../configuration/ConfigurationManager.java        |  46 ++-
 .../configuration/ConfigurationRegistry.java       | 189 ++-------
 .../configuration/ConfigurationTreeGenerator.java  | 283 +++++++++++++
 .../configuration/tree/ConverterToMapVisitor.java  |  42 +-
 .../internal/configuration/tree/NamedListNode.java |   2 +-
 .../configuration/util/ConfigurationFlattener.java |   6 +
 .../configuration/ConfigurationChangerTest.java    |  12 +-
 ...st.java => ConfigurationTreeGeneratorTest.java} |  59 +--
 .../configuration/direct/DirectPropertiesTest.java |  16 +-
 .../configuration/tree/InternalIdTest.java         |  26 +-
 .../configuration/tree/NamedListNodeTest.java      |  12 +-
 .../configuration/TestConfigurationChanger.java    |  28 +-
 .../DistributionZoneManagerTest.java               |  16 +-
 .../ItSslConfigurationValidationTest.java          |   2 +
 .../storage/ItRebalanceDistributedTest.java        |  14 +-
 .../runner/app/ItIgniteNodeRestartTest.java        |  20 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |  21 +-
 .../storage/LocalFileConfigurationStorage.java     | 180 +++++---
 .../DistributedConfigurationCatchUpTest.java       |  11 +-
 .../storage/LocalFileConfigurationStorageTest.java | 460 +++++++++++++++++++--
 20 files changed, 1054 insertions(+), 391 deletions(-)

diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
index b6e832ee2c..4a3b3beec4 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
@@ -17,19 +17,10 @@
 
 package org.apache.ignite.internal.configuration;
 
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigObject;
-import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.InternalConfiguration;
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
 
@@ -40,6 +31,30 @@ public class ConfigurationManager implements IgniteComponent 
{
     /** Configuration registry. */
     private final ConfigurationRegistry registry;
 
+    /**
+     * Constructor.
+     *
+     * @param rootKeys                    Configuration root keys.
+     * @param validators                  Validators.
+     * @param storage                     Configuration storage.
+     * @param generator                   Configuration tree generator.
+     * @throws IllegalArgumentException If the configuration type of the root 
keys is not equal to the storage type, or if the schema or its
+     *                                  extensions are not valid.
+     */
+    public ConfigurationManager(
+            Collection<RootKey<?, ?>> rootKeys,
+            Set<Validator<?, ?>> validators,
+            ConfigurationStorage storage,
+            ConfigurationTreeGenerator generator
+    ) {
+        registry = new ConfigurationRegistry(
+                rootKeys,
+                validators,
+                storage,
+                generator
+        );
+    }
+
     /**
      * Constructor.
      *
@@ -81,19 +96,6 @@ public class ConfigurationManager implements IgniteComponent 
{
         registry.stop();
     }
 
-    /**
-     * Bootstrap configuration manager with customer user cfg.
-     *
-     * @param configPath Customer configuration in hocon format.
-     * @throws InterruptedException If thread is interrupted during bootstrap.
-     * @throws ExecutionException   If configuration update failed for some 
reason.
-     */
-    public void bootstrap(Path configPath) throws InterruptedException, 
ExecutionException {
-        ConfigObject hoconCfg = 
ConfigFactory.parseFile(configPath.toFile()).root();
-
-        registry.change(HoconConverter.hoconSource(hoconCfg)).get();
-    }
-
     /**
      * Get configuration registry.
      *
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
index bff666bffa..5bea124c61 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
@@ -18,24 +18,13 @@
 package org.apache.ignite.internal.configuration;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.function.Predicate.not;
-import static java.util.stream.Collectors.toList;
 import static 
org.apache.ignite.internal.configuration.notifications.ConfigurationNotifier.notifyListeners;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.touch;
-import static org.apache.ignite.internal.util.CollectionUtils.difference;
-import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -53,13 +42,11 @@ import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
-import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.notifications.ConfigurationListener;
 import 
org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
 import 
org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
 import org.apache.ignite.configuration.validation.Validator;
 import 
org.apache.ignite.internal.configuration.ConfigurationChanger.ConfigurationUpdateListener;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import 
org.apache.ignite.internal.configuration.notifications.ConfigurationStorageRevisionListener;
 import 
org.apache.ignite.internal.configuration.notifications.ConfigurationStorageRevisionListenerHolder;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -96,8 +83,11 @@ public class ConfigurationRegistry implements 
IgniteComponent, ConfigurationStor
     /** Configuration change handler. */
     private final ConfigurationChanger changer;
 
-    /** Configuration generator. */
-    private final ConfigurationAsmGenerator cgen = new 
ConfigurationAsmGenerator();
+    /** Runtime implementations generator for node classes. */
+    private final ConfigurationTreeGenerator generator;
+
+    /** Flag that indicates if the {@link ConfigurationTreeGenerator} instance 
is owned by this object or not. */
+    private boolean ownConfigTreeGenerator = false;
 
     /** Configuration storage revision change listeners. */
     private final 
ConfigurationListenerHolder<ConfigurationStorageRevisionListener> 
storageRevisionListeners =
@@ -122,13 +112,34 @@ public class ConfigurationRegistry implements 
IgniteComponent, ConfigurationStor
             Collection<Class<?>> internalSchemaExtensions,
             Collection<Class<?>> polymorphicSchemaExtensions
     ) {
-        checkConfigurationType(rootKeys, storage);
-
-        Set<Class<?>> allSchemas = collectAllSchemas(rootKeys, 
internalSchemaExtensions, polymorphicSchemaExtensions);
+        this(
+                rootKeys,
+                validators,
+                storage,
+                new ConfigurationTreeGenerator(rootKeys, 
internalSchemaExtensions, polymorphicSchemaExtensions)
+        );
+
+        this.ownConfigTreeGenerator = true;
+    }
 
-        Map<Class<?>, Set<Class<?>>> internalExtensions = 
internalExtensionsWithCheck(allSchemas, internalSchemaExtensions);
+    /**
+     * Constructor.
+     *
+     * @param rootKeys                    Configuration root keys.
+     * @param validators                  Validators.
+     * @param storage                     Configuration storage.
+     * @throws IllegalArgumentException If the configuration type of the root 
keys is not equal to the storage type, or if the schema or its
+     *                                  extensions are not valid.
+     */
+    public ConfigurationRegistry(
+            Collection<RootKey<?, ?>> rootKeys,
+            Set<Validator<?, ?>> validators,
+            ConfigurationStorage storage,
+            ConfigurationTreeGenerator generator
+    ) {
+        this.generator = generator;
 
-        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = 
polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
+        checkConfigurationType(rootKeys, storage);
 
         this.rootKeys = rootKeys;
 
@@ -143,39 +154,17 @@ public class ConfigurationRegistry implements 
IgniteComponent, ConfigurationStor
         changer = new ConfigurationChanger(notificationUpdateListener(), 
rootKeys, validators0, storage) {
             @Override
             public InnerNode createRootNode(RootKey<?, ?> rootKey) {
-                return cgen.instantiateNode(rootKey.schemaClass());
+                return generator.instantiateNode(rootKey.schemaClass());
             }
         };
 
         rootKeys.forEach(rootKey -> {
-            cgen.compileRootSchema(rootKey.schemaClass(), internalExtensions, 
polymorphicExtensions);
-
-            DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, 
changer);
+            DynamicConfiguration<?, ?> cfg = generator.instantiateCfg(rootKey, 
changer);
 
             configs.put(rootKey.key(), cfg);
         });
     }
 
-    /**
-     * Collects all schemas and subschemas (recursively) from root keys, 
internal and polymorphic schema extensions.
-     *
-     * @param rootKeys                    root keys
-     * @param internalSchemaExtensions    internal schema extensions
-     * @param polymorphicSchemaExtensions polymorphic schema extensions
-     * @return set of all schema classes
-     */
-    private static Set<Class<?>> collectAllSchemas(Collection<RootKey<?, ?>> 
rootKeys,
-            Collection<Class<?>> internalSchemaExtensions,
-            Collection<Class<?>> polymorphicSchemaExtensions) {
-        Set<Class<?>> allSchemas = new HashSet<>();
-
-        allSchemas.addAll(collectSchemas(viewReadOnly(rootKeys, 
RootKey::schemaClass)));
-        allSchemas.addAll(collectSchemas(internalSchemaExtensions));
-        allSchemas.addAll(collectSchemas(polymorphicSchemaExtensions));
-
-        return allSchemas;
-    }
-
     /**
      * Registers default validator implementation to the validators map.
      *
@@ -204,6 +193,10 @@ public class ConfigurationRegistry implements 
IgniteComponent, ConfigurationStor
         changer.stop();
 
         storageRevisionListeners.clear();
+
+        if (ownConfigTreeGenerator) {
+            generator.close();
+        }
     }
 
     /**
@@ -387,114 +380,6 @@ public class ConfigurationRegistry implements 
IgniteComponent, ConfigurationStor
         return changer.notifyCurrentConfigurationListeners();
     }
 
-    /**
-     * Get configuration schemas and their validated internal extensions with 
checks.
-     *
-     * @param allSchemas               All configuration schemas.
-     * @param internalSchemaExtensions Internal extensions ({@link 
InternalConfiguration}) of configuration schemas ({@link
-     *                                 ConfigurationRoot} and {@link Config}).
-     * @return Mapping: original of the schema -> internal schema extensions.
-     * @throws IllegalArgumentException If the schema extension is invalid.
-     */
-    private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
-            Set<Class<?>> allSchemas,
-            Collection<Class<?>> internalSchemaExtensions
-    ) {
-        if (internalSchemaExtensions.isEmpty()) {
-            return Map.of();
-        }
-
-        Map<Class<?>, Set<Class<?>>> internalExtensions = 
internalSchemaExtensions(internalSchemaExtensions);
-
-        Set<Class<?>> notInAllSchemas = 
difference(internalExtensions.keySet(), allSchemas);
-
-        if (!notInAllSchemas.isEmpty()) {
-            throw new IllegalArgumentException(
-                    "Internal extensions for which no parent configuration 
schemas were found: " + notInAllSchemas
-            );
-        }
-
-        return internalExtensions;
-    }
-
-    /**
-     * Get polymorphic extensions of configuration schemas with checks.
-     *
-     * @param allSchemas                  All configuration schemas.
-     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link 
PolymorphicConfigInstance}) of configuration schemas.
-     * @return Mapping: polymorphic scheme -> extensions (instances) of 
polymorphic configuration.
-     * @throws IllegalArgumentException If the schema extension is invalid.
-     */
-    private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
-            Set<Class<?>> allSchemas,
-            Collection<Class<?>> polymorphicSchemaExtensions
-    ) {
-        Map<Class<?>, Set<Class<?>>> polymorphicExtensionsByParent = 
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
-
-        Set<Class<?>> notInAllSchemas = 
difference(polymorphicExtensionsByParent.keySet(), allSchemas);
-
-        if (!notInAllSchemas.isEmpty()) {
-            throw new IllegalArgumentException(
-                    "Polymorphic extensions for which no polymorphic 
configuration schemas were found: " + notInAllSchemas
-            );
-        }
-
-        Collection<Class<?>> noPolymorphicExtensionsSchemas = 
allSchemas.stream()
-                .filter(ConfigurationUtil::isPolymorphicConfig)
-                .filter(not(polymorphicExtensionsByParent::containsKey))
-                .collect(toList());
-
-        if (!noPolymorphicExtensionsSchemas.isEmpty()) {
-            throw new IllegalArgumentException(
-                    "Polymorphic configuration schemas for which no extensions 
were found: " + noPolymorphicExtensionsSchemas
-            );
-        }
-
-        checkPolymorphicConfigIds(polymorphicExtensionsByParent);
-
-        for (Map.Entry<Class<?>, Set<Class<?>>> e : 
polymorphicExtensionsByParent.entrySet()) {
-            Class<?> schemaClass = e.getKey();
-
-            Field typeIdField = schemaFields(schemaClass).get(0);
-
-            if (!isPolymorphicId(typeIdField)) {
-                throw new IllegalArgumentException(String.format(
-                        "First field in a polymorphic configuration schema 
must contain @%s: %s",
-                        PolymorphicId.class,
-                        schemaClass.getName()
-                ));
-            }
-        }
-
-        return polymorphicExtensionsByParent;
-    }
-
-    /**
-     * Checks that there are no conflicts between ids of a polymorphic 
configuration and its extensions (instances).
-     *
-     * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions 
(instances) of polymorphic configuration.
-     * @throws IllegalArgumentException If a polymorphic configuration id 
conflict is found.
-     * @see PolymorphicConfigInstance#value
-     */
-    private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>> 
polymorphicExtensions) {
-        // Mapping: id -> configuration schema.
-        Map<String, Class<?>> ids = new HashMap<>();
-
-        for (Map.Entry<Class<?>, Set<Class<?>>> e : 
polymorphicExtensions.entrySet()) {
-            for (Class<?> schemaClass : e.getValue()) {
-                String id = polymorphicInstanceId(schemaClass);
-                Class<?> prev = ids.put(id, schemaClass);
-
-                if (prev != null) {
-                    throw new IllegalArgumentException("Found an id conflict 
for a polymorphic configuration [id="
-                            + id + ", schemas=" + List.of(prev, schemaClass));
-                }
-            }
-
-            ids.clear();
-        }
-    }
-
     private Collection<CompletableFuture<?>> 
notifyStorageRevisionListeners(long storageRevision, long notificationNumber) {
         // Lazy init.
         List<CompletableFuture<?>> futures = null;
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
new file mode 100644
index 0000000000..1360b51a29
--- /dev/null
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeGenerator.java
@@ -0,0 +1,283 @@
+/*
+ * 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.internal.configuration;
+
+
+import static java.util.function.Function.identity;
+import static java.util.function.Predicate.not;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.internal.close.ManuallyCloseable;
+import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+/** Schema-aware configuration generator. */
+public class ConfigurationTreeGenerator implements ManuallyCloseable {
+
+    private final Map<String, RootKey<?, ?>> rootKeys;
+
+    @Nullable
+    private ConfigurationAsmGenerator generator = new 
ConfigurationAsmGenerator();
+
+    /**
+     * Constructor that takes a collection of root keys. Internal and 
polymorphic schema extensions are empty by default.
+     *
+     * @param rootKeys Root keys.
+     */
+    @TestOnly
+    public ConfigurationTreeGenerator(RootKey<?, ?>... rootKeys) {
+        this(List.of(rootKeys), Set.of(), Set.of());
+    }
+
+    /**
+     * Constructor that takes a collection of root keys and a collection of 
internal schema extensions.
+     *
+     * @param rootKeys Root keys.
+     * @param internalSchemaExtensions Internal schema extensions.
+     * @param polymorphicSchemaExtensions Polymorphic schema extensions.
+     */
+    public ConfigurationTreeGenerator(
+            Collection<RootKey<?, ?>> rootKeys,
+            Collection<Class<?>> internalSchemaExtensions,
+            Collection<Class<?>> polymorphicSchemaExtensions) {
+
+        this.rootKeys = rootKeys.stream().collect(toMap(RootKey::key, 
identity()));
+
+        Set<Class<?>> allSchemas = collectAllSchemas(rootKeys, 
internalSchemaExtensions, polymorphicSchemaExtensions);
+
+        Map<Class<?>, Set<Class<?>>> internalExtensions = 
internalExtensionsWithCheck(allSchemas, internalSchemaExtensions);
+        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = 
polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
+
+        rootKeys.forEach(key -> generator.compileRootSchema(key.schemaClass(), 
internalExtensions, polymorphicExtensions));
+    }
+
+    /**
+     * Creates a new instance of {@link SuperRoot} with all the roots created.
+     *
+     * @return New instance of {@link SuperRoot}.
+     */
+    public synchronized SuperRoot createSuperRoot() {
+        assert generator != null : "ConfigurationTreeGenerator is already 
closed";
+
+        SuperRoot superRoot = new SuperRoot(rootCreator());
+        for (RootKey<?, ?> rootKey : rootKeys.values()) {
+            superRoot.addRoot(rootKey, createRootNode(rootKey));
+        }
+
+        return superRoot;
+    }
+
+    /**
+     * Creates new instance of {@code *Configuration} class corresponding to 
the given Configuration Schema.
+     *
+     * @param rootKey Root key of the configuration root.
+     * @param changer Configuration changer instance to pass into constructor.
+     * @return Configuration instance.
+     */
+    public synchronized DynamicConfiguration<?, ?> instantiateCfg(RootKey<?, 
?> rootKey, DynamicConfigurationChanger changer) {
+        assert generator != null : "ConfigurationTreeGenerator is already 
closed";
+
+        return generator.instantiateCfg(rootKey, changer);
+    }
+
+    /**
+     * Creates new instance of {@code *Node} class corresponding to the given 
Configuration Schema class.
+     *
+     * @param schemaClass Configuration Schema class.
+     * @return Node instance.
+     */
+    public synchronized InnerNode instantiateNode(Class<?> schemaClass) {
+        assert generator != null : "ConfigurationTreeGenerator is already 
closed";
+
+        return generator.instantiateNode(schemaClass);
+    }
+
+    /**
+     * Creates new instance of root {@code *Node} class corresponding to the 
given root key.
+     */
+    public synchronized InnerNode createRootNode(RootKey<?, ?> rootKey) {
+        return instantiateNode(rootKey.schemaClass());
+    }
+
+    private Function<String, RootInnerNode> rootCreator() {
+        return key -> {
+            RootKey<?, ?> rootKey = rootKeys.get(key);
+
+            return rootKey == null ? null : new RootInnerNode(rootKey, 
createRootNode(rootKey));
+        };
+    }
+
+    @Override
+    public synchronized void close() {
+        generator = null;
+    }
+
+    /**
+     * Collects all schemas and subschemas (recursively) from root keys, 
internal and polymorphic schema extensions.
+     *
+     * @param rootKeys root keys
+     * @param internalSchemaExtensions internal schema extensions
+     * @param polymorphicSchemaExtensions polymorphic schema extensions
+     * @return set of all schema classes
+     */
+    private static Set<Class<?>> collectAllSchemas(Collection<RootKey<?, ?>> 
rootKeys,
+            Collection<Class<?>> internalSchemaExtensions,
+            Collection<Class<?>> polymorphicSchemaExtensions) {
+        Set<Class<?>> allSchemas = new HashSet<>();
+
+        allSchemas.addAll(collectSchemas(viewReadOnly(rootKeys, 
RootKey::schemaClass)));
+        allSchemas.addAll(collectSchemas(internalSchemaExtensions));
+        allSchemas.addAll(collectSchemas(polymorphicSchemaExtensions));
+
+        return allSchemas;
+    }
+
+    /**
+     * Get configuration schemas and their validated internal extensions with 
checks.
+     *
+     * @param allSchemas All configuration schemas.
+     * @param internalSchemaExtensions Internal extensions ({@link 
InternalConfiguration}) of configuration schemas
+     *         ({@link ConfigurationRoot} and {@link Config}).
+     * @return Mapping: original of the schema -> internal schema extensions.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     */
+    private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
+            Set<Class<?>> allSchemas,
+            Collection<Class<?>> internalSchemaExtensions
+    ) {
+        if (internalSchemaExtensions.isEmpty()) {
+            return Map.of();
+        }
+
+        Map<Class<?>, Set<Class<?>>> internalExtensions = 
internalSchemaExtensions(internalSchemaExtensions);
+
+        Set<Class<?>> notInAllSchemas = 
difference(internalExtensions.keySet(), allSchemas);
+
+        if (!notInAllSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Internal extensions for which no parent configuration 
schemas were found: " + notInAllSchemas
+            );
+        }
+
+        return internalExtensions;
+    }
+
+    /**
+     * Get polymorphic extensions of configuration schemas with checks.
+     *
+     * @param allSchemas All configuration schemas.
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link 
PolymorphicConfigInstance}) of configuration schemas.
+     * @return Mapping: polymorphic scheme -> extensions (instances) of 
polymorphic configuration.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     */
+    private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
+            Set<Class<?>> allSchemas,
+            Collection<Class<?>> polymorphicSchemaExtensions
+    ) {
+        Map<Class<?>, Set<Class<?>>> polymorphicExtensionsByParent = 
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
+
+        Set<Class<?>> notInAllSchemas = 
difference(polymorphicExtensionsByParent.keySet(), allSchemas);
+
+        if (!notInAllSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Polymorphic extensions for which no polymorphic 
configuration schemas were found: " + notInAllSchemas
+            );
+        }
+
+        Collection<Class<?>> noPolymorphicExtensionsSchemas = 
allSchemas.stream()
+                .filter(ConfigurationUtil::isPolymorphicConfig)
+                .filter(not(polymorphicExtensionsByParent::containsKey))
+                .collect(toList());
+
+        if (!noPolymorphicExtensionsSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Polymorphic configuration schemas for which no extensions 
were found: " + noPolymorphicExtensionsSchemas
+            );
+        }
+
+        checkPolymorphicConfigIds(polymorphicExtensionsByParent);
+
+        for (Map.Entry<Class<?>, Set<Class<?>>> e : 
polymorphicExtensionsByParent.entrySet()) {
+            Class<?> schemaClass = e.getKey();
+
+            Field typeIdField = schemaFields(schemaClass).get(0);
+
+            if (!isPolymorphicId(typeIdField)) {
+                throw new IllegalArgumentException(String.format(
+                        "First field in a polymorphic configuration schema 
must contain @%s: %s",
+                        PolymorphicId.class,
+                        schemaClass.getName()
+                ));
+            }
+        }
+
+        return polymorphicExtensionsByParent;
+    }
+
+    /**
+     * Checks that there are no conflicts between ids of a polymorphic 
configuration and its extensions (instances).
+     *
+     * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions 
(instances) of polymorphic configuration.
+     * @throws IllegalArgumentException If a polymorphic configuration id 
conflict is found.
+     * @see PolymorphicConfigInstance#value
+     */
+    private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>> 
polymorphicExtensions) {
+        // Mapping: id -> configuration schema.
+        Map<String, Class<?>> ids = new HashMap<>();
+
+        for (Map.Entry<Class<?>, Set<Class<?>>> e : 
polymorphicExtensions.entrySet()) {
+            for (Class<?> schemaClass : e.getValue()) {
+                String id = polymorphicInstanceId(schemaClass);
+                Class<?> prev = ids.put(id, schemaClass);
+
+                if (prev != null) {
+                    throw new IllegalArgumentException("Found an id conflict 
for a polymorphic configuration [id="
+                            + id + ", schemas=" + List.of(prev, schemaClass));
+                }
+            }
+
+            ids.clear();
+        }
+    }
+}
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index fb2655b8cd..ac77696ed7 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -38,6 +38,9 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     /** Include internal configuration nodes (private configuration 
extensions). */
     private final boolean includeInternal;
 
+    /** Skip nulls, empty Maps and empty lists. */
+    private final boolean skipEmptyValues;
+
     /** Stack with intermediate results. Used to store values during recursive 
calls. */
     private final Deque<Object> deque = new ArrayDeque<>();
 
@@ -45,9 +48,20 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
      * Constructor.
      *
      * @param includeInternal Include internal configuration nodes (private 
configuration extensions).
+     * @param skipEmptyValues Skip empty values.
      */
-    public ConverterToMapVisitor(boolean includeInternal) {
+    public ConverterToMapVisitor(boolean includeInternal, boolean 
skipEmptyValues) {
         this.includeInternal = includeInternal;
+        this.skipEmptyValues = skipEmptyValues;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param includeInternal Include internal configuration nodes (private 
configuration extensions).
+     */
+    public ConverterToMapVisitor(boolean includeInternal) {
+        this(includeInternal, false);
     }
 
     /** {@inheritDoc} */
@@ -69,6 +83,10 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     /** {@inheritDoc} */
     @Override
     public Object visitInnerNode(String key, InnerNode node) {
+        if (skipEmptyValues && node == null) {
+            return null;
+        }
+
         Map<String, Object> innerMap = new HashMap<>();
 
         deque.push(innerMap);
@@ -85,6 +103,10 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     /** {@inheritDoc} */
     @Override
     public Object visitNamedListNode(String key, NamedListNode<?> node) {
+        if (skipEmptyValues && node.size() == 0) {
+            return null;
+        }
+
         List<Object> list = new ArrayList<>(node.size());
 
         deque.push(list);
@@ -111,9 +133,27 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     private void addToParent(String key, Object val) {
         Object parent = deque.peek();
 
+        if (skipEmptyValues && val == null) {
+            return;
+        }
+
         if (parent instanceof Map) {
+            if (skipEmptyValues && val instanceof Map) {
+                Map<?, ?> map = (Map<?, ?>) val;
+                if (map.isEmpty()) {
+                    return;
+                }
+            }
+
             ((Map<String, Object>) parent).put(key, val);
         } else if (parent instanceof List) {
+            if (skipEmptyValues && val instanceof List) {
+                List<?> list = (List<?>) val;
+                if (list.isEmpty()) {
+                    return;
+                }
+            }
+
             ((Collection<Object>) parent).add(val);
         }
     }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index f37525eb89..d7e40314cf 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -55,7 +55,7 @@ public final class NamedListNode<N> implements 
NamedListChange<N, N>, Traversabl
     /** Name of a synthetic configuration property that's used to store "key" 
of the named list element in the storage. */
     public static final String NAME = "<name>";
 
-    /** Name of a synthetic configuration property that's used to store 
mapping fron names to internal identifiers in the storage. */
+    /** Name of a synthetic configuration property that's used to store 
mapping from names to internal identifiers in the storage. */
     public static final String IDS = "<ids>";
 
     /** Configuration name for the synthetic key. */
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
index bab4a1667e..ef6c0da18f 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -136,6 +136,12 @@ public class ConfigurationFlattener {
                 return null;
             }
 
+            // In case inner node is null in both trees,
+            // see 
LocalFileConfigurationStorageTest#innerNodeWithPartialContent – the node is 
someConfigurationValue.
+            if (oldNode == null && newNode == null) {
+                return null;
+            }
+
             if (oldNode == null) {
                 visitAsymmetricInnerNode(newNode, false);
             } else if (oldNode.schemaType() != newNode.schemaType()) {
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index 59b1be5292..38bea01769 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -58,7 +58,6 @@ import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.configuration.validation.ValidationContext;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.direct.KeyPathNode;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.Data;
@@ -113,13 +112,13 @@ public class ConfigurationChangerTest {
         public String strCfg;
     }
 
-    private static ConfigurationAsmGenerator cgen = new 
ConfigurationAsmGenerator();
+    private static ConfigurationTreeGenerator generator = new 
ConfigurationTreeGenerator(KEY, DefaultsConfiguration.KEY);
 
     private final TestConfigurationStorage storage = new 
TestConfigurationStorage(LOCAL);
 
     @AfterAll
     public static void afterAll() {
-        cgen = null;
+        generator.close();
     }
 
     /**
@@ -218,12 +217,10 @@ public class ConfigurationChangerTest {
         };
 
         ConfigurationChanger changer2 = new TestConfigurationChanger(
-                cgen,
                 List.of(KEY),
                 Set.of(validator),
                 storage,
-                List.of(),
-                List.of()
+                generator
         );
 
         changer2.start();
@@ -592,7 +589,8 @@ public class ConfigurationChangerTest {
     }
 
     private ConfigurationChanger createChanger(RootKey<?, ?> rootKey) {
-        return new TestConfigurationChanger(cgen, List.of(rootKey), Set.of(), 
storage, List.of(), List.of());
+
+        return new TestConfigurationChanger(List.of(rootKey), Set.of(), 
storage, generator);
     }
 
     private static KeyPathNode node(String key) {
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
similarity index 96%
rename from 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
rename to 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
index 36efcd9a66..af7cdd9359 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
@@ -40,6 +40,7 @@ import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import org.apache.ignite.configuration.ConfigurationReadOnlyException;
 import 
org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
+import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.AbstractConfiguration;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
@@ -54,6 +55,7 @@ import 
org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationChanger;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.DynamicConfiguration;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -66,51 +68,56 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 /**
- * Testing the {@link ConfigurationAsmGenerator}.
+ * Testing the {@link ConfigurationTreeGenerator}.
  */
-public class ConfigurationAsmGeneratorTest {
+public class ConfigurationTreeGeneratorTest {
     /** Configuration generator. */
-    private static ConfigurationAsmGenerator generator;
+    private static ConfigurationTreeGenerator generator;
+
+
+    private static Collection<Class<?>> internalExtensions = List.of(
+            ExtendedTestRootConfigurationSchema.class,
+            ExtendedSecondTestRootConfigurationSchema.class,
+            ExtendedTestConfigurationSchema.class,
+            ExtendedSecondTestConfigurationSchema.class
+    );
+
+    private static Collection<Class<?>> polymorphicExtensions = List.of(
+            FirstPolymorphicInstanceTestConfigurationSchema.class,
+            SecondPolymorphicInstanceTestConfigurationSchema.class,
+            NonDefaultPolymorphicInstanceTestConfigurationSchema.class,
+            FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
+            SecondPolymorphicNamedInstanceTestConfigurationSchema.class,
+            PolyInst0InjectedNameConfigurationSchema.class,
+            PolyInst1InjectedNameConfigurationSchema.class
+    );
+
+    private static Collection<RootKey<?, ?>> rootKeys = List.of(
+            TestRootConfiguration.KEY,
+            InjectedNameRootConfiguration.KEY,
+            RootFromAbstractConfiguration.KEY
+    );
 
     /** Configuration changer. */
     private ConfigurationChanger changer;
 
     @BeforeAll
     public static void beforeAll() {
-        generator = new ConfigurationAsmGenerator();
+        generator = new ConfigurationTreeGenerator(rootKeys, 
internalExtensions, polymorphicExtensions);
     }
 
     @AfterAll
     public static void afterAll() {
-        generator = null;
+        generator.close();
     }
 
     @BeforeEach
     void beforeEach() {
-        Collection<Class<?>> internalExtensions = List.of(
-                ExtendedTestRootConfigurationSchema.class,
-                ExtendedSecondTestRootConfigurationSchema.class,
-                ExtendedTestConfigurationSchema.class,
-                ExtendedSecondTestConfigurationSchema.class
-        );
-
-        Collection<Class<?>> polymorphicExtensions = List.of(
-                FirstPolymorphicInstanceTestConfigurationSchema.class,
-                SecondPolymorphicInstanceTestConfigurationSchema.class,
-                NonDefaultPolymorphicInstanceTestConfigurationSchema.class,
-                FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
-                SecondPolymorphicNamedInstanceTestConfigurationSchema.class,
-                PolyInst0InjectedNameConfigurationSchema.class,
-                PolyInst1InjectedNameConfigurationSchema.class
-        );
-
         changer = new TestConfigurationChanger(
-                generator,
-                List.of(TestRootConfiguration.KEY, 
InjectedNameRootConfiguration.KEY, RootFromAbstractConfiguration.KEY),
+                rootKeys,
                 Set.of(),
                 new TestConfigurationStorage(LOCAL),
-                internalExtensions,
-                polymorphicExtensions
+                generator
         );
 
         changer.start();
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
index 286c2a57ba..ed56c12c8d 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
@@ -99,16 +99,18 @@ public class DirectPropertiesTest {
         public String str = "bar";
     }
 
-    private final ConfigurationRegistry registry = new ConfigurationRegistry(
-            List.of(DirectConfiguration.KEY),
-            Set.of(),
-            new TestConfigurationStorage(LOCAL),
-            List.of(),
-            List.of()
-    );
+    private ConfigurationRegistry registry;
 
     @BeforeEach
     void setUp() {
+        registry = new ConfigurationRegistry(
+                List.of(DirectConfiguration.KEY),
+                Set.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of()
+        );
+
         registry.start();
 
         registry.initializeDefaults();
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
index dfcc0a5d3a..74a38ebf6f 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
@@ -37,6 +37,7 @@ import 
org.apache.ignite.configuration.annotation.PolymorphicConfig;
 import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.direct.DirectPropertiesTest;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
@@ -77,16 +78,25 @@ public class InternalIdTest {
     public static class InternalIdFooConfigurationSchema extends 
InternalIdPolymorphicConfigurationSchema {
     }
 
-    private final ConfigurationRegistry registry = new ConfigurationRegistry(
-            List.of(InternalIdParentConfiguration.KEY),
-            Set.of(),
-            new TestConfigurationStorage(LOCAL),
-            List.of(InternalIdInternalConfigurationSchema.class),
-            List.of(InternalIdFooConfigurationSchema.class)
-    );
+    private ConfigurationRegistry registry;
+
+    private ConfigurationTreeGenerator generator;
 
     @BeforeEach
     void setUp() {
+        generator = new ConfigurationTreeGenerator(
+                List.of(InternalIdParentConfiguration.KEY),
+                List.of(InternalIdInternalConfigurationSchema.class),
+                List.of(InternalIdFooConfigurationSchema.class)
+        );
+
+        registry = new ConfigurationRegistry(
+                List.of(InternalIdParentConfiguration.KEY),
+                Set.of(),
+                new TestConfigurationStorage(LOCAL),
+                generator
+        );
+
         registry.start();
 
         registry.initializeDefaults();
@@ -95,6 +105,8 @@ public class InternalIdTest {
     @AfterEach
     void tearDown() throws Exception {
         registry.stop();
+
+        generator.close();
     }
 
     /**
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index 83418ce89b..7ab55d9d36 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -38,8 +38,8 @@ 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.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.Data;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
@@ -79,7 +79,7 @@ public class NamedListNodeTest {
     }
 
     /** Runtime implementations generator. */
-    private static ConfigurationAsmGenerator cgen;
+    private static ConfigurationTreeGenerator cgen;
 
     /** Test configuration storage. */
     private TestConfigurationStorage storage;
@@ -90,13 +90,13 @@ public class NamedListNodeTest {
     /** Instantiates {@link #cgen}. */
     @BeforeAll
     public static void beforeAll() {
-        cgen = new ConfigurationAsmGenerator();
+        cgen = new ConfigurationTreeGenerator(FirstConfiguration.KEY);
     }
 
     /** Nullifies {@link #cgen} to prevent memory leak from having runtime 
ClassLoader accessible from GC root. */
     @AfterAll
     public static void afterAll() {
-        cgen = null;
+        cgen.close();
     }
 
     /**
@@ -107,12 +107,10 @@ public class NamedListNodeTest {
         storage = new TestConfigurationStorage(LOCAL);
 
         changer = new TestConfigurationChanger(
-                cgen,
                 List.of(FirstConfiguration.KEY),
                 Set.of(),
                 storage,
-                List.of(),
-                List.of()
+                cgen
         );
 
         changer.start();
diff --git 
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
 
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
index 4b72a70d2a..2bda7a0fe7 100644
--- 
a/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
+++ 
b/modules/configuration/src/testFixtures/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -18,20 +18,12 @@
 package org.apache.ignite.internal.configuration;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
 
 import java.util.Collection;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.InternalConfiguration;
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.jetbrains.annotations.Nullable;
@@ -39,36 +31,26 @@ import org.jetbrains.annotations.Nullable;
 /** Implementation of {@link ConfigurationChanger} to be used in tests. Has no 
support of listeners. */
 public class TestConfigurationChanger extends ConfigurationChanger {
     /** Runtime implementations generator for node classes. */
-    private final ConfigurationAsmGenerator cgen;
+    private final ConfigurationTreeGenerator generator;
 
     /**
      * Constructor.
      *
-     * @param cgen                        Runtime implementations generator 
for node classes. Will be used to instantiate nodes objects.
      * @param rootKeys                    Configuration root keys.
      * @param validators                  Validators.
      * @param storage                     Configuration storage.
-     * @param internalSchemaExtensions    Internal extensions ({@link 
InternalConfiguration}) of configuration schemas ({@link
-     *                                    ConfigurationRoot} and {@link 
Config}).
-     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link 
PolymorphicConfigInstance}) of configuration schemas.
+     * @param generator                   Runtime implementations tree 
generator for node classes.
      * @throws IllegalArgumentException If the configuration type of the root 
keys is not equal to the storage type.
      */
     public TestConfigurationChanger(
-            ConfigurationAsmGenerator cgen,
             Collection<RootKey<?, ?>> rootKeys,
             Set<Validator<?, ?>> validators,
             ConfigurationStorage storage,
-            Collection<Class<?>> internalSchemaExtensions,
-            Collection<Class<?>> polymorphicSchemaExtensions
+            ConfigurationTreeGenerator generator
     ) {
         super(noOpListener(), rootKeys, validators, storage);
 
-        this.cgen = cgen;
-
-        Map<Class<?>, Set<Class<?>>> internalExtensions = 
internalSchemaExtensions(internalSchemaExtensions);
-        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = 
polymorphicSchemaExtensions(polymorphicSchemaExtensions);
-
-        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), 
internalExtensions, polymorphicExtensions));
+        this.generator = generator;
     }
 
     private static ConfigurationUpdateListener noOpListener() {
@@ -89,6 +71,6 @@ public class TestConfigurationChanger extends 
ConfigurationChanger {
     /** {@inheritDoc} */
     @Override
     public InnerNode createRootNode(RootKey<?, ?> rootKey) {
-        return cgen.instantiateNode(rootKey.schemaClass());
+        return generator.createRootNode(rootKey);
     }
 }
diff --git 
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
 
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
index 09f7eb2207..04896f567b 100644
--- 
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
+++ 
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
@@ -64,13 +64,7 @@ class DistributionZoneManagerTest extends IgniteAbstractTest 
{
 
     private static final String NEW_ZONE_NAME = "zone2";
 
-    private final ConfigurationRegistry registry = new ConfigurationRegistry(
-            List.of(DistributionZonesConfiguration.KEY),
-            Set.of(FilterValidator.INSTANCE),
-            new TestConfigurationStorage(DISTRIBUTED),
-            List.of(),
-            List.of(TestPersistStorageConfigurationSchema.class)
-    );
+    private ConfigurationRegistry registry;
 
     private DistributionZoneManager distributionZoneManager;
 
@@ -79,6 +73,14 @@ class DistributionZoneManagerTest extends IgniteAbstractTest 
{
 
     @BeforeEach
     public void setUp() {
+        registry = new ConfigurationRegistry(
+                List.of(DistributionZonesConfiguration.KEY),
+                Set.of(FilterValidator.INSTANCE),
+                new TestConfigurationStorage(DISTRIBUTED),
+                List.of(),
+                List.of(TestPersistStorageConfigurationSchema.class)
+        );
+
         registry.start();
 
         registry.initializeDefaults();
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
index 36b858f33d..db1144c138 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItSslConfigurationValidationTest.java
@@ -25,6 +25,7 @@ import 
org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
 import org.apache.ignite.internal.testframework.TestIgnitionManager;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -34,6 +35,7 @@ import org.junit.jupiter.params.provider.ValueSource;
  * Integration test for checking SSL configuration validation.
  */
 @ExtendWith(WorkDirectoryExtension.class)
+@Disabled("https://issues.apache.org/jira/browse/IGNITE-19315";)
 public class ItSslConfigurationValidationTest {
     @ParameterizedTest
     @ValueSource(strings = {"clientConnector", "network", "rest"})
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
index 0172a9c54d..e78b37d878 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
@@ -76,6 +76,7 @@ import 
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyImp
 import 
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyServiceImpl;
 import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologyService;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.SecurityConfiguration;
 import 
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import 
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
@@ -583,6 +584,8 @@ public class ItRebalanceDistributedTest {
 
         private List<IgniteComponent> nodeComponents;
 
+        private final ConfigurationTreeGenerator generator;
+
         private final Map<TablePartitionId, CompletableFuture<Void>> 
finishHandleChangeStableAssignmentEventFutures
                 = new ConcurrentHashMap<>();
 
@@ -600,15 +603,18 @@ public class ItRebalanceDistributedTest {
 
             vaultManager = createVault(name, dir);
 
+            generator = new ConfigurationTreeGenerator(
+                    NetworkConfiguration.KEY, RestConfiguration.KEY, 
ClientConnectorConfiguration.KEY
+            );
+
             Path configPath = workDir.resolve(testInfo.getDisplayName());
             nodeCfgMgr = new ConfigurationManager(
                     List.of(NetworkConfiguration.KEY,
                             RestConfiguration.KEY,
                             ClientConnectorConfiguration.KEY),
                     Set.of(),
-                    new LocalFileConfigurationStorage(configPath),
-                    List.of(),
-                    List.of()
+                    new LocalFileConfigurationStorage(configPath, generator),
+                    generator
             );
 
             clusterService = ClusterServiceTestUtils.clusterService(
@@ -852,6 +858,8 @@ public class ItRebalanceDistributedTest {
                 }
             });
 
+
+            generator.close();
         }
 
         NetworkAddress address() {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index c4eef0882a..9db9950775 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -70,6 +70,7 @@ import 
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyImp
 import 
org.apache.ignite.internal.cluster.management.topology.LogicalTopologyServiceImpl;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
 import org.apache.ignite.internal.configuration.ConfigurationModules;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.NodeConfigWriteException;
 import org.apache.ignite.internal.configuration.SecurityConfiguration;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
@@ -118,7 +119,6 @@ import org.apache.ignite.internal.tx.message.TxMessageGroup;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
-import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteStringFormatter;
 import org.apache.ignite.lang.IgniteSystemProperties;
@@ -261,14 +261,20 @@ public class ItIgniteNodeRestartTest extends 
IgniteAbstractTest {
         } catch (IOException e) {
             throw new NodeConfigWriteException("Failed to write config content 
to file.", e);
         }
-        var nodeCfgMgr = new ConfigurationManager(
+
+        var generator = new ConfigurationTreeGenerator(
                 modules.local().rootKeys(),
-                modules.local().validators(),
-                new LocalFileConfigurationStorage(configFile),
                 modules.local().internalSchemaExtensions(),
                 modules.local().polymorphicSchemaExtensions()
         );
 
+        var nodeCfgMgr = new ConfigurationManager(
+                modules.local().rootKeys(),
+                modules.local().validators(),
+                new LocalFileConfigurationStorage(configFile, generator),
+                generator
+        );
+
         NetworkConfiguration networkConfiguration = 
nodeCfgMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY);
 
         var nettyBootstrapFactory = new 
NettyBootstrapFactory(networkConfiguration, name);
@@ -433,12 +439,6 @@ public class ItIgniteNodeRestartTest extends 
IgniteAbstractTest {
 
         nodeCfgMgr.start();
 
-        try {
-            nodeCfgMgr.bootstrap(configFile);
-        } catch (Exception e) {
-            throw new IgniteException("Unable to parse user-specific 
configuration.", e);
-        }
-
         // Start the remaining components.
         List<IgniteComponent> otherComponents = List.of(
                 nettyBootstrapFactory,
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 03eb58f250..f785d423aa 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -61,7 +61,7 @@ import 
org.apache.ignite.internal.configuration.AuthenticationConfiguration;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
 import org.apache.ignite.internal.configuration.ConfigurationModules;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
-import org.apache.ignite.internal.configuration.NodeConfigReadException;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.SecurityConfiguration;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -299,14 +299,19 @@ public class IgniteImpl implements Ignite {
 
         ConfigurationModules modules = 
loadConfigurationModules(serviceProviderClassLoader);
 
-        nodeCfgMgr = new ConfigurationManager(
+        ConfigurationTreeGenerator generator = new ConfigurationTreeGenerator(
                 modules.local().rootKeys(),
-                modules.local().validators(),
-                new LocalFileConfigurationStorage(configPath),
                 modules.local().internalSchemaExtensions(),
                 modules.local().polymorphicSchemaExtensions()
         );
 
+        nodeCfgMgr = new ConfigurationManager(
+                modules.local().rootKeys(),
+                modules.local().validators(),
+                new LocalFileConfigurationStorage(configPath, generator),
+                generator
+        );
+
         ConfigurationRegistry nodeConfigRegistry = 
nodeCfgMgr.configurationRegistry();
 
         NetworkConfiguration networkConfiguration = 
nodeConfigRegistry.getConfiguration(NetworkConfiguration.KEY);
@@ -642,14 +647,6 @@ public class IgniteImpl implements Ignite {
             // Node configuration manager startup.
             lifecycleManager.startComponent(nodeCfgMgr);
 
-            // Node configuration manager bootstrap.
-
-            try {
-                nodeCfgMgr.bootstrap(configPath);
-            } catch (Exception e) {
-                throw new NodeConfigReadException("Unable to parse 
user-specific configuration", e);
-            }
-
             // Start the components that are required to join the cluster.
             lifecycleManager.startComponents(
                     nettyBootstrapFactory,
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
 
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
index b43b069329..d730dd353d 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
@@ -17,10 +17,18 @@
 
 package org.apache.ignite.internal.configuration.storage;
 
+import static java.util.stream.Collectors.toMap;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
+
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigParseOptions;
 import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValue;
+import com.typesafe.config.impl.ConfigImpl;
 import java.io.IOException;
 import java.io.Serializable;
 import java.nio.charset.StandardCharsets;
@@ -28,8 +36,6 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.CompletableFuture;
@@ -40,10 +46,13 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.stream.Collectors;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.NodeConfigCreateException;
 import org.apache.ignite.internal.configuration.NodeConfigWriteException;
+import org.apache.ignite.internal.configuration.SuperRoot;
+import org.apache.ignite.internal.configuration.hocon.HoconConverter;
+import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
 import org.apache.ignite.internal.future.InFlightFutures;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
@@ -56,60 +65,88 @@ import org.apache.ignite.internal.util.IgniteUtils;
 public class LocalFileConfigurationStorage implements ConfigurationStorage {
     private static final IgniteLogger LOG = 
Loggers.forClass(LocalFileConfigurationStorage.class);
 
-    /**
-     * Path to config file.
-     */
+    /** Path to config file. */
     private final Path configPath;
 
-    /**
-     * Path to temporary configuration storage.
-     */
+    /** Path to temporary configuration storage. */
     private final Path tempConfigPath;
 
+    /** R/W lock to guard the latest configuration and config file. */
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
 
-    /**
-     * Latest state of last applied configuration.
-     */
+    /** Latest state of last applied configuration. */
     private final Map<String, Serializable> latest = new ConcurrentHashMap<>();
 
-    /**
-     *  Configuration changes listener.
-     *  */
+    /** Configuration tree generator. */
+    private final ConfigurationTreeGenerator generator;
+
+    /** Configuration changes listener. */
     private final AtomicReference<ConfigurationStorageListener> lsnrRef = new 
AtomicReference<>();
 
-    private final ExecutorService threadPool = Executors.newFixedThreadPool(2, 
new NamedThreadFactory("loc-cfg-file", LOG));
+    /** Thread pool for configuration updates notifications. */
+    private final ExecutorService notificationsThreadPool = 
Executors.newFixedThreadPool(
+            2, new NamedThreadFactory("cfg-file", LOG)
+    );
 
+    /** Tracks all running futures. */
     private final InFlightFutures futureTracker = new InFlightFutures();
 
+    /** Last revision for configuration. */
     private long lastRevision = 0L;
 
     /**
      * Constructor.
      *
      * @param configPath Path to node bootstrap configuration file.
+     * @param generator Configuration tree generator.
      */
-    public LocalFileConfigurationStorage(Path configPath) {
+    public LocalFileConfigurationStorage(Path configPath, 
ConfigurationTreeGenerator generator) {
         this.configPath = configPath;
-        tempConfigPath = configPath.resolveSibling(configPath.getFileName() + 
".tmp");
+        this.generator = generator;
+        this.tempConfigPath = 
configPath.resolveSibling(configPath.getFileName() + ".tmp");
+
         checkAndRestoreConfigFile();
     }
 
     @Override
     public CompletableFuture<Data> readDataOnRecovery() {
-        return CompletableFuture.completedFuture(new 
Data(Collections.emptyMap(), 0));
+        lock.writeLock().lock();
+        try {
+            SuperRoot superRoot = generator.createSuperRoot();
+            SuperRoot copiedSuperRoot = superRoot.copy();
+
+            Config hocon = readHoconFromFile();
+            HoconConverter.hoconSource(hocon.root()).descend(copiedSuperRoot);
+
+            Map<String, Serializable> flattenedUpdatesMap = 
createFlattenedUpdatesMap(superRoot, copiedSuperRoot);
+            flattenedUpdatesMap.forEach((key, value) -> {
+                if (value != null) { // Filter defaults.
+                    latest.put(key, value);
+                }
+            });
+
+            return CompletableFuture.completedFuture(new Data(latest, 
lastRevision));
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    private Config readHoconFromFile() {
+        checkAndRestoreConfigFile();
+
+        return ConfigFactory.parseFile(configPath.toFile(), 
ConfigParseOptions.defaults().setAllowMissing(false));
     }
 
     @Override
     public CompletableFuture<Map<String, ? extends Serializable>> 
readAllLatest(String prefix) {
         lock.readLock().lock();
         try {
-            checkAndRestoreConfigFile();
-            Map<String, Serializable> map = latest.entrySet()
-                    .stream()
-                    .filter(entry -> entry.getKey().startsWith(prefix))
-                    .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
-            return CompletableFuture.completedFuture(map);
+            return CompletableFuture.completedFuture(
+                    latest.entrySet()
+                            .stream()
+                            .filter(entry -> entry.getKey().startsWith(prefix))
+                            .collect(toMap(Entry::getKey, Entry::getValue))
+            );
         } finally {
             lock.readLock().unlock();
         }
@@ -119,7 +156,6 @@ public class LocalFileConfigurationStorage implements 
ConfigurationStorage {
     public CompletableFuture<Serializable> readLatest(String key) {
         lock.readLock().lock();
         try {
-            checkAndRestoreConfigFile();
             return CompletableFuture.completedFuture(latest.get(key));
         } finally {
             lock.readLock().unlock();
@@ -133,22 +169,31 @@ public class LocalFileConfigurationStorage implements 
ConfigurationStorage {
             if (ver != lastRevision) {
                 return CompletableFuture.completedFuture(false);
             }
-            checkAndRestoreConfigFile();
-            // TODO: https://issues.apache.org/jira/browse/IGNITE-19152
-            //saveValues(newValues);
-            latest.putAll(newValues);
-            lastRevision++;
-            runAsync(() -> lsnrRef.get().onEntriesChanged(new Data(newValues, 
lastRevision)));
+
+            mergeAndSave(newValues);
+
+            sendNotificationAsync(new Data(newValues, lastRevision));
+
             return CompletableFuture.completedFuture(true);
         } finally {
             lock.writeLock().unlock();
         }
     }
 
-    private void runAsync(Runnable runnable) {
-        CompletableFuture<Void> future = CompletableFuture.runAsync(runnable, 
threadPool);
+    private void mergeAndSave(Map<String, ? extends Serializable> newValues) {
+        updateLatestState(newValues);
+        saveConfigFile();
+        lastRevision++;
+    }
 
-        futureTracker.registerFuture(future);
+    private void updateLatestState(Map<String, ? extends Serializable> 
newValues) {
+        newValues.forEach((key, value) -> {
+            if (value == null) { // Null means that we should remove this 
entry.
+                latest.remove(key);
+            } else {
+                latest.put(key, value);
+            }
+        });
     }
 
     @Override
@@ -175,56 +220,62 @@ public class LocalFileConfigurationStorage implements 
ConfigurationStorage {
 
     @Override
     public void close() {
-        IgniteUtils.shutdownAndAwaitTermination(threadPool, 10, 
TimeUnit.SECONDS);
-
         futureTracker.cancelInFlightFutures();
+
+        IgniteUtils.shutdownAndAwaitTermination(notificationsThreadPool, 10, 
TimeUnit.SECONDS);
     }
 
-    private void saveValues(Map<String, ? extends Serializable> values) {
+    private void saveConfigFile() {
         try {
-            Files.write(tempConfigPath, 
renderHoconString(values).getBytes(StandardCharsets.UTF_8),
-                    StandardOpenOption.SYNC, StandardOpenOption.CREATE, 
StandardOpenOption.TRUNCATE_EXISTING);
-            Files.move(tempConfigPath, configPath, 
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+            Files.write(
+                    tempConfigPath,
+                    renderHoconString().getBytes(StandardCharsets.UTF_8),
+                    StandardOpenOption.SYNC, StandardOpenOption.CREATE, 
StandardOpenOption.TRUNCATE_EXISTING
+            );
+
+            Files.move(
+                    tempConfigPath,
+                    configPath,
+                    StandardCopyOption.ATOMIC_MOVE, 
StandardCopyOption.REPLACE_EXISTING
+            );
         } catch (IOException e) {
             throw new NodeConfigWriteException(
-                    "Failed to write values " + values + " to config file.", 
e);
+                    "Failed to write values to config file.", e);
         }
     }
 
     /**
      * Convert provided map to Hocon String representation.
      *
-     * @param values Values of configuration.
      * @return Configuration file string representation in HOCON format.
      */
-    private String renderHoconString(Map<String, ? extends Serializable> 
values) {
-        Map<String, Object> map = 
values.entrySet().stream().collect(Collectors.toMap(Entry::getKey, stringEntry 
-> {
-            Serializable value = stringEntry.getValue();
-            if (value.getClass().isArray()) {
-                return Arrays.asList((Object[]) value);
-            }
-            return value;
-        }));
-        Config other = ConfigFactory.parseMap(map);
-        Config newConfig = other.withFallback(parseConfigOptions()).resolve();
+    private String renderHoconString() {
+        // Super root that'll be filled from the storage data.
+        SuperRoot rootNode = generator.createSuperRoot();
+
+        fillFromPrefixMap(rootNode, toPrefixMap(latest));
+
+        Object transformed = rootNode.accept(null, new 
ConverterToMapVisitor(false, true));
+
+        ConfigValue conf = ConfigImpl.fromAnyRef(transformed, null);
+
+        return renderConfig((ConfigObject) conf);
+    }
+
+    private static String renderConfig(ConfigObject conf) {
+        Config newConfig = conf.toConfig().resolve();
         return newConfig.isEmpty()
                 ? ""
                 : 
newConfig.root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(false));
     }
 
-    private Config parseConfigOptions() {
-        return ConfigFactory.parseFile(
-                configPath.toFile(),
-                ConfigParseOptions.defaults().setAllowMissing(false));
-    }
-
     /** Check that configuration file still exists and restore it with latest 
applied state in case it was deleted. */
     private void checkAndRestoreConfigFile() {
         if (!configPath.toFile().exists()) {
             try {
                 if (configPath.toFile().createNewFile()) {
                     if (!latest.isEmpty()) {
-                        saveValues(latest);
+                        saveConfigFile();
                     }
                 } else {
                     throw new NodeConfigCreateException("Failed to re-create 
config file");
@@ -234,4 +285,13 @@ public class LocalFileConfigurationStorage implements 
ConfigurationStorage {
             }
         }
     }
+
+    private void sendNotificationAsync(Data data) {
+        CompletableFuture<Void> future = CompletableFuture.runAsync(
+                () -> lsnrRef.get().onEntriesChanged(data),
+                notificationsThreadPool
+        );
+
+        futureTracker.registerFuture(future);
+    }
 }
diff --git 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
index 776fa792be..32dc3e7d9c 100644
--- 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
+++ 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationCatchUpTest.java
@@ -29,7 +29,6 @@ import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -38,8 +37,8 @@ import java.util.function.Consumer;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 import org.apache.ignite.internal.metastorage.EntryEvent;
@@ -95,15 +94,14 @@ public class DistributedConfigurationCatchUpTest {
     public void testMetaStorageRevisionDifferentFromConfigurationOnRestart() 
throws Exception {
         RootKey<DistributedTestConfiguration, DistributedTestView> rootKey = 
DistributedTestConfiguration.KEY;
 
-        ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
+        ConfigurationTreeGenerator generator = new 
ConfigurationTreeGenerator(rootKey);
 
         MetaStorageMockWrapper wrapper = new MetaStorageMockWrapper();
 
         DistributedConfigurationStorage storage = storage(wrapper);
 
         try {
-            var changer = new TestConfigurationChanger(cgen, List.of(rootKey), 
Set.of(),
-                    storage, Collections.emptyList(), Collections.emptyList());
+            var changer = new TestConfigurationChanger(List.of(rootKey), 
Set.of(), storage, generator);
 
             try {
                 changer.start();
@@ -133,8 +131,7 @@ public class DistributedConfigurationCatchUpTest {
 
         try {
 
-            var changer = new TestConfigurationChanger(cgen, List.of(rootKey), 
Set.of(),
-                    storage, Collections.emptyList(), Collections.emptyList());
+            var changer = new TestConfigurationChanger(List.of(rootKey), 
Set.of(), storage, generator);
 
             try {
                 changer.start();
diff --git 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
index 568d33e267..6ffd2cacac 100644
--- 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
+++ 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
@@ -17,75 +17,457 @@
 
 package org.apache.ignite.internal.configuration.storage;
 
-import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.equalToCompressingWhiteSpace;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.hasValue;
 import static org.hamcrest.Matchers.is;
 
 import java.io.IOException;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
+import org.apache.ignite.internal.configuration.TestConfigurationChanger;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.junit.jupiter.api.Disabled;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-/**
- * Tests for the {@link LocalFileConfigurationStorage}.
- */
+/** Test for local file configurations storage. */
 @ExtendWith(WorkDirectoryExtension.class)
-@Disabled("https://issues.apache.org/jira/browse/IGNITE-19152";)
-public class LocalFileConfigurationStorageTest extends 
ConfigurationStorageTest {
+//TODO: https://issues.apache.org/jira/browse/IGNITE-19303
+public class LocalFileConfigurationStorageTest {
 
     private static final String CONFIG_NAME = "ignite-config.conf";
 
+    private static ConfigurationTreeGenerator treeGenerator;
+
     @WorkDirectory
     private Path tmpDir;
 
-    @Override
-    public ConfigurationStorage getStorage() {
-        return new LocalFileConfigurationStorage(getConfigFile());
+    private LocalFileConfigurationStorage storage;
+
+    private TestConfigurationChanger changer;
+
+    @BeforeAll
+    public static void beforeAll() {
+        treeGenerator = new ConfigurationTreeGenerator(TopConfiguration.KEY, 
TopEmptyConfiguration.KEY);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        treeGenerator.close();
+    }
+
+    private Path getConfigFile() {
+        return tmpDir.resolve(CONFIG_NAME);
+    }
+
+    @BeforeEach
+    void before() {
+        storage = new LocalFileConfigurationStorage(getConfigFile(), 
treeGenerator);
+
+        changer = new TestConfigurationChanger(
+                List.of(TopConfiguration.KEY),
+                Set.of(),
+                storage,
+                treeGenerator
+        );
+
+        changer.start();
+    }
+
+    @AfterEach
+    void after() {
+        changer.stop();
+    }
+
+
+    /** Default values are not enriched on read when the config file is empty. 
*/
+    @Test
+    void empty() throws IOException {
+        // Given
+        assertThat(configFileContent(), emptyString());
+
+        // When
+        var storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues.entrySet(), hasSize(0));
+    }
+
+    /** Named list entities can be added. */
+    @Test
+    void add() throws Exception {
+        // Given
+        assertThat(configFileContent(), emptyString());
+
+        // And
+        var topConfiguration = (TopConfiguration) 
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+        topConfiguration.namedList().change(b -> b.create("name1", x -> {
+            x.changeStrVal("strVal1");
+            x.changeIntVal(-1);
+        })).get();
+
+        // When
+        var storageValues = readAllLatest();
+
+        // Then the map has updated values
+        //
+        // top.namedList.<generatedUUID>.strVal  -> strVal1
+        // top.namedList.<generatedUUID>.intVal  -> -1
+        // top.namedList.<generatedUUID>.<name>  -> name1
+        // top.namedList.<ids>.name1             -> "<generatedUUID>"
+        // top.namedList.<generatedUUID>.<order> -> 0
+
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue("strVal1")));
+
+        // And
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-1\n"
+                        + "            name=name1\n"
+                        + "            strVal=strVal1\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "}"
+        ));
+
+        // When
+        topConfiguration.namedList().change(b -> b.create("name2", x -> {
+            x.changeStrVal("strVal2");
+            x.changeIntVal(-2);
+        })).get();
+        // And
+        storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-2)));
+        assertThat(storageValues, allOf(aMapWithSize(10), 
hasValue("strVal2")));
+        // And
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(10), 
hasValue("strVal1")));
+        // And
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-1\n"
+                        + "            name=name1\n"
+                        + "            strVal=strVal1\n"
+                        + "        },\n"
+                        + "        {\n"
+                        + "            intVal=-2\n"
+                        + "            name=name2\n"
+                        + "            strVal=strVal2\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "}\n"
+        ));
+    }
+
+    /** Update values. */
+    @Test
+    void update() throws Exception {
+        // Given
+        assertThat(configFileContent(), emptyString());
+
+        // When
+        var topConfiguration = (TopConfiguration) 
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+        topConfiguration.shortVal().update((short) 3).get();
+        // And
+        var storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+        // And
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    shortVal=3\n"
+                        + "}\n"
+        ));
+
+        // When create named list entity with defaults
+        topConfiguration.namedList().change(b -> b.create("name1", x -> {
+        })).get();
+        // And
+        storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(6), hasValue(1)));
+        assertThat(storageValues, allOf(aMapWithSize(6), hasValue("foo")));
+        // And
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=1\n"
+                        + "            name=name1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "    shortVal=3\n"
+                        + "}"
+        ));
+
+        // When update named list entity
+        topConfiguration.namedList().change(b -> b.update("name1", x -> {
+            x.changeStrVal("strVal1");
+            x.changeIntVal(-1);
+        })).get();
+        // And
+        storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(6), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(6), hasValue("strVal1")));
+        // And
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-1\n"
+                        + "            name=name1\n"
+                        + "            strVal=strVal1\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "    shortVal=3\n"
+                        + "}"
+        ));
     }
 
+    /** Remove values. */
     @Test
-    void testHocon() throws IOException {
-        // All of this is needed because write expects serializable values and 
only concrete classes are serializable
-        HashMap<String, ArrayList<String>> map = new HashMap<>(Map.of("list", 
new ArrayList<>(List.of("val1", "val2"))));
-        var data = Map.of("foo1", "bar1", "foo2", "bar2", "map", map);
-
-        assertThat(storage.write(data, 0), willBe(true));
-
-        String contents = Files.readString(getConfigFile());
-
-        // \n instead of System.lineSeparator because Config library writes \n 
only
-        assertThat(contents, is("foo1=bar1\n"
-                + "foo2=bar2\n"
-                + "map {\n"
-                + "    list=[\n"
-                + "        val1,\n"
-                + "        val2\n"
+    void remove() throws Exception {
+        // Given
+        var topConfiguration = (TopConfiguration) 
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+
+        topConfiguration.namedList().change(b -> {
+            b.create("name1", x -> {
+                x.changeStrVal("strVal1");
+                x.changeIntVal(-1);
+            });
+            b.create("name2", x -> {
+                x.changeStrVal("strVal2");
+                x.changeIntVal(-2);
+            });
+        }).get();
+
+        topConfiguration.shortVal().update((short) 3).get();
+        // And values are saved to file
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-1\n"
+                        + "            name=name1\n"
+                        + "            strVal=strVal1\n"
+                        + "        },\n"
+                        + "        {\n"
+                        + "            intVal=-2\n"
+                        + "            name=name2\n"
+                        + "            strVal=strVal2\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "    shortVal=3\n"
+                        + "}\n"
+        ));
+
+        // When remove named list entity
+        topConfiguration.namedList().change(b -> b.delete("name1")).get();
+        // And
+        var storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(6), 
Matchers.not(hasValue("strVal1"))));
+        // And entity removed from file
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-2\n"
+                        + "            name=name2\n"
+                        + "            strVal=strVal2\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "    shortVal=3\n"
+                        + "}\n"
+        ));
+
+        // When remove the last entity
+        topConfiguration.namedList().change(b -> b.delete("name2")).get();
+        // And
+        storageValues = readAllLatest();
+
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+        // And entity removed from file
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    shortVal=3\n"
+                        + "}\n"
+        ));
+    }
+
+    /** Delete file before read on recovery. */
+    @Test
+    void deleteFileBeforeReadOnRecovery() throws IOException {
+        // Given
+        Files.delete(getConfigFile());
+
+        // When
+        var storageValues = storage.readDataOnRecovery().join().values();
+
+        // Then
+        assertThat(storageValues.entrySet(), hasSize(0));
+        // And empty file was created
+        assertThat(configFileContent(), equalTo(""));
+    }
+
+
+    /** File content is not changed when read data on recovery. */
+    @Test
+    void fileContentIsNotChanged() throws IOException {
+        // Given
+        String fileContent = "top {\n"
+                + "    namedList=[\n"
+                + "        {\n"
+                + "            intVal=-1\n"
+                + "            name=name1\n"
+                + "        }\n"
                 + "    ]\n"
-                + "}\n"));
+                + "}\n";
+
+        Files.write(getConfigFile(), 
fileContent.getBytes(StandardCharsets.UTF_8));
+
+        // When
+        var storageValues = storage.readDataOnRecovery().join().values();
+        // Then
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue("foo"))); // 
default value
+        // And file was not changed
+        assertThat(configFileContent(), equalTo(fileContent));
     }
 
+    /** Delete file before read all. */
     @Test
-    void testMergeHocon() throws IOException {
-        var data = Map.of("foo1", "bar");
-        assertThat(storage.write(data, 0), willBe(true));
+    void deleteFileBeforeReadAll() throws Exception {
+        // Given
+        Files.delete(getConfigFile());
+
+        // When
+        var storageValues = readAllLatest();
 
-        var append = Map.of("foo1", "baz", "foo2", "bar");
-        assertThat(storage.write(append, 1), willBe(true));
+        // Then
+        assertThat(storageValues.entrySet(), hasSize(0));
+        // And there is no file
+        assertThat(Files.exists(getConfigFile()), is(false));
 
-        String contents = Files.readString(getConfigFile());
-        assertThat(contents, is("foo1=baz\n"
-                + "foo2=bar\n"));
+        // When update configuration
+        var topConfiguration = (TopConfiguration) 
treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
+        topConfiguration.namedList().change(b -> b.create("name1", x -> {
+            x.changeStrVal("strVal1");
+            x.changeIntVal(-1);
+        })).get();
+
+        // Then file is created
+        assertThat(configFileContent(), equalToCompressingWhiteSpace(
+                "top {\n"
+                        + "    namedList=[\n"
+                        + "        {\n"
+                        + "            intVal=-1\n"
+                        + "            name=name1\n"
+                        + "            strVal=strVal1\n"
+                        + "        }\n"
+                        + "    ]\n"
+                        + "}\n"
+        ));
     }
 
-    private Path getConfigFile() {
-        return tmpDir.resolve(CONFIG_NAME);
+    /** Read configuration when inner node configured with partial content 
(some fields are empty). */
+    @Test
+    void innerNodeWithPartialContent() throws Exception {
+        // Given
+        String content = "top: { inner.boolVal: true }";
+        Files.write(getConfigFile(), content.getBytes(StandardCharsets.UTF_8));
+
+        // Expect
+        assertThat(storage.readDataOnRecovery().get().values(), 
aMapWithSize(1));
+    }
+
+    private String configFileContent() throws IOException {
+        return Files.readString(getConfigFile());
+    }
+
+    private Map<String, ? extends Serializable> readAllLatest() {
+        return storage.readAllLatest("").join();
+    }
+
+    /** Root that has a single named list. */
+    @ConfigurationRoot(rootName = "top")
+    public static class TopConfigurationSchema {
+        @NamedConfigValue
+        public NamedListConfigurationSchema namedList;
+
+        @ConfigValue
+        public InnerConfigurationSchema inner;
+
+        @Value(hasDefault = true)
+        public short shortVal = 1;
+    }
+
+
+    /** Empty root that is needed to test empty configuration root rendering 
to file. */
+    @ConfigurationRoot(rootName = "emptyTop")
+    public static class TopEmptyConfigurationSchema {
+        @Value(hasDefault = true)
+        public short ignore = 1;
+    }
+
+    /** Inner config to test that it won't be saved to the file if not changed 
by the user.*/
+    @Config
+    public static class InnerConfigurationSchema {
+        @Value(hasDefault = true)
+        public String strVal = "foo";
+
+        @Value(hasDefault = true)
+        public boolean boolVal = false;
+
+        @ConfigValue
+        public NamedListConfigurationSchema someConfigurationValue;
+    }
+
+    /** Named list element node that contains another named list. */
+    @Config
+    public static class NamedListConfigurationSchema {
+        @Value(hasDefault = true)
+        public String strVal = "foo";
+
+        @Value(hasDefault = true)
+        public int intVal = 1;
     }
 }

Reply via email to