You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2021/02/01 13:11:12 UTC

[GitHub] [ignite-3] SammyVimes opened a new pull request #41: IGNITE-14094

SammyVimes opened a new pull request #41:
URL: https://github.com/apache/ignite-3/pull/41


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568485352



##########
File path: modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/configuration/ConfigurationStorageTest.java
##########
@@ -0,0 +1,208 @@
+package org.apache.ignite.configuration.sample.configuration;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+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.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.configuration.impl.ANode;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.hamcrest.collection.IsMapContaining;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConfigurationStorageTest {
+
+    private static final RootKey<?> KEY = () -> "key";
+    /** */
+    @Config
+    public static class AConfigurationSchema {
+        /** */
+        @ConfigValue
+        private BConfigurationSchema child;
+
+        /** */
+        @NamedConfigValue
+        private CConfigurationSchema elements;
+    }
+
+    /** */
+    @Config
+    public static class BConfigurationSchema {
+        /** */
+        @Value(immutable = true)
+        private int intCfg;
+
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    /** */
+    @Config
+    public static class CConfigurationSchema {
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    @Test
+    public void testPutGet() {
+        final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        final ConfigurationChanger changer = new ConfigurationChanger(storage);
+        changer.init();
+
+        changer.registerConfiguration(KEY, configuration);
+
+        changer.change(Collections.singletonMap(KEY, data));
+
+        final Data dataFromStorage = storage.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "1"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "1"));
+    }
+
+    @Test
+    public void testModifiedFromAnotherStorage() {
+        final TestConfigurationStorage.Storage singleSource = new TestConfigurationStorage.Storage();
+
+        final TestConfigurationStorage storage1 = new TestConfigurationStorage(singleSource);
+        final TestConfigurationStorage storage2 = new TestConfigurationStorage(singleSource);
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data1 = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        ANode data2 = new ANode()
+            .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+            .initElements(change -> change
+                .put("a", init -> init.initStrCfg("2"))
+                .put("b", init -> init.initStrCfg("2"))
+            );
+
+        final ConfigurationChanger changer1 = new ConfigurationChanger(storage1);
+        changer1.init();
+
+        final ConfigurationChanger changer2 = new ConfigurationChanger(storage2);
+        changer2.init();
+
+        changer1.registerConfiguration(KEY, configuration);
+        changer2.registerConfiguration(KEY, configuration);
+
+        changer1.change(Collections.singletonMap(KEY, data1));
+        changer2.change(Collections.singletonMap(KEY, data2));
+
+        final Data dataFromStorage = storage1.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(4, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 2));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "2"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "2"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.b.strCfg", "2"));
+    }
+
+    @Test
+    public void testModifiedFromAnotherStorageWithIncompatibleChanges() {
+        final TestConfigurationStorage.Storage singleSource = new TestConfigurationStorage.Storage();
+
+        final TestConfigurationStorage storage1 = new TestConfigurationStorage(singleSource);
+        final TestConfigurationStorage storage2 = new TestConfigurationStorage(singleSource);
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data1 = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        ANode data2 = new ANode()
+            .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+            .initElements(change -> change
+                .put("a", init -> init.initStrCfg("2"))
+                .put("b", init -> init.initStrCfg("2"))
+            );
+
+        final ConfigurationChanger changer1 = new ConfigurationChanger(storage1);
+        changer1.init();
+
+        final ConfigurationChanger changer2 = new ConfigurationChanger(storage2);
+        changer2.init();
+
+        changer1.registerConfiguration(KEY, configuration);
+        changer2.registerConfiguration(KEY, configuration);
+
+        changer1.change(Collections.singletonMap(KEY, data1));
+
+        con.setHasIssues(true);
+
+        assertThrows(ConfigurationValidationException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)));
+
+        final Data dataFromStorage = storage1.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "1"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "1"));
+    }
+
+    private static class DynamicConfigurationController {
+
+        final Configurator<?> configuration;
+
+        private boolean hasIssues;
+
+        public DynamicConfigurationController() {
+            this(false);
+        }
+
+        public DynamicConfigurationController(boolean hasIssues) {
+            this.hasIssues = hasIssues;
+
+            configuration = Mockito.mock(Configurator.class);
+
+            Mockito.when(configuration.validateChanges(Mockito.any())).then(mock -> {
+                if (this.hasIssues)
+                    return Collections.singletonList(new ValidationIssue());
+
+                return Collections.emptyList();
+            });
+        }
+
+        public void setHasIssues(boolean hasIssues) {
+            this.hasIssues = hasIssues;
+        }
+
+        public Configurator<?> getConfiguration() {
+            return configuration;
+        }
+    }
+
+}

Review comment:
       Please add empty line :)




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] SammyVimes closed pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
SammyVimes closed pull request #41:
URL: https://github.com/apache/ignite-3/pull/41


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568593458



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();

Review comment:
       I don't see how this version is synchronized with the one that you validated on. We'll discuss it later.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568481713



##########
File path: modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/configuration/ConfigurationStorageTest.java
##########
@@ -0,0 +1,208 @@
+package org.apache.ignite.configuration.sample.configuration;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+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.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.configuration.impl.ANode;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.hamcrest.collection.IsMapContaining;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConfigurationStorageTest {
+
+    private static final RootKey<?> KEY = () -> "key";
+    /** */
+    @Config
+    public static class AConfigurationSchema {
+        /** */
+        @ConfigValue
+        private BConfigurationSchema child;
+
+        /** */
+        @NamedConfigValue
+        private CConfigurationSchema elements;
+    }
+
+    /** */
+    @Config
+    public static class BConfigurationSchema {
+        /** */
+        @Value(immutable = true)
+        private int intCfg;
+
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    /** */
+    @Config
+    public static class CConfigurationSchema {
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    @Test
+    public void testPutGet() {
+        final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        final ConfigurationChanger changer = new ConfigurationChanger(storage);
+        changer.init();
+
+        changer.registerConfiguration(KEY, configuration);
+
+        changer.change(Collections.singletonMap(KEY, data));
+
+        final Data dataFromStorage = storage.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));

Review comment:
       I think we should use static imports for matchers




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568534741



##########
File path: modules/configuration-storage/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
##########
@@ -0,0 +1,43 @@
+/*
+ * 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.storage;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.concurrent.Future;
+
+public interface ConfigurationStorage {

Review comment:
       Looks like this and the next class can be removed, right?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568592244



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();
+
+            if (validationIssues.isEmpty())
+                success = configurationStorage.write(allChanges, version);
+            else
+                break;
+        }
+
+        if (!validationIssues.isEmpty())
+            throw new ConfigurationValidationException(validationIssues);
+    }
+
+    private List<ValidationIssue> validate(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);
+
+            List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
+            issues.addAll(list);
+        }
+
+        return issues;
+    }
+
+    private Map<String, Serializable> convertChangesToMap(RootKey<?> rootKey, TraversableTreeNode node) {
+        Map<String, Serializable> values = new HashMap<>();
+
+        node.accept(null, new ConfigurationVisitor() {
+
+            String currentKey = rootKey.key();
+
+            @Override public void visitLeafNode(String key, Serializable val) {
+                values.put(currentKey + "." + key, val);
+            }
+
+            @Override public void visitInnerNode(String key, InnerNode node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;

Review comment:
       Please use StringBuilder, concatenation in a recursive method is a bad idea.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568481713



##########
File path: modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/configuration/ConfigurationStorageTest.java
##########
@@ -0,0 +1,208 @@
+package org.apache.ignite.configuration.sample.configuration;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+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.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.configuration.impl.ANode;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.hamcrest.collection.IsMapContaining;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConfigurationStorageTest {
+
+    private static final RootKey<?> KEY = () -> "key";
+    /** */
+    @Config
+    public static class AConfigurationSchema {
+        /** */
+        @ConfigValue
+        private BConfigurationSchema child;
+
+        /** */
+        @NamedConfigValue
+        private CConfigurationSchema elements;
+    }
+
+    /** */
+    @Config
+    public static class BConfigurationSchema {
+        /** */
+        @Value(immutable = true)
+        private int intCfg;
+
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    /** */
+    @Config
+    public static class CConfigurationSchema {
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    @Test
+    public void testPutGet() {
+        final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        final ConfigurationChanger changer = new ConfigurationChanger(storage);
+        changer.init();
+
+        changer.registerConfiguration(KEY, configuration);
+
+        changer.change(Collections.singletonMap(KEY, data));
+
+        final Data dataFromStorage = storage.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));

Review comment:
       I think we should use static imports for matchers

##########
File path: modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/configuration/ConfigurationStorageTest.java
##########
@@ -0,0 +1,208 @@
+package org.apache.ignite.configuration.sample.configuration;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+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.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.configuration.impl.ANode;
+import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.hamcrest.collection.IsMapContaining;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ConfigurationStorageTest {
+
+    private static final RootKey<?> KEY = () -> "key";
+    /** */
+    @Config
+    public static class AConfigurationSchema {
+        /** */
+        @ConfigValue
+        private BConfigurationSchema child;
+
+        /** */
+        @NamedConfigValue
+        private CConfigurationSchema elements;
+    }
+
+    /** */
+    @Config
+    public static class BConfigurationSchema {
+        /** */
+        @Value(immutable = true)
+        private int intCfg;
+
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    /** */
+    @Config
+    public static class CConfigurationSchema {
+        /** */
+        @Value
+        private String strCfg;
+    }
+
+    @Test
+    public void testPutGet() {
+        final TestConfigurationStorage storage = new TestConfigurationStorage();
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        final ConfigurationChanger changer = new ConfigurationChanger(storage);
+        changer.init();
+
+        changer.registerConfiguration(KEY, configuration);
+
+        changer.change(Collections.singletonMap(KEY, data));
+
+        final Data dataFromStorage = storage.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "1"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "1"));
+    }
+
+    @Test
+    public void testModifiedFromAnotherStorage() {
+        final TestConfigurationStorage.Storage singleSource = new TestConfigurationStorage.Storage();
+
+        final TestConfigurationStorage storage1 = new TestConfigurationStorage(singleSource);
+        final TestConfigurationStorage storage2 = new TestConfigurationStorage(singleSource);
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data1 = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        ANode data2 = new ANode()
+            .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+            .initElements(change -> change
+                .put("a", init -> init.initStrCfg("2"))
+                .put("b", init -> init.initStrCfg("2"))
+            );
+
+        final ConfigurationChanger changer1 = new ConfigurationChanger(storage1);
+        changer1.init();
+
+        final ConfigurationChanger changer2 = new ConfigurationChanger(storage2);
+        changer2.init();
+
+        changer1.registerConfiguration(KEY, configuration);
+        changer2.registerConfiguration(KEY, configuration);
+
+        changer1.change(Collections.singletonMap(KEY, data1));
+        changer2.change(Collections.singletonMap(KEY, data2));
+
+        final Data dataFromStorage = storage1.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(4, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 2));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "2"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "2"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.b.strCfg", "2"));
+    }
+
+    @Test
+    public void testModifiedFromAnotherStorageWithIncompatibleChanges() {
+        final TestConfigurationStorage.Storage singleSource = new TestConfigurationStorage.Storage();
+
+        final TestConfigurationStorage storage1 = new TestConfigurationStorage(singleSource);
+        final TestConfigurationStorage storage2 = new TestConfigurationStorage(singleSource);
+
+        final DynamicConfigurationController con = new DynamicConfigurationController();
+
+        final Configurator<?> configuration = con.getConfiguration();
+
+        ANode data1 = new ANode()
+            .initChild(init -> init.initIntCfg(1).initStrCfg("1"))
+            .initElements(change -> change.put("a", init -> init.initStrCfg("1")));
+
+        ANode data2 = new ANode()
+            .initChild(init -> init.initIntCfg(2).initStrCfg("2"))
+            .initElements(change -> change
+                .put("a", init -> init.initStrCfg("2"))
+                .put("b", init -> init.initStrCfg("2"))
+            );
+
+        final ConfigurationChanger changer1 = new ConfigurationChanger(storage1);
+        changer1.init();
+
+        final ConfigurationChanger changer2 = new ConfigurationChanger(storage2);
+        changer2.init();
+
+        changer1.registerConfiguration(KEY, configuration);
+        changer2.registerConfiguration(KEY, configuration);
+
+        changer1.change(Collections.singletonMap(KEY, data1));
+
+        con.setHasIssues(true);
+
+        assertThrows(ConfigurationValidationException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)));
+
+        final Data dataFromStorage = storage1.readAll();
+        final Map<String, Serializable> dataMap = dataFromStorage.values();
+
+        assertEquals(3, dataMap.size());
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.intCfg", 1));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.child.strCfg", "1"));
+        assertThat(dataMap, IsMapContaining.hasEntry("key.elements.a.strCfg", "1"));
+    }
+
+    private static class DynamicConfigurationController {
+
+        final Configurator<?> configuration;
+
+        private boolean hasIssues;
+
+        public DynamicConfigurationController() {
+            this(false);
+        }
+
+        public DynamicConfigurationController(boolean hasIssues) {
+            this.hasIssues = hasIssues;
+
+            configuration = Mockito.mock(Configurator.class);
+
+            Mockito.when(configuration.validateChanges(Mockito.any())).then(mock -> {
+                if (this.hasIssues)
+                    return Collections.singletonList(new ValidationIssue());
+
+                return Collections.emptyList();
+            });
+        }
+
+        public void setHasIssues(boolean hasIssues) {
+            this.hasIssues = hasIssues;
+        }
+
+        public Configurator<?> getConfiguration() {
+            return configuration;
+        }
+    }
+
+}

Review comment:
       Please add empty line :)

##########
File path: modules/configuration-storage/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
##########
@@ -0,0 +1,43 @@
+/*
+ * 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.storage;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.concurrent.Future;
+
+public interface ConfigurationStorage {

Review comment:
       Looks like this and the next class can be removed, right?

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();
+
+            if (validationIssues.isEmpty())
+                success = configurationStorage.write(allChanges, version);
+            else
+                break;
+        }
+
+        if (!validationIssues.isEmpty())
+            throw new ConfigurationValidationException(validationIssues);
+    }
+
+    private List<ValidationIssue> validate(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);
+
+            List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
+            issues.addAll(list);
+        }
+
+        return issues;
+    }
+
+    private Map<String, Serializable> convertChangesToMap(RootKey<?> rootKey, TraversableTreeNode node) {
+        Map<String, Serializable> values = new HashMap<>();
+
+        node.accept(null, new ConfigurationVisitor() {
+
+            String currentKey = rootKey.key();
+
+            @Override public void visitLeafNode(String key, Serializable val) {
+                values.put(currentKey + "." + key, val);
+            }
+
+            @Override public void visitInnerNode(String key, InnerNode node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;

Review comment:
       Please use StringBuilder, concatenation in a recursive method is a bad idea.

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();
+
+            if (validationIssues.isEmpty())
+                success = configurationStorage.write(allChanges, version);
+            else
+                break;
+        }
+
+        if (!validationIssues.isEmpty())
+            throw new ConfigurationValidationException(validationIssues);
+    }
+
+    private List<ValidationIssue> validate(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);
+
+            List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
+            issues.addAll(list);
+        }
+
+        return issues;
+    }
+
+    private Map<String, Serializable> convertChangesToMap(RootKey<?> rootKey, TraversableTreeNode node) {
+        Map<String, Serializable> values = new HashMap<>();
+
+        node.accept(null, new ConfigurationVisitor() {
+
+            String currentKey = rootKey.key();
+
+            @Override public void visitLeafNode(String key, Serializable val) {
+                values.put(currentKey + "." + key, val);
+            }
+
+            @Override public void visitInnerNode(String key, InnerNode node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;
+
+                node.traverseChildren(this);
+
+                currentKey = previousKey;
+            }
+
+            @Override public <N extends InnerNode> void visitNamedListNode(String key, NamedListNode<N> node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;
+
+                for (String namedListKey : node.namedListKeys()) {
+                    String loopPreviousKey = currentKey;
+                    currentKey += "." + namedListKey;

Review comment:
       Keep in mind that keys in named lists must be escaped. Methods for that are locted in this PR right now: https://github.com/apache/ignite-3/pull/40/files

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();

Review comment:
       I don't see how this version is synchronized with the one that you validated on. We'll discuss it later.

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
##########
@@ -214,9 +205,13 @@ public T getRoot() {
         property.addListener(new PropertyListener<PROP, PROP>() {
             /** {@inheritDoc} */
             @Override public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
-                storage.save(key, newValue);
+//                storage.save(key, newValue);

Review comment:
       Yeah, about these listeners... I guess they'll be completely removed from the code, we'll discuss this later as well

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationEntry.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.storage;
+
+import java.io.Serializable;
+
+/**
+ * Key-value pair.
+ */
+public class ConfigurationEntry {

Review comment:
       I see two copies of the same class and it seems that none of them are actually used anywhere in code. Can you please remove both?

##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
##########
@@ -0,0 +1,4 @@
+package org.apache.ignite.configuration.validation;

Review comment:
       File without a license...
   You can ask me how to set up automatic license header creation so you won't have to do it manually.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568594172



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
##########
@@ -214,9 +205,13 @@ public T getRoot() {
         property.addListener(new PropertyListener<PROP, PROP>() {
             /** {@inheritDoc} */
             @Override public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
-                storage.save(key, newValue);
+//                storage.save(key, newValue);

Review comment:
       Yeah, about these listeners... I guess they'll be completely removed from the code, we'll discuss this later as well




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568594709



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationEntry.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.storage;
+
+import java.io.Serializable;
+
+/**
+ * Key-value pair.
+ */
+public class ConfigurationEntry {

Review comment:
       I see two copies of the same class and it seems that none of them are actually used anywhere in code. Can you please remove both?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568595301



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
##########
@@ -0,0 +1,4 @@
+package org.apache.ignite.configuration.validation;

Review comment:
       File without a license...
   You can ask me how to set up automatic license header creation so you won't have to do it manually.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [ignite-3] ibessonov commented on a change in pull request #41: IGNITE-14094

Posted by GitBox <gi...@apache.org>.
ibessonov commented on a change in pull request #41:
URL: https://github.com/apache/ignite-3/pull/41#discussion_r568593020



##########
File path: modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
##########
@@ -0,0 +1,133 @@
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.storage.Data;
+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;
+
+// TODO: stupid stub name, think later
+public class ConfigurationChanger {
+
+    private Map<RootKey<?>, Configurator<?>> registry = new HashMap<>();
+
+    private ConfigurationStorage configurationStorage;
+
+    private final AtomicInteger version = new AtomicInteger(0);
+
+    public ConfigurationChanger(ConfigurationStorage configurationStorage) {
+        this.configurationStorage = configurationStorage;
+    }
+
+    public void init() {
+        final Data data = configurationStorage.readAll();
+        version.set(data.version());
+
+        configurationStorage.addListener(changedEntries -> {
+            // TODO: add tree update
+            version.set(changedEntries.version());
+        });
+
+        // TODO: iterate over data and fill Configurators
+    }
+
+    public void registerConfiguration(RootKey<?> key, Configurator<?> configurator) {
+        registry.put(key, configurator);
+    }
+
+    public <T extends ConfigurationTree<?, ?>> void change(Map<RootKey<?>, TraversableTreeNode> changes) {
+        Map<String, Serializable> allChanges = changes.entrySet().stream()
+            .map((Map.Entry<RootKey<?>, TraversableTreeNode> change) -> convertChangesToMap(change.getKey(), change.getValue()))
+            .flatMap(map -> map.entrySet().stream())
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        boolean success = false;
+
+        List<ValidationIssue> validationIssues = Collections.emptyList();
+
+        while (!success) {
+            validationIssues = validate(changes);
+
+            final int version = this.version.get();
+
+            if (validationIssues.isEmpty())
+                success = configurationStorage.write(allChanges, version);
+            else
+                break;
+        }
+
+        if (!validationIssues.isEmpty())
+            throw new ConfigurationValidationException(validationIssues);
+    }
+
+    private List<ValidationIssue> validate(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);
+
+            List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
+            issues.addAll(list);
+        }
+
+        return issues;
+    }
+
+    private Map<String, Serializable> convertChangesToMap(RootKey<?> rootKey, TraversableTreeNode node) {
+        Map<String, Serializable> values = new HashMap<>();
+
+        node.accept(null, new ConfigurationVisitor() {
+
+            String currentKey = rootKey.key();
+
+            @Override public void visitLeafNode(String key, Serializable val) {
+                values.put(currentKey + "." + key, val);
+            }
+
+            @Override public void visitInnerNode(String key, InnerNode node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;
+
+                node.traverseChildren(this);
+
+                currentKey = previousKey;
+            }
+
+            @Override public <N extends InnerNode> void visitNamedListNode(String key, NamedListNode<N> node) {
+                String previousKey = currentKey;
+
+                if (key != null)
+                    currentKey += "." + key;
+
+                for (String namedListKey : node.namedListKeys()) {
+                    String loopPreviousKey = currentKey;
+                    currentKey += "." + namedListKey;

Review comment:
       Keep in mind that keys in named lists must be escaped. Methods for that are locted in this PR right now: https://github.com/apache/ignite-3/pull/40/files




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org