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

[ignite-3] branch main updated: IGNITE-14193 Default values support for configuration framework (#61)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0b09ebc  IGNITE-14193 Default values support for configuration framework (#61)
0b09ebc is described below

commit 0b09ebc235709c27c14654841e6ba46cb99f27f3
Author: ibessonov <be...@gmail.com>
AuthorDate: Wed Mar 10 13:19:10 2021 +0300

    IGNITE-14193 Default values support for configuration framework (#61)
---
 .../processor/internal/ConfigSet.java              |  26 +++-
 .../internal/TestConfigurationSchema.java          |   8 +-
 .../processor/internal/Processor.java              |  49 ++++++--
 .../internal/util/ConfigurationUtilTest.java       |   2 +-
 .../sample/AutoAdjustConfigurationSchema.java      |   4 +-
 .../sample/CacheConfigurationSchema.java           |   3 +-
 .../sample/ConfigurationArrayTest.java             |   2 +-
 .../sample/ConstructableTreeNodeTest.java          |  32 +++++
 .../sample/DiscoveryConfigurationSchema.java       |   4 +-
 .../sample/NodeConfigurationSchema.java            |   7 +-
 .../sample/TraversableTreeNodeTest.java            |   8 +-
 .../sample/storage/ConfigurationChangerTest.java   |  54 +++++++-
 .../ignite/configuration/ConfigurationChanger.java | 136 ++++++++++++++++++---
 .../ignite/configuration/annotation/Value.java     |   6 +
 .../configuration/internal/ConfigurationNode.java  |   3 +
 .../configuration/tree/ConstructableTreeNode.java  |   9 ++
 .../ignite/configuration/tree/NamedListChange.java |   3 +
 .../ignite/configuration/tree/NamedListNode.java   |   7 +-
 .../configuration/RestConfigurationSchema.java     |   5 +-
 .../extended/AutoAdjustConfigurationSchema.java    |   5 +-
 .../extended/DataStorageConfigurationSchema.java   |   7 +-
 21 files changed, 321 insertions(+), 59 deletions(-)

diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
index 6b0ab23..4f5a425 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
@@ -18,9 +18,15 @@ package org.apache.ignite.configuration.processor.internal;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.Collection;
 import javax.tools.JavaFileObject;
 import org.apache.commons.io.IOUtils;
 import spoon.Launcher;
+import spoon.SpoonException;
+import spoon.compiler.SpoonResource;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtType;
+import spoon.support.compiler.VirtualFile;
 
 /**
  * Wrapper for generated classes of the configuration schema.
@@ -79,7 +85,7 @@ public class ConfigSet {
             throw new RuntimeException("Failed to parse class: " + e.getMessage(), e);
         }
 
-        return new ParsedClass(Launcher.parseClass(classFileContent));
+        return new ParsedClass(parseClass(classFileContent));
     }
 
     /**
@@ -97,4 +103,22 @@ public class ConfigSet {
     public ParsedClass getNodeClass() {
         return node;
     }
+
+    /**
+     * Butchered version of {@link Launcher#parseClass(String)}, because {@code spoon} is such a garbage it can't even
+     * parse valid classes without issues.
+     *
+     * @param code Code.
+     * @return AST.
+     */
+    private static CtClass<?> parseClass(String code) {
+        Launcher launcher = new Launcher();
+        launcher.addInputResource((SpoonResource)(new VirtualFile(code)));
+        launcher.getEnvironment().setNoClasspath(true);
+        launcher.getEnvironment().setAutoImports(true);
+        Collection<CtType<?>> allTypes = launcher.buildModel().getAllTypes();
+
+        // This is how we do "getLast" for streams. Pretty bad, I know.
+        return (CtClass)allTypes.stream().reduce((fst, snd) -> snd).orElseThrow(SpoonException::new);
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
index 90fe957..b0144ea 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
@@ -23,14 +23,14 @@ import org.apache.ignite.configuration.annotation.Value;
 @ConfigurationRoot(rootName = "test")
 public class TestConfigurationSchema {
     @Value
-    private String value1;
+    public String value1;
 
     @Value
-    private long primitiveLong;
+    public long primitiveLong;
 
     @Value
-    private int primitiveInt;
+    public int primitiveInt;
 
     @Value
-    private String[] stringArray;
+    public String[] stringArray;
 }
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
index 898346c..3b2715a 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -92,6 +92,9 @@ public class Processor extends AbstractProcessor {
     /** Wildcard (?) TypeName. */
     private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
 
+    /** Inherit doc javadoc. */
+    private static final String INHERIT_DOC = "{@inheritDoc}";
+
     /** Class file writer. */
     private Filer filer;
 
@@ -172,6 +175,8 @@ public class Processor extends AbstractProcessor {
             CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
 
             for (VariableElement field : fields) {
+                assert field.getModifiers().contains(PUBLIC) : clazz.getQualifiedName() + "#" + field.getSimpleName();
+
                 Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
 
                 // Get original field type (must be another configuration schema or "primitive" like String or long)
@@ -270,7 +275,7 @@ public class Processor extends AbstractProcessor {
             props.put(configClass, configDesc);
 
             // Create VIEW, INIT and CHANGE classes
-            createPojoBindings(fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
+            createPojoBindings(clazz, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
 
             if (isRoot) {
                 TypeMirror storageType = null;
@@ -342,7 +347,7 @@ public class Processor extends AbstractProcessor {
 
         MethodSpec.Builder getMethodBuilder = MethodSpec.methodBuilder(fieldName)
             .addAnnotation(Override.class)
-            .addJavadoc("{@inheritDoc}")
+            .addJavadoc(INHERIT_DOC)
             .addModifiers(PUBLIC, FINAL)
             .returns(types.getInterfaceGetMethodType());
 
@@ -501,11 +506,13 @@ public class Processor extends AbstractProcessor {
 
     /**
      * Create VIEW, INIT and CHANGE classes and methods.
+     * @param clazz Original class for the schema.
      * @param fields List of configuration fields.
      * @param schemaClassName Class name of schema.
      * @param configurationClassBuilder Configuration class builder.
      */
     private void createPojoBindings(
+        TypeElement clazz,
         List<VariableElement> fields,
         ClassName schemaClassName,
         TypeSpec.Builder configurationClassBuilder,
@@ -550,13 +557,18 @@ public class Processor extends AbstractProcessor {
             .superclass(ClassName.get(InnerNode.class))
             .addSuperinterface(viewClsName)
             .addSuperinterface(changeClsName)
-            .addSuperinterface(initClsName);
+            .addSuperinterface(initClsName)
+            // Cannot use "schemaClassName" here because it can't handle inner static classes.
+            .addField(FieldSpec.builder(ClassName.get(clazz), "_spec", PRIVATE, FINAL)
+                .initializer("new $T()", ClassName.get(clazz))
+                .build()
+            );
 
         TypeVariableName t = TypeVariableName.get("T");
 
         MethodSpec.Builder traverseChildrenBuilder = MethodSpec.methodBuilder("traverseChildren")
             .addAnnotation(Override.class)
-            .addJavadoc("{@inheritDoc}")
+            .addJavadoc(INHERIT_DOC)
             .addModifiers(PUBLIC)
             .addTypeVariable(t)
             .returns(TypeName.VOID)
@@ -564,7 +576,7 @@ public class Processor extends AbstractProcessor {
 
         MethodSpec.Builder traverseChildBuilder = MethodSpec.methodBuilder("traverseChild")
             .addAnnotation(Override.class)
-            .addJavadoc("{@inheritDoc}")
+            .addJavadoc(INHERIT_DOC)
             .addModifiers(PUBLIC)
             .addTypeVariable(t)
             .returns(t)
@@ -575,13 +587,23 @@ public class Processor extends AbstractProcessor {
 
         MethodSpec.Builder constructBuilder = MethodSpec.methodBuilder("construct")
             .addAnnotation(Override.class)
-            .addJavadoc("{@inheritDoc}")
+            .addJavadoc(INHERIT_DOC)
             .addModifiers(PUBLIC)
             .returns(TypeName.VOID)
+            .addException(NoSuchElementException.class)
             .addParameter(ClassName.get(String.class), "key")
             .addParameter(ClassName.get(ConfigurationSource.class), "src")
             .beginControlFlow("switch (key)");
 
+        MethodSpec.Builder constructDefaultBuilder = MethodSpec.methodBuilder("constructDefault")
+            .addAnnotation(Override.class)
+            .addJavadoc(INHERIT_DOC)
+            .addModifiers(PUBLIC)
+            .returns(TypeName.BOOLEAN)
+            .addException(NoSuchElementException.class)
+            .addParameter(ClassName.get(String.class), "key")
+            .beginControlFlow("switch (key)");
+
         ClassName consumerClsName = ClassName.get(Consumer.class);
 
         for (VariableElement field : fields) {
@@ -817,6 +839,14 @@ public class Processor extends AbstractProcessor {
                         schemaFieldType.box()
                     )
                     .addStatement(INDENT + "break");
+
+                    if (valAnnotation.hasDefault()) {
+                        constructDefaultBuilder
+                            .addStatement("case $S: $L = _spec.$L", fieldName, fieldName, fieldName)
+                            .addStatement(INDENT + "return true");
+                    }
+                    else
+                        constructDefaultBuilder.addStatement("case $S: return false", fieldName);
                 }
                 else if (namedListField) {
                     constructBuilder
@@ -862,10 +892,15 @@ public class Processor extends AbstractProcessor {
             .addStatement("default: throw new $T(key)", NoSuchElementException.class)
             .endControlFlow();
 
+        constructDefaultBuilder
+            .addStatement("default: throw new $T(key)", NoSuchElementException.class)
+            .endControlFlow();
+
         nodeClsBuilder
             .addMethod(traverseChildrenBuilder.build())
             .addMethod(traverseChildBuilder.build())
-            .addMethod(constructBuilder.build());
+            .addMethod(constructBuilder.build())
+            .addMethod(constructDefaultBuilder.build());
 
         TypeSpec viewCls = viewClsBuilder.build();
         TypeSpec changeCls = changeClsBuilder.build();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
index a989733..0a0fa7a 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
@@ -93,7 +93,7 @@ public class ConfigurationUtilTest {
     public static class ChildConfigurationSchema {
         /** */
         @Value
-        private String str;
+        public String str;
     }
 
     /** */
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
index ef3c02b..86245a5 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
@@ -34,9 +34,9 @@ public class AutoAdjustConfigurationSchema {
     @Min(value = 0, message = "Minimal is 0")
     @Validate(value = AutoAdjustValidator.class, message = "a")
     @Validate(value = AutoAdjustValidator2.class, message = "b")
-    private long timeout;
+    public long timeout;
 
     /** Enabled. */
     @Value
-    private boolean enabled;
+    public boolean enabled;
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
index 8d35e66..5a04640 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
@@ -29,6 +29,5 @@ public class CacheConfigurationSchema {
     /** Size. */
     @Value
     @Min(value = 1, message = "Minimal cache size is 1")
-    private int size;
-
+    public int size;
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
index a565653..4f9929e 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
@@ -38,7 +38,7 @@ public class ConfigurationArrayTest {
     public static class TestArrayConfigurationSchema {
         /** */
         @Value
-        private String[] array;
+        public String[] array;
     }
 
     /**
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
index e1e8421..12f260d 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
@@ -28,10 +28,12 @@ import org.apache.ignite.configuration.tree.NamedListNode;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /** */
 public class ConstructableTreeNodeTest {
@@ -144,4 +146,34 @@ public class ConstructableTreeNodeTest {
 
         assertEquals("value", elementsNode.get("name").strCfg());
     }
+
+    /** */
+    @Test
+    public void constructDefault() {
+        // Inner node with no leaves.
+        var parentNode = new ParentNode();
+
+        assertThrows(NoSuchElementException.class, () -> parentNode.constructDefault("child"));
+        assertThrows(NoSuchElementException.class, () -> parentNode.constructDefault("elements"));
+
+        // Inner node with a leaf.
+        parentNode.changeElements(elements -> elements.create("name", element -> {}));
+
+        NamedElementNode elementNode = parentNode.elements().get("name");
+
+        assertFalse(elementNode.constructDefault("strCfg"));
+
+        // Another inner node with leaves.
+        parentNode.changeChild(child -> {});
+
+        ChildNode child = parentNode.child();
+
+        assertFalse(child.constructDefault("strCfg"));
+
+        assertThrows(NullPointerException.class, () -> child.intCfg());
+
+        assertTrue(child.constructDefault("intCfg"));
+
+        assertEquals(99, child.intCfg());
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
index 289878d..e709c21 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
@@ -27,9 +27,9 @@ import org.apache.ignite.configuration.annotation.Value;
 public class DiscoveryConfigurationSchema {
     /** Node failure detection timeout. */
     @Value
-    private int failureDetectionTimeout;
+    public int failureDetectionTimeout;
 
     /** Node join timeout. */
     @Value
-    private int joinTimeout;
+    public int joinTimeout;
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
index 74d2d48..df21d02 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
@@ -31,15 +31,14 @@ public class NodeConfigurationSchema {
     /** Consistent id. */
     @Value(immutable = true)
     @NotNull(message = "Consistent id must not be null")
-    private String consistentId;
+    public String consistentId;
 
     /** Port. */
     @Value
-    private int port;
+    public int port;
 
     /** Auto adjust enabled. */
     @Value
     @Validate(NodeValidator.class)
-    private boolean autoAdjustEnabled;
-
+    public boolean autoAdjustEnabled;
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
index 214a173..6248654 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
@@ -61,12 +61,12 @@ public class TraversableTreeNodeTest {
     @Config
     public static class ChildConfigurationSchema {
         /** */
-        @Value(immutable = true)
-        private int intCfg;
+        @Value(immutable = true, hasDefault = true)
+        public int intCfg = 99;
 
         /** */
         @Value
-        private String strCfg;
+        public String strCfg;
     }
 
     /** */
@@ -74,7 +74,7 @@ public class TraversableTreeNodeTest {
     public static class NamedElementConfigurationSchema {
         /** */
         @Value
-        private String strCfg;
+        public String strCfg;
     }
 
     /** */
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
index 60d4b0a..d35f4c2 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
@@ -29,6 +29,7 @@ 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.configuration.sample.storage.impl.ANode;
+import org.apache.ignite.configuration.sample.storage.impl.DefaultsNode;
 import org.apache.ignite.configuration.storage.Data;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.junit.jupiter.api.Test;
@@ -61,11 +62,11 @@ public class ConfigurationChangerTest {
     public static class BConfigurationSchema {
         /** */
         @Value(immutable = true)
-        private int intCfg;
+        public int intCfg;
 
         /** */
         @Value
-        private String strCfg;
+        public String strCfg;
     }
 
     /** */
@@ -73,7 +74,7 @@ public class ConfigurationChangerTest {
     public static class CConfigurationSchema {
         /** */
         @Value
-        private String strCfg;
+        public String strCfg;
     }
 
     /**
@@ -234,6 +235,53 @@ public class ConfigurationChangerTest {
         assertNull(newRoot.child());
     }
 
+    /** */
+    @ConfigurationRoot(rootName = "def", storage = TestConfigurationStorage.class)
+    public static class DefaultsConfigurationSchema {
+        /** */
+        @ConfigValue
+        private DefaultsChildConfigurationSchema child;
+
+        /** */
+        @NamedConfigValue
+        private DefaultsChildConfigurationSchema childsList;
+
+        /** */
+        @Value(hasDefault = true)
+        public String defStr = "foo";
+    }
+
+    /** */
+    @Config
+    public static class DefaultsChildConfigurationSchema {
+        /** */
+        @Value(hasDefault = true)
+        public String defStr = "bar";
+    }
+
+    @Test
+    public void defaultsOnInit() throws Exception {
+        var changer = new ConfigurationChanger();
+
+        changer.addRootKey(DefaultsConfiguration.KEY);
+
+        changer.init(new TestConfigurationStorage());
+
+        DefaultsNode root = (DefaultsNode)changer.getRootNode(DefaultsConfiguration.KEY);
+
+        assertEquals("foo", root.defStr());
+        assertEquals("bar", root.child().defStr());
+
+        // This is not init, move it to another test =(
+        changer.change(Map.of(DefaultsConfiguration.KEY, new DefaultsNode().changeChildsList(childs ->
+            childs.create("name", child -> {})
+        ))).get(1, SECONDS);
+
+        root = (DefaultsNode)changer.getRootNode(DefaultsConfiguration.KEY);
+
+        assertEquals("bar", root.childsList().get("name").defStr());
+    }
+
     /**
      * Wrapper for Configurator mock to control validation.
      */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
index b6f91fb..1934f72 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
@@ -27,13 +27,17 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
 import java.util.stream.Collectors;
 import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
 import org.apache.ignite.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.configuration.storage.Data;
 import org.apache.ignite.configuration.storage.StorageException;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
 import org.apache.ignite.configuration.tree.TraversableTreeNode;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.configuration.validation.ValidationIssue;
@@ -110,23 +114,26 @@ public class ConfigurationChanger {
         }
 
         Map<RootKey<?>, InnerNode> storageRootsMap = new HashMap<>();
+        // Map to collect defaults for not initialized configurations.
+        Map<RootKey<?>, InnerNode> storageDefaultsMap = new HashMap<>();
 
         Map<String, ?> dataValuesPrefixMap = ConfigurationUtil.toPrefixMap(data.values());
 
         for (RootKey<?> rootKey : storageRootKeys) {
             Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
 
-            if (rootPrefixMap == null) {
-                //TODO IGNITE-14193 Init with defaults.
-                storageRootsMap.put(rootKey, rootKey.createRootNode());
-            }
-            else {
-                InnerNode rootNode = rootKey.createRootNode();
+            InnerNode rootNode = rootKey.createRootNode();
 
+            if (rootPrefixMap != null)
                 ConfigurationUtil.fillFromPrefixMap(rootNode, rootPrefixMap);
 
-                storageRootsMap.put(rootKey, rootNode);
-            }
+            // Collecting defaults requires fresh new root.
+            InnerNode defaultsNode = rootKey.createRootNode();
+
+            addDefaults(rootNode, defaultsNode);
+
+            storageRootsMap.put(rootKey, rootNode);
+            storageDefaultsMap.put(rootKey, defaultsNode);
         }
 
         storagesRootsMap.put(configurationStorage.getClass(), new StorageRoots(storageRootsMap, data.version()));
@@ -135,6 +142,74 @@ public class ConfigurationChanger {
             configurationStorage.getClass(),
             changedEntries
         ));
+
+        // Do this strictly after adding listeners, otherwise we can lose these changes.
+        try {
+            //TODO IGNITE-14183 Do not write defaults that have not been validated. This can ruin everything.
+            change(storageDefaultsMap).get();
+        }
+        catch (InterruptedException | ExecutionException e) {
+            throw new ConfigurationChangeException("too bad", e);
+        }
+    }
+
+    /**
+     * Fill {@code dst} node with default values, required to complete {@code src} node.
+     * These two objects can be the same, this would mean that all {@code null} values of {@code scr} will be
+     * replaced with defaults if it's possible.
+     *
+     * @param src Source node.
+     * @param dst Destination node.
+     */
+    private void addDefaults(InnerNode src, InnerNode dst) {
+        src.traverseChildren(new ConfigurationVisitor<>() {
+            @Override public Object visitLeafNode(String key, Serializable val) {
+                // If source value is null then inititalise the same value on the destination node.
+                if (val == null)
+                    dst.constructDefault(key);
+
+                return null;
+            }
+
+            @Override public Object visitInnerNode(String key, InnerNode srcNode) {
+                // Instantiate field in destination node before doing something else.
+                // Not a big deal if it wasn't null.
+                dst.construct(key, new ConfigurationSource() {});
+
+                // Get that inner node from destination to continue the processing.
+                InnerNode dstNode = dst.traverseChild(key, new ConfigurationVisitor<>() {
+                    @Override public InnerNode visitInnerNode(String key, InnerNode dstNode) {
+                        return dstNode;
+                    }
+                });
+
+                // "dstNode" is guaranteed to not be null even if "src" and "dst" match.
+                // Null in "srcNode" means that we should initialize everything that we can in "dstNode"
+                // unconditionally. It's only possible if we pass it as a source as well.
+                addDefaults(srcNode == null ? dstNode : srcNode, dstNode);
+
+                return null;
+            }
+
+            @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> srcNamedList) {
+                // Here we don't need to preemptively initialise corresponsing field, because it can never be null.
+                NamedListNode<?> dstNamedList = dst.traverseChild(key, new ConfigurationVisitor<>() {
+                    @Override public <N extends InnerNode> NamedListNode<?> visitNamedListNode(String key, NamedListNode<N> dstNode) {
+                        return dstNode;
+                    }
+                });
+
+                for (String namedListKey : srcNamedList.namedListKeys()) {
+                    // But, in order to get non-null value from "dstNamedList.get(namedListKey)" we must explicitly
+                    // ensure its existance.
+                    dstNamedList.construct(namedListKey, new ConfigurationSource() {});
+
+                    addDefaults(srcNamedList.get(namedListKey), dstNamedList.get(namedListKey));
+                }
+
+                return null;
+            }
+        });
     }
 
     /**
@@ -161,7 +236,7 @@ public class ConfigurationChanger {
      * Change configuration.
      * @param changes Map of changes by root key.
      */
-    public CompletableFuture<Void> change(Map<RootKey<?>, TraversableTreeNode> changes) {
+    public CompletableFuture<Void> change(Map<RootKey<?>, ? extends TraversableTreeNode> changes) {
         if (changes.isEmpty())
             return CompletableFuture.completedFuture(null);
 
@@ -195,18 +270,45 @@ public class ConfigurationChanger {
      * @param fut Future, that must be completed after changes are written to the storage.
      */
     private void change0(
-        Map<RootKey<?>, TraversableTreeNode> changes,
+        Map<RootKey<?>, ? extends TraversableTreeNode> changes,
         ConfigurationStorage storage,
         CompletableFuture<?> fut
     ) {
+        StorageRoots storageRoots = storagesRootsMap.get(storage.getClass());
+
         Map<String, Serializable> allChanges = new HashMap<>();
 
-        for (Map.Entry<RootKey<?>, TraversableTreeNode> change : changes.entrySet())
-            allChanges.putAll(nodeToFlatMap(change.getKey(), getRootNode(change.getKey()), change.getValue()));
+        for (Map.Entry<RootKey<?>, ? extends TraversableTreeNode> entry : changes.entrySet()) {
+            RootKey<?> rootKey = entry.getKey();
+            TraversableTreeNode change = entry.getValue();
+
+            // It's important to get the root from "roots" object rather then "storageRootMap" or "getRootNode(...)".
+            InnerNode currentRootNode = storageRoots.roots.get(rootKey);
+
+            // These are changes explicitly provided by the client.
+            allChanges.putAll(nodeToFlatMap(rootKey, currentRootNode, change));
+
+            // It is necessary to reinitialize default values every time.
+            // Possible use case that explicitly requires it: creation of the same named list entry with slightly
+            // different set of values and different dynamic defaults at the same time.
+            InnerNode patchedRootNode = ConfigurationUtil.patch(currentRootNode, change);
+            InnerNode defaultsNode = rootKey.createRootNode();
 
-        StorageRoots roots = storagesRootsMap.get(storage.getClass());
+            addDefaults(patchedRootNode, defaultsNode);
+
+            // These are default values for non-initialized values, required to complete the configuration.
+            //TODO IGNITE-14183 Take these defaults into account during validation.
+            allChanges.putAll(nodeToFlatMap(rootKey, patchedRootNode, defaultsNode));
+        }
+
+        // Unlikely but still possible.
+        if (allChanges.isEmpty()) {
+            fut.complete(null);
+
+            return;
+        }
 
-        ValidationResult validationResult = validate(roots, changes);
+        ValidationResult validationResult = validate(storageRoots, changes);
 
         List<ValidationIssue> validationIssues = validationResult.issues();
 
@@ -216,7 +318,7 @@ public class ConfigurationChanger {
             return;
         }
 
-        CompletableFuture<Boolean> writeFut = storage.write(allChanges, roots.version);
+        CompletableFuture<Boolean> writeFut = storage.write(allChanges, storageRoots.version);
 
         writeFut.whenCompleteAsync((casResult, throwable) -> {
             if (throwable != null)
@@ -301,11 +403,11 @@ public class ConfigurationChanger {
     @SuppressWarnings("unused")
     private ValidationResult validate(
         StorageRoots storageRoots,
-        Map<RootKey<?>, TraversableTreeNode> changes
+        Map<RootKey<?>, ? extends TraversableTreeNode> changes
     ) {
         List<ValidationIssue> issues = new ArrayList<>();
 
-        for (Map.Entry<RootKey<?>, TraversableTreeNode> entry : changes.entrySet()) {
+        for (Map.Entry<RootKey<?>, ? extends TraversableTreeNode> entry : changes.entrySet()) {
             RootKey<?> rootKey = entry.getKey();
             TraversableTreeNode changesForRoot = entry.getValue();
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
index b39c484..a5efbb5 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
@@ -45,4 +45,10 @@ public @interface Value {
      * @return {@code true} if this value can only be initialized and can't be changed afterwards.
      */
     boolean immutable() default false;
+
+    /**
+     * Indicator that current cpnfoguration value has default value. Value itself is derived from instantiated object
+     * of corresponding schema type. This means that default is not necessarily a constant value.
+     */
+    boolean hasDefault() default false;
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
index 12461a8..bcee71a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
@@ -19,6 +19,7 @@ package org.apache.ignite.configuration.internal;
 
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import org.apache.ignite.configuration.ConfigurationChanger;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
@@ -68,6 +69,8 @@ public abstract class ConfigurationNode<VIEW> {
         this.key = key;
         this.rootKey = rootKey;
         this.changer = changer;
+
+        assert Objects.equals(rootKey.key(), keys.get(0));
     }
 
     /**
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
index 44b77d8..2584006 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
@@ -32,6 +32,15 @@ public interface ConstructableTreeNode {
     void construct(String key,/* boolean canMutate, */ ConfigurationSource src) throws NoSuchElementException;
 
     /**
+     * Assigns default value to the corresponding leaf. Defaults are gathered from configuration schema class.
+     *
+     * @param key Field name to be initialized.
+     * @return {@code true} if default value has been assigned, {@code false} otherwise.
+     * @throws NoSuchElementException If there's no such field or it is not a leaf value.
+     */
+    boolean constructDefault(String key) throws NoSuchElementException;
+
+    /**
      * Public equivalent of {@link Object#clone()} method. Creates a copy with effectively the same content.
      * Helps to preserve trees immutability after construction is completed.
      *
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListChange.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListChange.java
index 1dc15f6..1e57a55 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListChange.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListChange.java
@@ -40,4 +40,7 @@ public interface NamedListChange<Change, Init> extends NamedListInit<Init> {
      * @throws IllegalStateException If {@link #update(String, Consumer)} has been invoked with the same key previously.
      */
     NamedListChange<Change, Init> delete(String key) throws IllegalStateException;
+
+    /** {@inheritDoc} */
+    @Override NamedListChange<Change, Init> create(String key, Consumer<Init> valConsumer);
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
index 55ca78a..609abfa 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
@@ -95,7 +95,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
         return this;
     }
 
-    @Override public NamedListInit<N> create(String key, Consumer<N> valConsumer) {
+    @Override public NamedListChange<N, N> create(String key, Consumer<N> valConsumer) {
         Objects.requireNonNull(valConsumer, "valConsumer");
 
         N val = map.get(key);
@@ -124,6 +124,11 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
     }
 
     /** {@inheritDoc} */
+    @Override public boolean constructDefault(String key) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
     @Override public NamedListNode<N> copy() {
         return new NamedListNode<>(this);
     }
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
index 10c5e52..5cd7bc1 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java
@@ -23,14 +23,13 @@ import org.apache.ignite.configuration.annotation.Value;
 /**
  * Configuration schema for REST endpoint subtree.
  */
-@SuppressWarnings("PMD.UnusedPrivateField")
 @ConfigurationRoot(rootName = "rest")
 public class RestConfigurationSchema {
     /** */
     @Value
-    private int port;
+    public int port;
 
     /** */
     @Value
-    private int portRange;
+    public int portRange;
 }
diff --git a/modules/runner/src/main/java/org/apache/ignite/configuration/extended/AutoAdjustConfigurationSchema.java b/modules/runner/src/main/java/org/apache/ignite/configuration/extended/AutoAdjustConfigurationSchema.java
index e86df97..87ebef5 100644
--- a/modules/runner/src/main/java/org/apache/ignite/configuration/extended/AutoAdjustConfigurationSchema.java
+++ b/modules/runner/src/main/java/org/apache/ignite/configuration/extended/AutoAdjustConfigurationSchema.java
@@ -22,15 +22,14 @@ import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.Value;
 
 /** */
-@SuppressWarnings("PMD.UnusedPrivateField")
 @Config
 public class AutoAdjustConfigurationSchema {
     /** */
     @Value
-    private boolean enabled;
+    public boolean enabled;
 
     /** */
     @Value
     @Min(value = 0, message = "Minimum value is 0")
-    private int timeout;
+    public int timeout;
 }
diff --git a/modules/runner/src/main/java/org/apache/ignite/configuration/extended/DataStorageConfigurationSchema.java b/modules/runner/src/main/java/org/apache/ignite/configuration/extended/DataStorageConfigurationSchema.java
index f9d9b93..2a8b678 100644
--- a/modules/runner/src/main/java/org/apache/ignite/configuration/extended/DataStorageConfigurationSchema.java
+++ b/modules/runner/src/main/java/org/apache/ignite/configuration/extended/DataStorageConfigurationSchema.java
@@ -21,18 +21,17 @@ import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.Value;
 
 /** */
-@SuppressWarnings("PMD.UnusedPrivateField")
 @Config
 public class DataStorageConfigurationSchema {
     /** */
     @Value
-    private int pageSize;
+    public int pageSize;
 
     /** */
     @Value
-    private String storagePath;
+    public String storagePath;
 
     /** */
     @Value
-    private String walPath;
+    public String walPath;
 }