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/02/19 10:00:02 UTC

[ignite-3] branch main updated: IGNITE-14194 Multiple storages support for configuration framework. (#55)

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 ca0d35e  IGNITE-14194 Multiple storages support for configuration framework. (#55)
ca0d35e is described below

commit ca0d35ee95260a839b69d7e5b46717a3c4905393
Author: ibessonov <be...@gmail.com>
AuthorDate: Fri Feb 19 12:59:52 2021 +0300

    IGNITE-14194 Multiple storages support for configuration framework. (#55)
---
 .../processor/internal/Processor.java              |  51 +++--
 .../internal/util/ConfigurationUtilTest.java       |  32 +++-
 .../sample/storage/ConfigurationChangerTest.java   |  69 +++----
 .../sample/storage/TestConfigurationStorage.java   |   6 +-
 .../ignite/configuration/ConfigurationChanger.java | 206 ++++++++++++++++-----
 .../configuration/ConfigurationRegistry.java       |  20 ++
 .../org/apache/ignite/configuration/RootKey.java   |  13 +-
 .../apache/ignite/configuration/RootKeyImpl.java   |  56 ++++++
 .../internal/util/ConfigurationUtil.java           |  69 ++++++-
 .../storage/ConfigurationStorage.java              |   2 +-
 .../apache/ignite/configuration/storage/Data.java  |   6 +-
 .../ignite/configuration/tree/InnerNode.java       |   2 +-
 12 files changed, 413 insertions(+), 119 deletions(-)

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 ef1797e..33a85d5 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
@@ -34,6 +34,7 @@ import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -56,7 +57,10 @@ import javax.lang.model.element.Modifier;
 import javax.lang.model.element.PackageElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypesException;
+import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Elements;
+import org.apache.ignite.configuration.ConfigurationRegistry;
 import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.ConfigurationValue;
 import org.apache.ignite.configuration.Configurator;
@@ -148,11 +152,12 @@ public class Processor extends AbstractProcessor {
         String packageForUtil = "";
 
         // All classes annotated with @Config
-        final Set<TypeElement> annotatedConfigs = roundEnvironment
+        final List<TypeElement> annotatedConfigs = roundEnvironment
             .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class)).stream()
             .filter(element -> element.getKind() == ElementKind.CLASS)
             .map(TypeElement.class::cast)
-            .collect(Collectors.toSet());
+            .sorted(Comparator.comparing((TypeElement element) -> element.getQualifiedName().toString()).reversed())
+            .collect(Collectors.toList());
 
         if (annotatedConfigs.isEmpty())
             return false;
@@ -317,8 +322,18 @@ public class Processor extends AbstractProcessor {
             // Create VIEW, INIT and CHANGE classes
             createPojoBindings(packageName, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
 
-            if (isRoot)
-                createRootKeyField(configInterface, configurationInterfaceBuilder, configDesc);
+            if (isRoot) {
+                TypeMirror storageType = null;
+
+                try {
+                    rootAnnotation.storage();
+                }
+                catch (MirroredTypesException e) {
+                    storageType = e.getTypeMirrors().get(0);
+                }
+
+                createRootKeyField(configInterface, configurationInterfaceBuilder, configDesc, storageType, schemaClassName);
+            }
 
             // Create constructors for configuration class
             createConstructors(configClass, configName, configurationClassBuilder, CONFIGURATOR_TYPE, constructorBodyBuilder, copyConstructorBodyBuilder);
@@ -363,23 +378,20 @@ public class Processor extends AbstractProcessor {
     /** */
     private void createRootKeyField(ClassName configInterface,
         TypeSpec.Builder configurationClassBuilder,
-        ConfigurationDescription configDesc) {
-
+        ConfigurationDescription configDesc,
+        TypeMirror storageType,
+        ClassName schemaClassName
+    ) {
         ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ClassName.get(RootKey.class), configInterface);
 
-        TypeSpec anonymousClass = TypeSpec.anonymousClassBuilder("")
-            .addSuperinterface(fieldTypeName)
-            .addMethod(MethodSpec
-                .methodBuilder("key")
-                .addAnnotation(Override.class)
-                .addModifiers(PUBLIC)
-                .returns(TypeName.get(String.class))
-                .addStatement("return $S", configDesc.getName())
-                .build()).build();
+        ClassName nodeClassName = ClassName.get(
+            schemaClassName.packageName() + ".impl",
+            schemaClassName.simpleName().replace("ConfigurationSchema", "Node")
+        );
 
         FieldSpec keyField = FieldSpec.builder(
             fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
-            .initializer("$L", anonymousClass)
+            .initializer("$T.newRootKey($S, $T.class, $T::new)", ConfigurationRegistry.class, configDesc.getName(), storageType, nodeClassName)
             .build();
 
         configurationClassBuilder.addField(keyField);
@@ -858,14 +870,15 @@ public class Processor extends AbstractProcessor {
             .addSuperinterface(changeClsName)
             .addSuperinterface(initClsName);
 
+        TypeVariableName t = TypeVariableName.get("T");
+
         MethodSpec.Builder traverseChildrenBuilder = MethodSpec.methodBuilder("traverseChildren")
             .addAnnotation(Override.class)
             .addJavadoc("{@inheritDoc}")
             .addModifiers(PUBLIC)
+            .addTypeVariable(t)
             .returns(TypeName.VOID)
-            .addParameter(ClassName.get(ConfigurationVisitor.class), "visitor");
-
-        TypeVariableName t = TypeVariableName.get("T");
+            .addParameter(ParameterizedTypeName.get(ClassName.get(ConfigurationVisitor.class), t), "visitor");
 
         MethodSpec.Builder traverseChildBuilder = MethodSpec.methodBuilder("traverseChild")
             .addAnnotation(Override.class)
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 c88e5eb..a989733 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
@@ -176,10 +176,34 @@ public class ConfigurationUtilTest {
 
     /** */
     @Test
-    public void fillFromSuffixMapSuccessfully() {
+    public void toPrefixMap() {
+        assertEquals(
+            Map.of("foo", 42),
+            ConfigurationUtil.toPrefixMap(Map.of("foo", 42))
+        );
+
+        assertEquals(
+            Map.of("foo.bar", 42),
+            ConfigurationUtil.toPrefixMap(Map.of("foo\\.bar", 42))
+        );
+
+        assertEquals(
+            Map.of("foo", Map.of("bar1", 10, "bar2", 20)),
+            ConfigurationUtil.toPrefixMap(Map.of("foo.bar1", 10, "foo.bar2", 20))
+        );
+
+        assertEquals(
+            Map.of("root1", Map.of("leaf1", 10), "root2", Map.of("leaf2", 20)),
+            ConfigurationUtil.toPrefixMap(Map.of("root1.leaf1", 10, "root2.leaf2", 20))
+        );
+    }
+
+    /** */
+    @Test
+    public void fillFromPrefixMapSuccessfully() {
         var parentNode = new ParentNode();
 
-        ConfigurationUtil.fillFromSuffixMap(parentNode, Map.of(
+        ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
             "elements", Map.of(
                 "name1", Map.of(
                     "child", Map.of("str", "value1")
@@ -196,14 +220,14 @@ public class ConfigurationUtilTest {
 
     /** */
     @Test
-    public void fillFromSuffixMapSuccessfullyWithRemove() {
+    public void fillFromPrefixMapSuccessfullyWithRemove() {
         var parentNode = new ParentNode().changeElements(elements ->
             elements.update("name", element ->
                 element.changeChild(child -> {})
             )
         );
 
-        ConfigurationUtil.fillFromSuffixMap(parentNode, Map.of(
+        ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
             "elements", singletonMap("name", null)
         ));
 
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 976efa8..494fb67 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
@@ -23,9 +23,9 @@ import java.util.concurrent.ExecutionException;
 import org.apache.ignite.configuration.ConfigurationChangeException;
 import org.apache.ignite.configuration.ConfigurationChanger;
 import org.apache.ignite.configuration.Configurator;
-import org.apache.ignite.configuration.RootKey;
 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.configuration.sample.storage.impl.ANode;
@@ -34,20 +34,17 @@ import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsMapContaining.hasEntry;
+import static org.apache.ignite.configuration.sample.storage.AConfiguration.KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * Test configuration changer.
  */
 public class ConfigurationChangerTest {
-    /** Root configuration key. */
-    private static final RootKey<?> KEY = () -> "key";
-
     /** */
-    @Config
+    @ConfigurationRoot(rootName = "key", storage = TestConfigurationStorage.class)
     public static class AConfigurationSchema {
         /** */
         @ConfigValue
@@ -93,19 +90,17 @@ public class ConfigurationChangerTest {
             .initElements(change -> change.create("a", init -> init.initStrCfg("1")));
 
         final ConfigurationChanger changer = new ConfigurationChanger(storage);
-        changer.init();
+        changer.init(KEY);
 
         changer.registerConfiguration(KEY, configurator);
 
         changer.change(Collections.singletonMap(KEY, data)).get();
 
-        final Data dataFromStorage = storage.readAll();
-        final Map<String, Serializable> dataMap = dataFromStorage.values();
+        ANode newRoot = (ANode)changer.getRootNode(KEY);
 
-        assertEquals(3, dataMap.size());
-        assertThat(dataMap, hasEntry("key.child.intCfg", 1));
-        assertThat(dataMap, hasEntry("key.child.strCfg", "1"));
-        assertThat(dataMap, hasEntry("key.elements.a.strCfg", "1"));
+        assertEquals(1, newRoot.child().intCfg());
+        assertEquals("1", newRoot.child().strCfg());
+        assertEquals("1", newRoot.elements().get("a").strCfg());
     }
 
     /**
@@ -130,10 +125,10 @@ public class ConfigurationChangerTest {
             );
 
         final ConfigurationChanger changer1 = new ConfigurationChanger(storage);
-        changer1.init();
+        changer1.init(KEY);
 
         final ConfigurationChanger changer2 = new ConfigurationChanger(storage);
-        changer2.init();
+        changer2.init(KEY);
 
         changer1.registerConfiguration(KEY, configurator);
         changer2.registerConfiguration(KEY, configurator);
@@ -141,14 +136,19 @@ public class ConfigurationChangerTest {
         changer1.change(Collections.singletonMap(KEY, data1)).get();
         changer2.change(Collections.singletonMap(KEY, data2)).get();
 
-        final Data dataFromStorage = storage.readAll();
-        final Map<String, Serializable> dataMap = dataFromStorage.values();
+        ANode newRoot1 = (ANode)changer1.getRootNode(KEY);
 
-        assertEquals(4, dataMap.size());
-        assertThat(dataMap, hasEntry("key.child.intCfg", 2));
-        assertThat(dataMap, hasEntry("key.child.strCfg", "2"));
-        assertThat(dataMap, hasEntry("key.elements.a.strCfg", "2"));
-        assertThat(dataMap, hasEntry("key.elements.b.strCfg", "2"));
+        assertEquals(2, newRoot1.child().intCfg());
+        assertEquals("2", newRoot1.child().strCfg());
+        assertEquals("2", newRoot1.elements().get("a").strCfg());
+        assertEquals("2", newRoot1.elements().get("b").strCfg());
+
+        ANode newRoot2 = (ANode)changer2.getRootNode(KEY);
+
+        assertEquals(2, newRoot2.child().intCfg());
+        assertEquals("2", newRoot2.child().strCfg());
+        assertEquals("2", newRoot2.elements().get("a").strCfg());
+        assertEquals("2", newRoot2.elements().get("b").strCfg());
     }
 
     /**
@@ -173,10 +173,10 @@ public class ConfigurationChangerTest {
             );
 
         final ConfigurationChanger changer1 = new ConfigurationChanger(storage);
-        changer1.init();
+        changer1.init(KEY);
 
         final ConfigurationChanger changer2 = new ConfigurationChanger(storage);
-        changer2.init();
+        changer2.init(KEY);
 
         changer1.registerConfiguration(KEY, configurator);
         changer2.registerConfiguration(KEY, configurator);
@@ -187,13 +187,11 @@ public class ConfigurationChangerTest {
 
         assertThrows(ExecutionException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)).get());
 
-        final Data dataFromStorage = storage.readAll();
-        final Map<String, Serializable> dataMap = dataFromStorage.values();
+        ANode newRoot = (ANode)changer2.getRootNode(KEY);
 
-        assertEquals(3, dataMap.size());
-        assertThat(dataMap, hasEntry("key.child.intCfg", 1));
-        assertThat(dataMap, hasEntry("key.child.strCfg", "1"));
-        assertThat(dataMap, hasEntry("key.elements.a.strCfg", "1"));
+        assertEquals(1, newRoot.child().intCfg());
+        assertEquals("1", newRoot.child().strCfg());
+        assertEquals("1", newRoot.elements().get("a").strCfg());
     }
 
     /**
@@ -206,17 +204,17 @@ public class ConfigurationChangerTest {
         final ConfiguratorController configuratorController = new ConfiguratorController();
         final Configurator<?> configurator = configuratorController.configurator();
 
-        ANode data = new ANode();
+        ANode data = new ANode().initChild(child -> child.initIntCfg(1));
 
         final ConfigurationChanger changer = new ConfigurationChanger(storage);
 
         storage.fail(true);
 
-        assertThrows(ConfigurationChangeException.class, changer::init);
+        assertThrows(ConfigurationChangeException.class, () -> changer.init(KEY));
 
         storage.fail(false);
 
-        changer.init();
+        changer.init(KEY);
 
         changer.registerConfiguration(KEY, configurator);
 
@@ -230,6 +228,9 @@ public class ConfigurationChangerTest {
         final Map<String, Serializable> dataMap = dataFromStorage.values();
 
         assertEquals(0, dataMap.size());
+
+        ANode newRoot = (ANode)changer.getRootNode(KEY);
+        assertNull(newRoot.child());
     }
 
     /**
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
index a9f3d32..f29a6af 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
@@ -24,7 +24,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import org.apache.ignite.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.configuration.storage.ConfigurationStorageListener;
 import org.apache.ignite.configuration.storage.Data;
@@ -41,7 +41,7 @@ public class TestConfigurationStorage implements ConfigurationStorage {
     private List<ConfigurationStorageListener> listeners = new ArrayList<>();
 
     /** Storage version. */
-    private AtomicInteger version = new AtomicInteger(0);
+    private AtomicLong version = new AtomicLong(0);
 
     /** Should fail on every operation. */
     private boolean fail = false;
@@ -63,7 +63,7 @@ public class TestConfigurationStorage implements ConfigurationStorage {
     }
 
     /** {@inheritDoc} */
-    @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, int sentVersion) throws StorageException {
+    @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long sentVersion) throws StorageException {
         if (fail)
             return CompletableFuture.failedFuture(new StorageException("Failed to write data"));
 
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 b840246..fa156cb 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
@@ -18,16 +18,21 @@ package org.apache.ignite.configuration;
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.atomic.AtomicInteger;
 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.InnerNode;
 import org.apache.ignite.configuration.tree.TraversableTreeNode;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.configuration.validation.ValidationIssue;
@@ -42,37 +47,90 @@ public class ConfigurationChanger {
     private final ForkJoinPool pool = new ForkJoinPool(2);
 
     /** Map of configurations' configurators. */
-    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+    @Deprecated
+    private final Map<RootKey<?>, Configurator<?>> configurators = new HashMap<>();
 
-    /** Storage. */
-    private ConfigurationStorage configurationStorage;
+    /** Map that has all the trees in accordance to their storages. */
+    private final Map<Class<? extends ConfigurationStorage>, StorageRoots> storagesRootsMap = new ConcurrentHashMap<>();
 
-    /** Changer's last known version of storage. */
-    private final AtomicInteger version = new AtomicInteger(0);
+    /**
+     * Immutable data container to store version and all roots associated with the specific storage.
+     */
+    public static class StorageRoots {
+        /** Immutable forest, so to say. */
+        private final Map<RootKey<?>, InnerNode> roots;
+
+        /** Version associated with the currently known storage state. */
+        private final long version;
+
+        /** */
+        private StorageRoots(Map<RootKey<?>, InnerNode> roots, long version) {
+            this.roots = Collections.unmodifiableMap(roots);
+            this.version = version;
+        }
+    }
+
+    /** Storage instances by their classes. Comes in handy when all you have is {@link RootKey}. */
+    private final Map<Class<? extends ConfigurationStorage>, ConfigurationStorage> storageInstances = new HashMap<>();
 
     /** Constructor. */
-    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
-        this.configurationStorage = configurationStorage;
+    public ConfigurationChanger(ConfigurationStorage... configurationStorages) {
+        for (ConfigurationStorage storage : configurationStorages)
+            storageInstances.put(storage.getClass(), storage);
     }
 
     /**
      * Initialize changer.
      */
-    public void init() throws ConfigurationChangeException {
-        final Data data;
+    // ConfigurationChangeException, really?
+    public void init(RootKey<?>... rootKeys) throws ConfigurationChangeException {
+        Map<Class<? extends ConfigurationStorage>, Set<RootKey<?>>> rootsByStorage = new HashMap<>();
 
-        try {
-            data = configurationStorage.readAll();
-        }
-        catch (StorageException e) {
-            throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
+        for (RootKey<?> rootKey : rootKeys) {
+            Class<? extends ConfigurationStorage> storageType = rootKey.getStorageType();
+
+            rootsByStorage.computeIfAbsent(storageType, c -> new HashSet<>()).add(rootKey);
         }
 
-        version.set(data.version());
+        for (ConfigurationStorage configurationStorage : storageInstances.values()) {
+            Data data;
+
+            try {
+                data = configurationStorage.readAll();
+            }
+            catch (StorageException e) {
+                throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
+            }
+
+            Map<RootKey<?>, InnerNode> storageRootsMap = new HashMap<>();
+
+            Map<String, ?> dataValuesPrefixMap = ConfigurationUtil.toPrefixMap(data.values());
 
-        configurationStorage.addListener(this::updateFromListener);
+            for (RootKey<?> rootKey : rootsByStorage.get(configurationStorage.getClass())) {
+                Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
 
-        // TODO: IGNITE-14118 iterate over data and fill Configurators
+                if (rootPrefixMap == null) {
+                    //TODO IGNITE-14193 Init with defaults.
+                    storageRootsMap.put(rootKey, rootKey.createRootNode());
+                }
+                else {
+                    InnerNode rootNode = rootKey.createRootNode();
+
+                    ConfigurationUtil.fillFromPrefixMap(rootNode, rootPrefixMap);
+
+                    storageRootsMap.put(rootKey, rootNode);
+                }
+            }
+
+            storagesRootsMap.put(configurationStorage.getClass(), new StorageRoots(storageRootsMap, data.version()));
+
+            configurationStorage.addListener(changedEntries -> updateFromListener(
+                configurationStorage.getClass(),
+                changedEntries
+            ));
+
+            // TODO: IGNITE-14118 iterate over data and fill Configurators
+        }
     }
 
     /**
@@ -80,8 +138,19 @@ public class ConfigurationChanger {
      * @param key Root configuration key of the configurator.
      * @param configurator Configuration's configurator.
      */
+    //TODO IGNITE-14183 Refactor, get rid of configurator and create some "validator".
+    @Deprecated
     public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
-        registry.put(key, configurator);
+        configurators.put(key, configurator);
+    }
+
+    /**
+     * Get root node by root key. Subject to revisiting.
+     *
+     * @param rootKey Root key.
+     */
+    public TraversableTreeNode getRootNode(RootKey<?> rootKey) {
+        return this.storagesRootsMap.get(rootKey.getStorageType()).roots.get(rootKey);
     }
 
     /**
@@ -89,9 +158,28 @@ public class ConfigurationChanger {
      * @param changes Map of changes by root key.
      */
     public CompletableFuture<Void> change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        if (changes.isEmpty())
+            return CompletableFuture.completedFuture(null);
+
+        Set<Class<? extends ConfigurationStorage>> storagesTypes = changes.keySet().stream()
+            .map(RootKey::getStorageType)
+            .collect(Collectors.toSet());
+
+        assert !storagesTypes.isEmpty();
+
+        if (storagesTypes.size() != 1) {
+            return CompletableFuture.failedFuture(
+                new ConfigurationChangeException("Cannot change configurations belonging to different roots")
+            );
+        }
+
+        Class<? extends ConfigurationStorage> storageType = storagesTypes.iterator().next();
+
+        ConfigurationStorage storage = storageInstances.get(storageType);
+
         CompletableFuture<Void> fut = new CompletableFuture<>();
 
-        pool.execute(() -> change0(changes, fut));
+        pool.execute(() -> change0(changes, storage, fut));
 
         return fut;
     }
@@ -99,15 +187,22 @@ public class ConfigurationChanger {
     /**
      * Internal configuration change method that completes provided future.
      * @param changes Map of changes by root key.
+     * @param storage Storage instance.
      * @param fut Future, that must be completed after changes are written to the storage.
      */
-    private void change0(Map<RootKey<?>, TraversableTreeNode> changes, CompletableFuture<?> fut) {
+    private void change0(
+        Map<RootKey<?>, TraversableTreeNode> changes,
+        ConfigurationStorage storage,
+        CompletableFuture<?> fut
+    ) {
         Map<String, Serializable> allChanges = changes.entrySet().stream()
             .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> nodeToFlatMap(change.getKey(), change.getValue()))
             .flatMap(map -> map.entrySet().stream())
             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 
-        final ValidationResult validationResult = validate(changes);
+        StorageRoots roots = storagesRootsMap.get(storage.getClass());
+
+        ValidationResult validationResult = validate(roots, changes);
 
         List<ValidationIssue> validationIssues = validationResult.issues();
 
@@ -117,9 +212,7 @@ public class ConfigurationChanger {
             return;
         }
 
-        final int version = validationResult.version();
-
-        CompletableFuture<Boolean> writeFut = configurationStorage.write(allChanges, version);
+        CompletableFuture<Boolean> writeFut = storage.write(allChanges, roots.version);
 
         writeFut.whenCompleteAsync((casResult, throwable) -> {
             if (throwable != null)
@@ -127,40 +220,70 @@ public class ConfigurationChanger {
             else if (casResult)
                 fut.complete(null);
             else
-                change0(changes, fut);
+                change0(changes, storage, fut);
         }, pool);
     }
 
     /**
      * Update configuration from storage listener.
+     * @param storageType Type of the storage that propagated these changes.
      * @param changedEntries Changed data.
      */
-    private synchronized void updateFromListener(Data changedEntries) {
-        // TODO: IGNITE-14118 add tree update
-        version.set(changedEntries.version());
+    private void updateFromListener(
+        Class<? extends ConfigurationStorage> storageType,
+        Data changedEntries
+    ) {
+        StorageRoots oldStorageRoots = this.storagesRootsMap.get(storageType);
+
+        Map<RootKey<?>, InnerNode> storageRootsMap = new HashMap<>(oldStorageRoots.roots);
+
+        Map<String, ?> dataValuesPrefixMap = ConfigurationUtil.toPrefixMap(changedEntries.values());
+
+        for (RootKey<?> rootKey : oldStorageRoots.roots.keySet()) {
+            //TODO IGNITE-14182 Remove is not yet supported here.
+            Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
+
+            if (rootPrefixMap != null) {
+                InnerNode rootNode = oldStorageRoots.roots.get(rootKey).copy();
+
+                ConfigurationUtil.fillFromPrefixMap(rootNode, rootPrefixMap);
+
+                storageRootsMap.put(rootKey, rootNode);
+            }
+        }
+
+        StorageRoots storageRoots = new StorageRoots(storageRootsMap, changedEntries.version());
+
+        storagesRootsMap.put(storageType, storageRoots);
+
+        //TODO IGNITE-14180 Notify listeners.
     }
 
     /**
      * Validate configuration changes.
+     *
+     * @param storageRoots Storage roots.
      * @param changes Configuration changes.
      * @return Validation results.
      */
-    private synchronized ValidationResult validate(Map<RootKey<?>, TraversableTreeNode> changes) {
-        final int version = this.version.get();
-
+    @SuppressWarnings("unused") // Will be used in the future, I promise (IGNITE-14183).
+    private ValidationResult validate(
+        StorageRoots storageRoots,
+        Map<RootKey<?>, TraversableTreeNode> changes
+    ) {
         List<ValidationIssue> issues = new ArrayList<>();
 
         for (Map.Entry<RootKey<?>, TraversableTreeNode> entry : changes.entrySet()) {
             RootKey<?> rootKey = entry.getKey();
             TraversableTreeNode changesForRoot = entry.getValue();
 
-            final Configurator<?> configurator = registry.get(rootKey);
+            final Configurator<?> configurator = configurators.get(rootKey);
 
             List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
             issues.addAll(list);
         }
 
-        return new ValidationResult(issues, version);
+        return new ValidationResult(issues);
     }
 
     /**
@@ -170,17 +293,12 @@ public class ConfigurationChanger {
         /** List of issues. */
         private final List<ValidationIssue> issues;
 
-        /** Version of configuration that changes were validated against. */
-        private final int version;
-
         /**
          * Constructor.
          * @param issues List of issues.
-         * @param version Version.
          */
-        private ValidationResult(List<ValidationIssue> issues, int version) {
+        private ValidationResult(List<ValidationIssue> issues) {
             this.issues = issues;
-            this.version = version;
         }
 
         /**
@@ -190,13 +308,5 @@ public class ConfigurationChanger {
         public List<ValidationIssue> issues() {
             return issues;
         }
-
-        /**
-         * Get version.
-         * @return Version.
-         */
-        public int version() {
-            return version;
-        }
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
index 356d8e5..7597805 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
@@ -20,7 +20,11 @@ package org.apache.ignite.configuration;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.tree.InnerNode;
 
 /** */
 public class ConfigurationRegistry {
@@ -43,4 +47,20 @@ public class ConfigurationRegistry {
     public Map<String, Configurator<? extends DynamicConfiguration<?, ?, ?>>> getConfigurators() {
         return Collections.unmodifiableMap(configs);
     }
+
+    /**
+     * Method to instantiate a new {@link RootKey} for your configuration root. Invoked in generated code only.
+     * Does not register this root anywhere, used for static object initialization only.
+     *
+     * @param rootName Name of the root as described in {@link ConfigurationRoot#rootName()}.
+     * @param storageType Storage class as descried in {@link ConfigurationRoot#storage()}.
+     * @param rootSupplier Closure to instantiate internal configuration tree roots.
+     */
+    public static <T extends ConfigurationTree<?, ?>> RootKey<T> newRootKey(
+        String rootName,
+        Class<? extends ConfigurationStorage> storageType,
+        Supplier<InnerNode> rootSupplier
+    ) {
+        return new RootKeyImpl<>(rootName, storageType, rootSupplier);
+    }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
index 488eb03..d253b05 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
@@ -17,8 +17,17 @@
 
 package org.apache.ignite.configuration;
 
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.tree.InnerNode;
+
 /** */
-public interface RootKey<T extends ConfigurationTree<?, ?>> {
+public abstract class RootKey<T extends ConfigurationTree<?, ?>> {
+    /** */
+    public abstract String key();
+
+    /** */
+    abstract Class<? extends ConfigurationStorage> getStorageType();
+
     /** */
-    public String key();
+    abstract InnerNode createRootNode();
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKeyImpl.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKeyImpl.java
new file mode 100644
index 0000000..61537c7
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKeyImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import java.util.function.Supplier;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.tree.InnerNode;
+
+/** */
+class RootKeyImpl<T extends ConfigurationTree<?, ?>> extends RootKey<T> {
+    /** */
+    private final String rootName;
+
+    /** */
+    private final Class<? extends ConfigurationStorage> storageType;
+
+    /** */
+    private final Supplier<InnerNode> rootSupplier;
+
+    /** */
+    RootKeyImpl(String rootName, Class<? extends ConfigurationStorage> storageType, Supplier<InnerNode> rootSupplier) {
+        this.rootName = rootName;
+        this.storageType = storageType;
+        this.rootSupplier = rootSupplier;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String key() {
+        return rootName;
+    }
+
+    /** {@inheritDoc} */
+    @Override Class<? extends ConfigurationStorage> getStorageType() {
+        return storageType;
+    }
+
+    /** {@inheritDoc} */
+    @Override InnerNode createRootNode() {
+        return rootSupplier.get();
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
index 79d569f..d77a3bc 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
@@ -59,7 +59,7 @@ public class ConfigurationUtil {
      * Splits string using unescaped {@code .} character as a separator.
      *
      * @param keys Qualified key where escaped subkeys are joined with dots.
-     * @return List of unescaped subkeys.
+     * @return Random access list of unescaped subkeys.
      * @see #unescape(String)
      * @see #join(List)
      */
@@ -136,6 +136,63 @@ public class ConfigurationUtil {
     }
 
     /**
+     * Converts raw map with dot-separated keys into a prefix map.
+     *
+     * @param rawConfig Original map.
+     * @return Prefix map.
+     * @see #split(String)
+     */
+    public static Map<String, ?> toPrefixMap(Map<String, Serializable> rawConfig) {
+        Map<String, Object> res = new HashMap<>();
+
+        for (Map.Entry<String, Serializable> entry : rawConfig.entrySet()) {
+            List<String> keys = split(entry.getKey());
+
+            assert keys instanceof RandomAccess : keys.getClass();
+
+            insert(res, keys, 0, entry.getValue());
+        }
+
+        return res;
+    }
+
+    /**
+     * Inserts value into the prefix by a given "path".
+     *
+     * @param map Output map.
+     * @param keys List of keys.
+     * @param idx Starting position in the {@code keys} list.
+     * @param val Value to be inserted.
+     */
+    private static void insert(Map<String, Object> map, List<String> keys, int idx, Serializable val) {
+        String key = keys.get(idx);
+
+        if (keys.size() == idx + 1) {
+            assert !map.containsKey(key) : map.get(key);
+
+            map.put(key, val);
+        }
+        else {
+            Object node = map.get(key);
+
+            Map<String, Object> submap;
+
+            if (node == null) {
+                submap = new HashMap<>();
+
+                map.put(key, submap);
+            }
+            else {
+                assert node instanceof Map : node;
+
+                submap = (Map<String, Object>)node;
+            }
+
+            insert(submap, keys, idx + 1, val);
+        }
+    }
+
+    /**
      * Convert Map tree to configuration tree. No error handling here.
      *
      * @param node Node to fill. Not necessarily empty.
@@ -144,7 +201,7 @@ public class ConfigurationUtil {
      * @throws UnsupportedOperationException if prefix map structure doesn't correspond to actual tree structure.
      *      This will be fixed when method is actually used in configuration storage intergration.
      */
-    public static void fillFromSuffixMap(ConstructableTreeNode node, Map<String, ?> prefixMap) {
+    public static void fillFromPrefixMap(ConstructableTreeNode node, Map<String, ?> prefixMap) {
         assert node instanceof InnerNode;
 
         /** */
@@ -201,8 +258,11 @@ public class ConfigurationUtil {
                         node.construct(key, null);
                     else if (val instanceof Map)
                         node.construct(key, new InnerConfigurationSource((Map<String, ?>)val));
-                    else
+                    else {
+                        assert val instanceof Serializable;
+
                         node.construct(key, new LeafConfigurationSource((Serializable)val));
+                    }
                 }
             }
         }
@@ -227,7 +287,8 @@ public class ConfigurationUtil {
 
             /** {@inheritDoc} */
             @Override public Void visitLeafNode(String key, Serializable val) {
-                values.put(currentKey.toString() + key, val);
+                if (val != null)
+                    values.put(currentKey.toString() + key, val);
 
                 return null;
             }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
index 386e500..b0cc047 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
@@ -39,7 +39,7 @@ public interface ConfigurationStorage {
      * @return Future that gives you {@code true} if successfully written, {@code false} if version of the storage is
      *      different from the passed argument and {@link StorageException} if failed to write data.
      */
-    CompletableFuture<Boolean> write(Map<String, Serializable> newValues, int version);
+    CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version);
 
     /**
      * Get all the keys of the configuration storage.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
index eea2afb..8c41d20 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
@@ -27,14 +27,14 @@ public class Data {
     private final Map<String, Serializable> values;
 
     /** Configuration storage version. */
-    private final int version;
+    private final long version;
 
     /**
      * Constructor.
      * @param values Values.
      * @param version Version.
      */
-    public Data(Map<String, Serializable> values, int version) {
+    public Data(Map<String, Serializable> values, long version) {
         this.values = values;
         this.version = version;
     }
@@ -51,7 +51,7 @@ public class Data {
      * Get version.
      * @return version.
      */
-    public int version() {
+    public long version() {
         return version;
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
index df45b42..ef5f588 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
@@ -44,7 +44,7 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
      *
      * @param visitor Configuration visitor.
      */
-    public abstract void traverseChildren(ConfigurationVisitor visitor);
+    public abstract <T> void traverseChildren(ConfigurationVisitor<T> visitor);
 
     /**
      * Method with auto-generated implementation. Must look like this: