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/08/04 15:04:29 UTC
[ignite-3] branch main updated: IGNITE-15166 Implemented "rename"
API for named lists configuration. (#244)
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 d3dc66f IGNITE-15166 Implemented "rename" API for named lists configuration. (#244)
d3dc66f is described below
commit d3dc66f1eee0816944db746c8cc25be6f29bef0b
Author: ibessonov <be...@gmail.com>
AuthorDate: Wed Aug 4 18:02:28 2021 +0300
IGNITE-15166 Implemented "rename" API for named lists configuration. (#244)
---
.../configuration/ConfigurationChangerTest.java | 72 +++--
.../notifications/ConfigurationListenerTest.java | 352 +++++++++++++++++++--
.../configuration/tree/NamedListNodeTest.java | 311 ++++++++++++++++++
.../configuration/tree/NamedListOrderTest.java | 239 --------------
.../configuration/util/ConfigurationUtilTest.java | 97 ++----
.../validation/ValidationUtilTest.java | 2 +-
.../ignite/configuration/ConfigurationTree.java | 12 +-
.../ignite/configuration/ConfigurationValue.java | 4 +-
.../ignite/configuration/NamedListChange.java | 22 +-
.../notifications/ConfigurationListener.java | 4 +-
.../ConfigurationNamedListListener.java | 24 +-
.../configuration/ConfigurationChanger.java | 54 ++--
.../configuration/DynamicConfiguration.java | 12 +-
.../internal/configuration/DynamicProperty.java | 4 +-
.../internal/configuration/tree/NamedListNode.java | 230 ++++++++++++--
.../configuration/util/ConfigurationFlattener.java | 25 +-
.../util/ConfigurationNotificationsUtil.java | 126 ++++++--
.../configuration/util/ConfigurationUtil.java | 285 +++++------------
.../internal/table/distributed/TableManager.java | 26 +-
.../ignite/internal/table/TableManagerTest.java | 10 +-
20 files changed, 1191 insertions(+), 720 deletions(-)
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index ef7503f..741af6d 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -23,7 +23,9 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
import org.apache.ignite.configuration.ConfigurationChangeException;
+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;
@@ -37,7 +39,8 @@ import org.apache.ignite.configuration.validation.Validator;
import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.Data;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
-import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
@@ -45,7 +48,6 @@ import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.internal.configuration.AConfiguration.KEY;
-import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.superRootPatcher;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -112,11 +114,10 @@ public class ConfigurationChangerTest {
changer.addRootKey(KEY);
changer.register(storage);
- AChange data = ((AChange)changer.createRootNode(KEY))
+ changer.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
- .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
-
- changer.change(superRootPatcher(KEY, (TraversableTreeNode)data), null).get(1, SECONDS);
+ .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
+ ), null).get(1, SECONDS);
AView newRoot = (AView)changer.getRootNode(KEY);
@@ -140,20 +141,18 @@ public class ConfigurationChangerTest {
changer2.addRootKey(KEY);
changer2.register(storage);
- AChange data1 = ((AChange)changer1.createRootNode(KEY))
+ changer1.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
- .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
-
- changer1.change(superRootPatcher(KEY, (TraversableTreeNode)data1), null).get(1, SECONDS);
+ .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
+ ), null).get(1, SECONDS);
- AChange data2 = ((AChange)changer2.createRootNode(KEY))
+ changer2.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
.changeElements(change -> change
- .create("a", element -> element.changeStrCfg("2"))
+ .createOrUpdate("a", element -> element.changeStrCfg("2"))
.create("b", element -> element.changeStrCfg("2"))
- );
-
- changer2.change(superRootPatcher(KEY, (TraversableTreeNode)data2), null).get(1, SECONDS);
+ )
+ ), null).get(1, SECONDS);
AView newRoot1 = (AView)changer1.getRootNode(KEY);
@@ -185,11 +184,10 @@ public class ConfigurationChangerTest {
changer2.addRootKey(KEY);
changer2.register(storage);
- AChange data1 = ((AChange)changer1.createRootNode(KEY))
+ changer1.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
- .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
-
- changer1.change(superRootPatcher(KEY, (TraversableTreeNode)data1), null).get(1, SECONDS);
+ .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
+ ), null).get(1, SECONDS);
changer2.addValidator(MaybeInvalid.class, new Validator<MaybeInvalid, Object>() {
@Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
@@ -197,14 +195,13 @@ public class ConfigurationChangerTest {
}
});
- AChange data2 = ((AChange)changer2.createRootNode(KEY))
+ assertThrows(ExecutionException.class, () -> changer2.change(source(KEY, (AChange parent) -> parent
.changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
.changeElements(change -> change
.create("a", element -> element.changeStrCfg("2"))
.create("b", element -> element.changeStrCfg("2"))
- );
-
- assertThrows(ExecutionException.class, () -> changer2.change(superRootPatcher(KEY, (TraversableTreeNode)data2), null).get(1, SECONDS));
+ )
+ ), null).get(1, SECONDS));
AView newRoot = (AView)changer2.getRootNode(KEY);
@@ -233,9 +230,9 @@ public class ConfigurationChangerTest {
storage.fail(true);
- AChange data = ((AChange)changer.createRootNode(KEY)).changeChild(child -> child.changeIntCfg(1));
-
- assertThrows(ExecutionException.class, () -> changer.change(superRootPatcher(KEY, (TraversableTreeNode)data), null).get(1, SECONDS));
+ assertThrows(ExecutionException.class, () -> changer.change(source(KEY, (AChange parent) -> parent
+ .changeChild(child -> child.changeIntCfg(1))
+ ), null).get(1, SECONDS));
storage.fail(false);
@@ -294,15 +291,28 @@ public class ConfigurationChangerTest {
assertEquals("bar", root.child().defStr());
assertEquals(List.of("xyz"), Arrays.asList(root.child().arr()));
- DefaultsChange change = ((DefaultsChange)changer.createRootNode(DefaultsConfiguration.KEY)).changeChildsList(childs ->
- childs.create("name", child -> {})
- );
-
- changer.change(superRootPatcher(DefaultsConfiguration.KEY, (TraversableTreeNode)change), null).get(1, SECONDS);
+ changer.change(source(DefaultsConfiguration.KEY, (DefaultsChange def) -> def
+ .changeChildsList(childs ->
+ childs.create("name", child -> {})
+ )
+ ), null).get(1, SECONDS);
root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
assertEquals("bar", root.childsList().get("name").defStr());
}
+ private static <Change> ConfigurationSource source(RootKey<?, ? super Change> rootKey, Consumer<Change> changer) {
+ return new ConfigurationSource() {
+ @Override public void descend(ConstructableTreeNode node) {
+ ConfigurationSource changerSrc = new ConfigurationSource() {
+ @Override public void descend(ConstructableTreeNode node) {
+ changer.accept((Change)node);
+ }
+ };
+
+ node.construct(rootKey.key(), changerSrc);
+ }
+ };
+ }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index 111e9c5..3a5d906 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -21,6 +21,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
@@ -34,6 +36,7 @@ import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static java.util.concurrent.CompletableFuture.completedFuture;
@@ -41,6 +44,8 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.fail;
/** */
public class ConfigurationListenerTest {
@@ -137,9 +142,9 @@ public class ConfigurationListenerTest {
assertEquals(List.of("parent", "child", "str"), log);
}
- /** */
+ /** Tests notifications validity when a new named list element is created. */
@Test
- public void namedListNode() throws Exception {
+ public void namedListNodeOnCreate() throws Exception {
List<String> log = new ArrayList<>();
configuration.listen(ctx -> {
@@ -155,29 +160,98 @@ public class ConfigurationListenerTest {
});
configuration.elements().listen(ctx -> {
- if (ctx.oldValue().size() == 0) {
- ChildView newValue = ctx.newValue().get("name");
+ assertEquals(0, ctx.oldValue().size());
+
+ ChildView newValue = ctx.newValue().get("name");
+
+ assertNotNull(newValue);
+ assertEquals("default", newValue.str());
+
+ log.add("elements");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(new ConfigurationNamedListListener<ChildView>() {
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onCreate(ConfigurationNotificationEvent<ChildView> ctx) {
+ assertNull(ctx.oldValue());
+
+ ChildView newValue = ctx.newValue();
assertNotNull(newValue);
assertEquals("default", newValue.str());
+
+ log.add("create");
+
+ return completedFuture(null);
}
- else if (ctx.newValue().size() == 0) {
- ChildView oldValue = ctx.oldValue().get("name");
- assertNotNull(oldValue);
- assertEquals("foo", oldValue.str());
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("update");
+
+ return completedFuture(null);
}
- else {
- ChildView oldValue = ctx.oldValue().get("name");
- assertNotNull(oldValue);
- assertEquals("default", oldValue.str());
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onRename(
+ String oldName,
+ String newName,
+ ConfigurationNotificationEvent<ChildView> ctx
+ ) {
+ log.add("rename");
- ChildView newValue = ctx.newValue().get("name");
+ return completedFuture(null);
+ }
- assertNotNull(newValue);
- assertEquals("foo", newValue.str());
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onDelete(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("delete");
+
+ return completedFuture(null);
}
+ });
+
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.create("name", element -> {}))
+ ).get(1, SECONDS);
+
+ assertEquals(List.of("parent", "elements", "create"), log);
+ }
+
+ /** Tests notifications validity when a named list element is edited. */
+ @Test
+ public void namedListNodeOnUpdate() throws Exception {
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.create("name", element -> {}))
+ ).get(1, SECONDS);
+
+ List<String> log = new ArrayList<>();
+
+ configuration.listen(ctx -> {
+ log.add("parent");
+
+ return completedFuture(null);
+ });
+
+ configuration.child().listen(ctx -> {
+ log.add("child");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(ctx -> {
+
+ ChildView oldValue = ctx.oldValue().get("name");
+
+ assertNotNull(oldValue);
+ assertEquals("default", oldValue.str());
+
+ ChildView newValue = ctx.newValue().get("name");
+
+ assertNotNull(newValue);
+ assertEquals("foo", newValue.str());
log.add("elements");
@@ -187,13 +261,6 @@ public class ConfigurationListenerTest {
configuration.elements().listen(new ConfigurationNamedListListener<ChildView>() {
/** {@inheritDoc} */
@Override public CompletableFuture<?> onCreate(ConfigurationNotificationEvent<ChildView> ctx) {
- assertNull(ctx.oldValue());
-
- ChildView newValue = ctx.newValue();
-
- assertNotNull(newValue);
- assertEquals("default", newValue.str());
-
log.add("create");
return completedFuture(null);
@@ -217,14 +284,118 @@ public class ConfigurationListenerTest {
}
/** {@inheritDoc} */
+ @Override public CompletableFuture<?> onRename(
+ String oldName,
+ String newName,
+ ConfigurationNotificationEvent<ChildView> ctx
+ ) {
+ log.add("rename");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
@Override public CompletableFuture<?> onDelete(ConfigurationNotificationEvent<ChildView> ctx) {
- assertNull(ctx.newValue());
+ log.add("delete");
+
+ return completedFuture(null);
+ }
+ });
+
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.createOrUpdate("name", element -> element.changeStr("foo")))
+ ).get(1, SECONDS);
+
+ assertEquals(List.of("parent", "elements", "update"), log);
+ }
+
+
+ /** Tests notifications validity when a named list element is renamed. */
+ @Test
+ public void namedListNodeOnRename() throws Exception {
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.create("name", element -> {}))
+ ).get(1, SECONDS);
+
+ List<String> log = new ArrayList<>();
+
+ configuration.listen(ctx -> {
+ log.add("parent");
+
+ return completedFuture(null);
+ });
+
+ configuration.child().listen(ctx -> {
+ log.add("child");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(ctx -> {
+ assertEquals(1, ctx.oldValue().size());
+
+ ChildView oldValue = ctx.oldValue().get("name");
+
+ assertNotNull(oldValue);
+ assertEquals("default", oldValue.str());
+
+ assertEquals(1, ctx.newValue().size());
+
+ ChildView newValue = ctx.newValue().get("newName");
+
+ assertNotNull(newValue, ctx.newValue().namedListKeys().toString());
+ assertEquals("default", newValue.str());
+
+ assertSame(oldValue, newValue);
+
+ log.add("elements");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(new ConfigurationNamedListListener<ChildView>() {
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onCreate(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("create");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("update");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onRename(
+ String oldName,
+ String newName,
+ ConfigurationNotificationEvent<ChildView> ctx
+ ) {
+ assertEquals("name", oldName);
+ assertEquals("newName", newName);
ChildView oldValue = ctx.oldValue();
assertNotNull(oldValue);
- assertEquals("foo", oldValue.str());
+ assertEquals("default", oldValue.str());
+
+ ChildView newValue = ctx.newValue();
+
+ assertNotNull(newValue);
+ assertEquals("default", newValue.str());
+ assertSame(oldValue, newValue);
+
+ log.add("rename");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onDelete(ConfigurationNotificationEvent<ChildView> ctx) {
log.add("delete");
return completedFuture(null);
@@ -232,20 +403,90 @@ public class ConfigurationListenerTest {
});
configuration.change(parent ->
- parent.changeElements(elements -> elements.create("name", element -> {}))
+ parent.changeElements(elements -> elements.rename("name", "newName"))
).get(1, SECONDS);
- assertEquals(List.of("parent", "elements", "create"), log);
-
- log.clear();
+ assertEquals(List.of("parent", "elements", "rename"), log);
+ }
+ /** Tests notifications validity when a named list element is deleted. */
+ @Test
+ public void namedListNodeOnDelete() throws Exception {
configuration.change(parent ->
- parent.changeElements(elements -> elements.createOrUpdate("name", element -> element.changeStr("foo")))
+ parent.changeElements(elements -> elements.create("name", element -> {}))
).get(1, SECONDS);
- assertEquals(List.of("parent", "elements", "update"), log);
+ List<String> log = new ArrayList<>();
+
+ configuration.listen(ctx -> {
+ log.add("parent");
+
+ return completedFuture(null);
+ });
+
+ configuration.child().listen(ctx -> {
+ log.add("child");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(ctx -> {
+ assertEquals(0, ctx.newValue().size());
+
+ ChildView oldValue = ctx.oldValue().get("name");
+
+ assertNotNull(oldValue);
+ assertEquals("default", oldValue.str());
+
+ log.add("elements");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(new ConfigurationNamedListListener<ChildView>() {
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onCreate(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("create");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<ChildView> ctx) {
+ log.add("update");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onRename(
+ String oldName,
+ String newName,
+ ConfigurationNotificationEvent<ChildView> ctx
+ ) {
+ log.add("rename");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onDelete(ConfigurationNotificationEvent<ChildView> ctx) {
+ assertNull(ctx.newValue());
+
+ ChildView oldValue = ctx.oldValue();
- log.clear();
+ assertNotNull(oldValue);
+ assertEquals("default", oldValue.str());
+
+ log.add("delete");
+
+ return completedFuture(null);
+ }
+ });
+
+ configuration.elements().get("name").listen(ctx -> {
+ return completedFuture(null);
+ });
configuration.change(parent ->
parent.changeElements(elements -> elements.delete("name"))
@@ -253,4 +494,53 @@ public class ConfigurationListenerTest {
assertEquals(List.of("parent", "elements", "delete"), log);
}
+
+ /** */
+ @Test
+ @Disabled("Will be fixed in https://issues.apache.org/jira/browse/IGNITE-15193")
+ public void dataRace() throws Exception {
+ configuration.change(parent -> parent.changeElements(elements ->
+ elements.create("name", e -> {}))
+ ).get(1, SECONDS);
+
+ CountDownLatch wait = new CountDownLatch(1);
+ CountDownLatch release = new CountDownLatch(1);
+
+ List<String> log = new ArrayList<>();
+
+ configuration.listen(ctx -> {
+ try {
+ wait.await(1, SECONDS);
+ }
+ catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+
+ release.countDown();
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().get("name").listen(ctx -> {
+ assertNull(ctx.newValue());
+
+ log.add("deleted");
+
+ return completedFuture(null);
+ });
+
+ Future<Void> fut = configuration.change(parent -> parent.changeElements(elements ->
+ elements.delete("name"))
+ );
+
+ wait.countDown();
+
+ configuration.elements();
+
+ release.await(1, SECONDS);
+
+ fut.get(1, SECONDS);
+
+ assertEquals(List.of("deleted"), log);
+ }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
new file mode 100644
index 0000000..4d2df6e
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.tree;
+
+import java.io.Serializable;
+import java.util.Map;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.TestConfigurationChanger;
+import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.anEmptyMap;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/** Test for named list nodes. */
+public class NamedListNodeTest {
+ /** Root that has a single named list. */
+ @ConfigurationRoot(rootName = "a")
+ public static class AConfigurationSchema {
+ /** */
+ @NamedConfigValue
+ public BConfigurationSchema b;
+ }
+
+ /** Named list element node that in inself contains another named list. */
+ @Config
+ public static class BConfigurationSchema {
+ /** Every named list element node must have at least one configuration field that is not named list. */
+ @Value(hasDefault = true)
+ public String c = "foo";
+
+ @NamedConfigValue
+ public BConfigurationSchema b;
+ }
+
+ /** Runtime implementations generator. */
+ private static ConfigurationAsmGenerator cgen;
+
+ /** Test configuration storage. */
+ private TestConfigurationStorage storage;
+
+ /** Test configuration changer. */
+ private TestConfigurationChanger changer;
+
+ /** Instantiates {@link #cgen}. */
+ @BeforeAll
+ public static void beforeAll() {
+ cgen = new ConfigurationAsmGenerator();
+ }
+
+ /** Nullifies {@link #cgen} to prevent memory leak from having runtime ClassLoader accessible from GC root. */
+ @AfterAll
+ public static void afterAll() {
+ cgen = null;
+ }
+
+ /** */
+ @BeforeEach
+ public void before() {
+ storage = new TestConfigurationStorage();
+
+ changer = new TestConfigurationChanger(cgen);
+ changer.addRootKey(AConfiguration.KEY);
+ changer.register(storage);
+ }
+
+ /** */
+ @AfterEach
+ public void after() {
+ changer.stop();
+ }
+
+ /**
+ * Tests that there are no unnecessary {@code <order>} values in the storage after all basic named list operations.
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void storageData() throws Exception {
+ // Manually instantiate configuration instance.
+ var a = (AConfiguration)cgen.instantiateCfg(AConfiguration.KEY, changer);
+
+ // Create values on several layers at the same time. They all should have <order> = 0.
+ a.b().change(b -> b.create("X", x -> x.changeB(xb -> xb.create("Z0", z0 -> {})))).get();
+
+ String xId = ((NamedListNode<?>)a.b().value()).internalId("X");
+ String z0Id = ((NamedListNode<?>)a.b().get("X").b().value()).internalId("Z0");
+
+ Map<String, Serializable> storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(6),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z0")
+ ))
+ );
+
+ BConfiguration x = a.b().get("X");
+
+ // Append new key. It should have <order> = 1.
+ x.b().change(xb -> xb.create("Z5", z5 -> {})).get();
+
+ String z5Id = ((NamedListNode<?>)a.b().get("X").b().value()).internalId("Z5");
+
+ storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(9),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z0"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z5Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z5Id), 1),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z5Id), "Z5")
+ ))
+ );
+
+ // Insert new key somewhere in the middle. Index of Z5 should be updated to 2.
+ x.b().change(xb -> xb.create(1, "Z2", z2 -> {})).get();
+
+ String z2Id = ((NamedListNode<?>)a.b().get("X").b().value()).internalId("Z2");
+
+ storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(12),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z0"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z2Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z2Id), 1),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z2Id), "Z2"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z5Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z5Id), 2),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z5Id), "Z5")
+ ))
+ );
+
+ // Insert new key somewhere in the middle. Indexes of Z3 and Z5 should be updated to 2 and 3.
+ x.b().change(xb -> xb.createAfter("Z2", "Z3", z3 -> {})).get();
+
+ String z3Id = ((NamedListNode<?>)a.b().get("X").b().value()).internalId("Z3");
+
+ storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(15),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z0"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z2Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z2Id), 1),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z2Id), "Z2"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z3Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z3Id), 2),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z3Id), "Z3"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z5Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z5Id), 3),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z5Id), "Z5")
+ ))
+ );
+
+ // Delete keys from the middle. Indexes of Z3 should be updated to 1.
+ x.b().change(xb -> xb.delete("Z2").delete("Z5")).get();
+
+ storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(9),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z0"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z3Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z3Id), 1),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z3Id), "Z3")
+ ))
+ );
+
+ // Delete keys from the middle. Indexes of Z3 should be updated to 1.
+ x.b().change(xb -> xb.rename("Z0", "Z1")).get();
+
+ storageValues = storage.readAll().values();
+
+ assertThat(
+ storageValues,
+ is(Matchers.<Map<String, Serializable>>allOf(
+ aMapWithSize(9),
+ hasEntry(format("a.b.%s.c", xId), "foo"),
+ hasEntry(format("a.b.%s.<order>", xId), 0),
+ hasEntry(format("a.b.%s.<name>", xId), "X"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z0Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z0Id), 0),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z0Id), "Z1"),
+ hasEntry(format("a.b.%s.b.%s.c", xId, z3Id), "foo"),
+ hasEntry(format("a.b.%s.b.%s.<order>", xId, z3Id), 1),
+ hasEntry(format("a.b.%s.b.%s.<name>", xId, z3Id), "Z3")
+ ))
+ );
+
+ // Delete values on several layers simultaneously. Storage must be empty after that.
+ a.b().change(b -> b.delete("X")).get();
+
+ assertThat(storage.readAll().values(), is(anEmptyMap()));
+ }
+
+ /** Tests exceptions described in methods signatures. */
+ @Test
+ public void errors() throws Exception {
+ var b = new NamedListNode<>("name", () -> cgen.instantiateNode(BConfigurationSchema.class));
+
+ b.create("X", x -> {}).create("Y", y -> {});
+
+ // NPE in keys.
+ assertThrows(NullPointerException.class, () -> b.create(null, z -> {}));
+ assertThrows(NullPointerException.class, () -> b.createOrUpdate(null, z -> {}));
+ assertThrows(NullPointerException.class, () -> b.create(0, null, z -> {}));
+ assertThrows(NullPointerException.class, () -> b.createAfter(null, "Z", z -> {}));
+ assertThrows(NullPointerException.class, () -> b.createAfter("X", null, z -> {}));
+ assertThrows(NullPointerException.class, () -> b.rename(null, "Z"));
+ assertThrows(NullPointerException.class, () -> b.rename("X", null));
+ assertThrows(NullPointerException.class, () -> b.delete(null));
+
+ // NPE in closures.
+ assertThrows(NullPointerException.class, () -> b.create("Z", null));
+ assertThrows(NullPointerException.class, () -> b.createOrUpdate("Z", null));
+ assertThrows(NullPointerException.class, () -> b.create(0, "Z", null));
+ assertThrows(NullPointerException.class, () -> b.createAfter("X", "Z", null));
+
+ // Already existing keys.
+ assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> {}));
+ assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x -> {}));
+ assertThrows(IllegalArgumentException.class, () -> b.createAfter("X", "Y", y -> {}));
+ assertThrows(IllegalArgumentException.class, () -> b.rename("X", "Y"));
+
+ // Nonexistent preceding key.
+ assertThrows(IllegalArgumentException.class, () -> b.createAfter("A", "Z", z -> {}));
+
+ // Wrong indexes.
+ assertThrows(IndexOutOfBoundsException.class, () -> b.create(-1, "Z", z -> {}));
+ assertThrows(IndexOutOfBoundsException.class, () -> b.create(3, "Z", z -> {}));
+
+ // Nonexisting key.
+ assertThrows(IllegalArgumentException.class, () -> b.rename("A", "Z"));
+
+ // Operations after delete.
+ b.delete("X");
+ assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> {}));
+ assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x -> {}));
+ assertThrows(IllegalArgumentException.class, () -> b.rename("X", "Z"));
+ assertThrows(IllegalArgumentException.class, () -> b.rename("Y", "X"));
+
+ // Deletion of nonexistent elements doesn't break anything.
+ b.delete("X");
+ b.delete("Y");
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java
deleted file mode 100644
index 16637c6..0000000
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.configuration.tree;
-
-import java.util.Map;
-import org.apache.ignite.configuration.NamedListChange;
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.NamedConfigValue;
-import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.internal.configuration.TestConfigurationChanger;
-import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
-import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-/** Test for keys ordering in named list nodes. */
-public class NamedListOrderTest {
- /** Root that has a single named list. */
- @ConfigurationRoot(rootName = "a")
- public static class AConfigurationSchema {
- /** */
- @NamedConfigValue
- public BConfigurationSchema b;
- }
-
- /** Named list element node that in inself contains another named list. */
- @Config
- public static class BConfigurationSchema {
- /** Every named list element node must have at least one configuration field that is not named list. */
- @Value(hasDefault = true)
- public String c = "foo";
-
- @NamedConfigValue
- public BConfigurationSchema b;
- }
-
- /** Runtime implementations generator. */
- private static ConfigurationAsmGenerator cgen;
-
- /** Test configuration storage. */
- private TestConfigurationStorage storage;
-
- /** Test configuration changer. */
- private TestConfigurationChanger changer;
-
- /** Instantiates {@link #cgen}. */
- @BeforeAll
- public static void beforeAll() {
- cgen = new ConfigurationAsmGenerator();
- }
-
- /** Nullifies {@link #cgen} to prevent memory leak from having runtime ClassLoader accessible from GC root. */
- @AfterAll
- public static void afterAll() {
- cgen = null;
- }
-
- /** */
- @BeforeEach
- public void before() {
- storage = new TestConfigurationStorage();
-
- changer = new TestConfigurationChanger(cgen);
- changer.addRootKey(AConfiguration.KEY);
- changer.register(storage);
- }
-
- /** */
- @AfterEach
- public void after() {
- changer.stop();
- }
-
- /**
- * Tests that there are no unnecessary {@code <idx>} values in the storage after all basic named list operations.
- *
- * @throws Exception If failed.
- */
- @Test
- public void storageData() throws Exception {
- // Manually instantiate configuration instance.
- var a = (AConfiguration)cgen.instantiateCfg(AConfiguration.KEY, changer);
-
- // Create values on several layers at the same time. They all should have <idx> = 0.
- a.b().change(b -> b.create("X", x -> x.changeB(xb -> xb.create("Z0", z0 -> {})))).get();
-
- assertEquals(
- Map.of(
- "a.b.X.c", "foo",
- "a.b.X.<idx>", 0,
- "a.b.X.b.Z0.c", "foo",
- "a.b.X.b.Z0.<idx>", 0
- ),
- storage.readAll().values()
- );
-
- BConfiguration x = a.b().get("X");
-
- // Append new key. It should have <idx> = 1.
- x.b().change(xb -> xb.create("Z5", z5 -> {})).get();
-
- assertEquals(
- Map.of(
- "a.b.X.c", "foo",
- "a.b.X.<idx>", 0,
- "a.b.X.b.Z0.c", "foo",
- "a.b.X.b.Z0.<idx>", 0,
- "a.b.X.b.Z5.c", "foo",
- "a.b.X.b.Z5.<idx>", 1
- ),
- storage.readAll().values()
- );
-
- // Insert new key somewhere in the middle. Index of Z5 should be updated to 2.
- x.b().change(xb -> xb.create(1, "Z2", z2 -> {})).get();
-
- assertEquals(
- Map.of(
- "a.b.X.c", "foo",
- "a.b.X.<idx>", 0,
- "a.b.X.b.Z0.c", "foo",
- "a.b.X.b.Z0.<idx>", 0,
- "a.b.X.b.Z2.c", "foo",
- "a.b.X.b.Z2.<idx>", 1,
- "a.b.X.b.Z5.c", "foo",
- "a.b.X.b.Z5.<idx>", 2
- ),
- storage.readAll().values()
- );
-
- // Insert new key somewhere in the middle. Indexes of Z3 and Z5 should be updated to 2 and 3.
- x.b().change(xb -> xb.createAfter("Z2", "Z3", z3 -> {})).get();
-
- assertEquals(
- Map.of(
- "a.b.X.c", "foo",
- "a.b.X.<idx>", 0,
- "a.b.X.b.Z0.c", "foo",
- "a.b.X.b.Z0.<idx>", 0,
- "a.b.X.b.Z2.c", "foo",
- "a.b.X.b.Z2.<idx>", 1,
- "a.b.X.b.Z3.c", "foo",
- "a.b.X.b.Z3.<idx>", 2,
- "a.b.X.b.Z5.c", "foo",
- "a.b.X.b.Z5.<idx>", 3
- ),
- storage.readAll().values()
- );
-
- // Delete key from the middle. Indexes of Z3 and Z5 should be updated to 1 and 2.
- x.b().change(xb -> xb.delete("Z2")).get();
-
- assertEquals(
- Map.of(
- "a.b.X.c", "foo",
- "a.b.X.<idx>", 0,
- "a.b.X.b.Z0.c", "foo",
- "a.b.X.b.Z0.<idx>", 0,
- "a.b.X.b.Z3.c", "foo",
- "a.b.X.b.Z3.<idx>", 1,
- "a.b.X.b.Z5.c", "foo",
- "a.b.X.b.Z5.<idx>", 2
- ),
- storage.readAll().values()
- );
-
- // Delete values on several layers simultaneously. Storage must be empty after that.
- a.b().change(b -> b.delete("X")).get();
-
- assertEquals(
- Map.of(),
- storage.readAll().values()
- );
- }
-
- /** Tests exceptions described in methods signatures. */
- @Test
- public void creationErrors() throws Exception {
- // Manually instantiate configuration instance.
- var a = (AConfiguration)cgen.instantiateCfg(AConfiguration.KEY, changer);
-
- a.b().change(b -> b.create("X", x -> {}).create("Y", y -> {})).get();
-
- // Dirty cast, but appropriate for this particular test.
- var b = (NamedListChange<BView>)a.b().value();
-
- // NPE in keys.
- assertThrows(NullPointerException.class, () -> b.create(null, z -> {}));
- assertThrows(NullPointerException.class, () -> b.createOrUpdate(null, z -> {}));
- assertThrows(NullPointerException.class, () -> b.create(0, null, z -> {}));
- assertThrows(NullPointerException.class, () -> b.createAfter(null, "Z", z -> {}));
- assertThrows(NullPointerException.class, () -> b.createAfter("X", null, z -> {}));
-
- // NPE in closures.
- assertThrows(NullPointerException.class, () -> b.create("Z", null));
- assertThrows(NullPointerException.class, () -> b.createOrUpdate("Z", null));
- assertThrows(NullPointerException.class, () -> b.create(0, "Z", null));
- assertThrows(NullPointerException.class, () -> b.createAfter("X", "Z", null));
-
- // Already existing keys.
- assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> {}));
- assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x -> {}));
- assertThrows(IllegalArgumentException.class, () -> b.createAfter("X", "Y", y -> {}));
-
- // Nonexistent preceding key.
- assertThrows(IllegalArgumentException.class, () -> b.createAfter("A", "Z", z -> {}));
-
- // Wrong indexes.
- assertThrows(IndexOutOfBoundsException.class, () -> b.create(-1, "Z", z -> {}));
- assertThrows(IndexOutOfBoundsException.class, () -> b.create(3, "Z", z -> {}));
-
- // Create after delete.
- b.delete("X");
- assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> {}));
- assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x -> {}));
- }
-}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index 0b7fe20..9bf04cc 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -30,7 +30,6 @@ import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.SuperRoot;
import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
-import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
import org.jetbrains.annotations.NotNull;
@@ -39,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static java.util.Collections.singletonMap;
+import static org.apache.ignite.internal.configuration.tree.NamedListNode.NAME;
import static org.apache.ignite.internal.configuration.tree.NamedListNode.ORDER_IDX;
import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -46,10 +46,11 @@ import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -258,13 +259,15 @@ public class ConfigurationUtilTest {
ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
"elements", Map.of(
- "name2", Map.of(
+ "0123456789abcde0123456789abcde", Map.of(
"child", Map.of("str", "value2"),
- ORDER_IDX, 1
+ ORDER_IDX, 1,
+ NAME, "name2"
),
- "name1", Map.of(
+ "12345689abcdef0123456789abcdef0", Map.of(
"child", Map.of("str", "value1"),
- ORDER_IDX, 0
+ ORDER_IDX, 0,
+ NAME, "name1"
)
)
));
@@ -311,9 +314,10 @@ public class ConfigurationUtilTest {
)
),
is(allOf(
- aMapWithSize(2),
- hasEntry("root.elements.name.child.str", (Serializable)"foo"),
- hasEntry("root.elements.name.<idx>", 0)
+ aMapWithSize(3),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]child[.]str"), hasToString("foo")),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<order>"), is(0)),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<name>"), hasToString("name"))
))
);
@@ -329,9 +333,10 @@ public class ConfigurationUtilTest {
.changeElements(elements -> elements.delete("name"))
),
is(allOf(
- aMapWithSize(2),
- hasEntry("root.elements.name.child.str", null),
- hasEntry("root.elements.name.<idx>", null)
+ aMapWithSize(3),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]child[.]str"), nullValue()),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<order>"), nullValue()),
+ hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<name>"), nullValue())
))
);
}
@@ -357,70 +362,4 @@ public class ConfigurationUtilTest {
// Create flat diff between two super trees.
return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
}
-
- /**
- * Tests basic invariants of {@link ConfigurationUtil#patch(ConstructableTreeNode, TraversableTreeNode)} method.
- */
- @Test
- public void patch() {
- var originalRoot = newParentInstance();
-
- originalRoot.changeElements(elements ->
- elements.create("name1", element ->
- element.changeChild(child -> child.changeStr("value1"))
- )
- );
-
- // Updating config.
- ParentView updatedRoot = ConfigurationUtil.patch(originalRoot, (TraversableTreeNode)copy(originalRoot).changeElements(elements ->
- elements.createOrUpdate("name1", element ->
- element.changeChild(child -> child.changeStr("value2"))
- )
- ));
-
- assertNotSame(originalRoot, updatedRoot);
- assertNotSame(originalRoot.elements(), updatedRoot.elements());
- assertNotSame(originalRoot.elements().get("name1"), updatedRoot.elements().get("name1"));
- assertNotSame(originalRoot.elements().get("name1").child(), updatedRoot.elements().get("name1").child());
-
- assertEquals("value1", originalRoot.elements().get("name1").child().str());
- assertEquals("value2", updatedRoot.elements().get("name1").child().str());
-
- // Expanding config.
- ParentView expandedRoot = ConfigurationUtil.patch(originalRoot, (TraversableTreeNode)copy(originalRoot).changeElements(elements ->
- elements.createOrUpdate("name2", element ->
- element.changeChild(child -> child.changeStr("value2"))
- )
- ));
-
- assertNotSame(originalRoot, expandedRoot);
- assertNotSame(originalRoot.elements(), expandedRoot.elements());
-
- assertSame(originalRoot.elements().get("name1"), expandedRoot.elements().get("name1"));
- assertNull(originalRoot.elements().get("name2"));
- assertNotNull(expandedRoot.elements().get("name2"));
-
- assertEquals("value2", expandedRoot.elements().get("name2").child().str());
-
- // Shrinking config.
- ParentView shrinkedRoot = (ParentView)ConfigurationUtil.patch((InnerNode)expandedRoot, (TraversableTreeNode)copy(expandedRoot).changeElements(elements ->
- elements.delete("name1")
- ));
-
- assertNotSame(expandedRoot, shrinkedRoot);
- assertNotSame(expandedRoot.elements(), shrinkedRoot.elements());
-
- assertNotNull(expandedRoot.elements().get("name1"));
- assertNull(shrinkedRoot.elements().get("name1"));
- assertNotNull(shrinkedRoot.elements().get("name2"));
- }
-
- /**
- * @param parent {@link ParentView} object.
- * @return Copy of the {@code parent} objects cast to {@link ParentChange}.
- * @see ConstructableTreeNode#copy()
- */
- private ParentChange copy(ParentView parent) {
- return (ParentChange)((ConstructableTreeNode)parent).copy();
- }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
index 1a95c85..8946f09 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
@@ -112,7 +112,7 @@ public class ValidationUtilTest {
public void before() {
root = cgen.instantiateNode(ValidatedRootConfigurationSchema.class);
- ConfigurationUtil.addDefaults(root, root);
+ ConfigurationUtil.addDefaults(root);
}
/** */
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
index 0b7cbab..0336442 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
@@ -17,8 +17,7 @@
package org.apache.ignite.configuration;
-import java.util.Map;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
@@ -29,17 +28,10 @@ import java.util.function.Consumer;
*/
public interface ConfigurationTree<VIEW, CHANGE> extends ConfigurationProperty<VIEW, CHANGE> {
/**
- * Children of the tree.
- *
- * @return Map from {@code String} to a corresponding {@link ConfigurationProperty}.
- */
- Map<String, ConfigurationProperty<?, ?>> members();
-
- /**
* Changes this configuration node value.
*
* @param change CHANGE object.
* @return Future that is completed when configuration change is finished either successfully or not.
*/
- Future<Void> change(Consumer<CHANGE> change);
+ CompletableFuture<Void> change(Consumer<CHANGE> change);
}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
index bd70cb1..8c8cb21 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
@@ -17,7 +17,7 @@
package org.apache.ignite.configuration;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
/**
@@ -34,5 +34,5 @@ public interface ConfigurationValue<VIEW> extends ConfigurationProperty<VIEW, VI
* @return Future that signifies end of the update operation. Can also be completed with
* {@link ConfigurationValidationException} and {@link ConfigurationChangeException}.
*/
- Future<Void> update(VIEW change);
+ CompletableFuture<Void> update(VIEW change);
}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
index ba42132..624b35f 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
@@ -18,6 +18,8 @@
package org.apache.ignite.configuration;
import java.util.function.Consumer;
+import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
+import org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
/**
* Closure parameter for {@link NamedConfigurationTree#change(Consumer)} method. Contains methods to modify named lists.
@@ -82,7 +84,25 @@ public interface NamedListChange<Change> extends NamedListView<Change> {
NamedListChange<Change> createOrUpdate(String key, Consumer<Change> valConsumer);
/**
- * Remove the value from named list configuration.
+ * Renames the existing value in the named list configuration. Element with key {@code oldKey} must exist and key
+ * {@code newKey} must not. Error will occur if {@code newKey} has just been deleted on the same
+ * {@link NamedListChange} instance (to distinguish between
+ * {@link ConfigurationNamedListListener#onRename(String, String, ConfigurationNotificationEvent)} and
+ * {@link ConfigurationNamedListListener#onUpdate(ConfigurationNotificationEvent)} on {@code newKey}).
+ *
+ * @param oldKey Key for the value to be updated.
+ * @param newKey New key for the same value.
+ * @return {@code this} for chaining.
+ *
+ * @throws NullPointerException If one of parameters is null.
+ * @throws IllegalArgumentException If an element with name {@code newKey} already exists, or an element with name
+ * {@code oldKey} doesn't exist, or {@link #delete(String)} has previously been invoked with either the
+ * {@code newKey} or the {@code oldKey}.
+ */
+ NamedListChange<Change> rename(String oldKey, String newKey);
+
+ /**
+ * Removes the value from the named list configuration.
*
* @param key Key for the value to be removed.
* @return {@code this} for chaining.
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
index 252a462..534d5ec 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
@@ -31,8 +31,8 @@ public interface ConfigurationListener<VIEW> {
* Called on property value update.
*
* @param ctx Notification context.
- * @return Future that signifies end of listener execution.
+ * @return Future that signifies the end of the listener execution.
*/
- @NotNull CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<VIEW> ctx);
+ @NotNull CompletableFuture<?> onUpdate(@NotNull ConfigurationNotificationEvent<VIEW> ctx);
}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java
index 028a8d8..18d5549 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java
@@ -30,15 +30,31 @@ public interface ConfigurationNamedListListener<VIEW> extends ConfigurationListe
* Called when new named list element is created.
*
* @param ctx Notification context.
- * @return Future that signifies end of listener execution.
+ * @return Future that signifies the end of the listener execution.
*/
- @NotNull CompletableFuture<?> onCreate(ConfigurationNotificationEvent<VIEW> ctx);
+ @NotNull CompletableFuture<?> onCreate(@NotNull ConfigurationNotificationEvent<VIEW> ctx);
+
+ /**
+ * Called when a named list element is renamed. Semantically equivalent to
+ * {@link #onUpdate(ConfigurationNotificationEvent)} with the difference that the content of the element might
+ * have not been changed. No separate {@link #onUpdate(ConfigurationNotificationEvent)} call is performed when
+ * {@link #onRename(String, String, ConfigurationNotificationEvent)} is already invoked.
+ *
+ * @param oldName Name, previously assigned to the element.
+ * @param newName New name of the element.
+ * @param ctx Notification context.
+ * @return Future that signifies the end of the listener execution.
+ */
+ @NotNull CompletableFuture<?> onRename(
+ @NotNull String oldName,
+ @NotNull String newName,
+ @NotNull ConfigurationNotificationEvent<VIEW> ctx);
/**
* Called when named list element is deleted.
*
* @param ctx Notification context.
- * @return Future that signifies end of listener execution.
+ * @return Future that signifies the end of the listener execution.
*/
- @NotNull CompletableFuture<?> onDelete(ConfigurationNotificationEvent<VIEW> ctx);
+ @NotNull CompletableFuture<?> onDelete(@NotNull ConfigurationNotificationEvent<VIEW> ctx);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index e54121a..58b1494 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -52,9 +52,8 @@ import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.dropNulls;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
-import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.nodePatcher;
-import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.patch;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
/**
@@ -233,32 +232,33 @@ public abstract class ConfigurationChanger {
assert configurationStorage != null : storageType;
- StorageRoots storageRoots = storagesRootsMap.get(storageType);
-
- SuperRoot superRoot = storageRoots.roots;
- SuperRoot defaultsNode = new SuperRoot(rootCreator());
+ try {
+ ConfigurationSource defaultsCfgSource = new ConfigurationSource() {
+ @Override public void descend(ConstructableTreeNode node) {
+ addDefaults((InnerNode)node);
+ }
+ };
- addDefaults(superRoot, defaultsNode);
+ changeInternally(defaultsCfgSource, configurationStorage).get();
+ }
+ catch (ExecutionException e) {
+ Throwable cause = e.getCause();
- List<ValidationIssue> validationIssues = ValidationUtil.validate(
- superRoot,
- defaultsNode,
- this::getRootNode,
- cachedAnnotations,
- validators
- );
+ if (cause instanceof ConfigurationValidationException)
+ throw (ConfigurationValidationException)cause;
- if (!validationIssues.isEmpty())
- throw new ConfigurationValidationException(validationIssues);
+ if (cause instanceof ConfigurationChangeException)
+ throw (ConfigurationChangeException)cause;
- try {
- changeInternally(nodePatcher(defaultsNode), storageInstances.get(storageType)).get();
- }
- catch (InterruptedException | ExecutionException e) {
throw new ConfigurationChangeException(
"Failed to write defalut configuration values into the storage " + configurationStorage.getClass(), e
);
}
+ catch (InterruptedException e) {
+ throw new ConfigurationChangeException(
+ "Failed to initialize configuration storage " + configurationStorage.getClass(), e
+ );
+ }
}
/**
@@ -358,23 +358,19 @@ public abstract class ConfigurationChanger {
src.descend(changes);
- SuperRoot patchedSuperRoot = patch(curRoots, changes);
-
- SuperRoot defaultsNode = new SuperRoot(rootCreator());
+ addDefaults(changes);
- addDefaults(patchedSuperRoot, defaultsNode);
-
- SuperRoot patchedChanges = patch(changes, defaultsNode);
-
- Map<String, Serializable> allChanges = createFlattenedUpdatesMap(curRoots, patchedChanges);
+ Map<String, Serializable> allChanges = createFlattenedUpdatesMap(curRoots, changes);
// Unlikely but still possible.
if (allChanges.isEmpty())
return null;
+ dropNulls(changes);
+
List<ValidationIssue> validationIssues = ValidationUtil.validate(
curRoots,
- patch(patchedSuperRoot, defaultsNode),
+ changes,
this::getRootNode,
cachedAnnotations,
validators
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
index 6fbc031..1ed111f 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.RandomAccess;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.ConfigurationTree;
@@ -66,7 +66,7 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
}
/** {@inheritDoc} */
- @Override public final Future<Void> change(Consumer<CHANGE> change) {
+ @Override public final CompletableFuture<Void> change(Consumer<CHANGE> change) {
Objects.requireNonNull(change, "Configuration consumer cannot be null.");
assert keys instanceof RandomAccess;
@@ -100,8 +100,12 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
return refreshValue();
}
- /** {@inheritDoc} */
- @Override public Map<String, ConfigurationProperty<?, ?>> members() {
+ /**
+ * Returns all child nodes of the current configuration tree node.
+ *
+ * @return Map from childs keys to a corresponding {@link ConfigurationProperty}.
+ */
+ public Map<String, ConfigurationProperty<?, ?>> members() {
return Collections.unmodifiableMap(members);
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
index 5bd67c1..6f4b85d 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
@@ -21,7 +21,7 @@ import java.io.Serializable;
import java.util.List;
import java.util.Objects;
import java.util.RandomAccess;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
import org.apache.ignite.configuration.ConfigurationValue;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -53,7 +53,7 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
}
/** {@inheritDoc} */
- @Override public Future<Void> update(T newValue) {
+ @Override public CompletableFuture<Void> update(T newValue) {
Objects.requireNonNull(newValue, "Configuration value cannot be null.");
assert keys instanceof RandomAccess;
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index 6a90b15..d6b97ee 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -17,13 +17,18 @@
package org.apache.ignite.internal.configuration.tree;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.ignite.configuration.NamedListChange;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
/**
* Configuration node implementation for the collection of named {@link InnerNode}s. Unlike implementations of
@@ -33,7 +38,12 @@ import org.apache.ignite.configuration.annotation.NamedConfigValue;
*/
public final class NamedListNode<N extends InnerNode> implements NamedListChange<N>, TraversableTreeNode, ConstructableTreeNode {
/** Name of a synthetic configuration property that describes the order of elements in a named list. */
- public static final String ORDER_IDX = "<idx>";
+ public static final String ORDER_IDX = "<order>";
+
+ /**
+ * Name of a synthetic configuration property that's used to store "key" of the named list element in the storage.
+ */
+ public static final String NAME = "<name>";
/** Configuration name for the synthetic key. */
private final String syntheticKeyName;
@@ -41,8 +51,11 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/** Supplier of new node objects when new list element node has to be created. */
private final Supplier<N> valSupplier;
- /** Internal container for named list element. Maps keys to named list elements nodes. */
- private final OrderedMap<N> map;
+ /** Internal container for named list element. Maps keys to named list elements nodes with their internal ids. */
+ private final OrderedMap<ElementDescriptor<N>> map;
+
+ /** Mapping from internal ids to public keys. */
+ private final Map<String, String> reverseIdMap;
/**
* Default constructor.
@@ -55,6 +68,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
this.syntheticKeyName = syntheticKeyName;
this.valSupplier = valSupplier;
map = new OrderedMap<>();
+ reverseIdMap = new HashMap<>();
}
/**
@@ -65,7 +79,11 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
private NamedListNode(NamedListNode<N> node) {
syntheticKeyName = node.syntheticKeyName;
valSupplier = node.valSupplier;
- map = new OrderedMap<>(node.map);
+ map = new OrderedMap<>();
+ reverseIdMap = new HashMap<>(node.reverseIdMap);
+
+ for (String key : node.map.keys())
+ map.put(key, node.map.get(key).shallowCopy());
}
/** {@inheritDoc} */
@@ -80,12 +98,16 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/** {@inheritDoc} */
@Override public final N get(String key) {
- return map.get(key);
+ ElementDescriptor<N> element = map.get(key);
+
+ return element == null ? null : element.value;
}
/** {@inheritDoc} */
@Override public N get(int index) throws IndexOutOfBoundsException {
- return map.get(index);
+ ElementDescriptor<N> element = map.get(index);
+
+ return element == null ? null : element.value;
}
/** {@inheritDoc} */
@@ -100,11 +122,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
checkNewKey(key);
- N val = valSupplier.get();
+ ElementDescriptor<N> element = new ElementDescriptor<>(valSupplier.get());
+
+ map.put(key, element);
- map.put(key, val);
+ reverseIdMap.put(element.internalId, key);
- valConsumer.accept(val);
+ valConsumer.accept(element.value);
return this;
}
@@ -119,11 +143,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
checkNewKey(key);
- N val = valSupplier.get();
+ ElementDescriptor<N> element = new ElementDescriptor<>(valSupplier.get());
- map.putByIndex(index, key, val);
+ map.putByIndex(index, key, element);
- valConsumer.accept(val);
+ reverseIdMap.put(element.internalId, key);
+
+ valConsumer.accept(element.value);
return this;
}
@@ -139,11 +165,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
checkNewKey(key);
- N val = valSupplier.get();
+ ElementDescriptor<N> element = new ElementDescriptor<>(valSupplier.get());
+
+ map.putAfter(precedingKey, key, element);
- map.putAfter(precedingKey, key, val);
+ reverseIdMap.put(element.internalId, key);
- valConsumer.accept(val);
+ valConsumer.accept(element.value);
return this;
}
@@ -153,17 +181,47 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
Objects.requireNonNull(key, "key");
Objects.requireNonNull(valConsumer, "valConsumer");
- if (map.containsKey(key) && map.get(key) == null)
+ if (map.containsKey(key) && map.get(key).value == null)
throw new IllegalArgumentException("You can't create entity that has just been deleted [key=" + key + ']');
- N val = map.get(key);
+ ElementDescriptor<N> element = map.get(key);
+
+ if (element == null) {
+ element = new ElementDescriptor<>(valSupplier.get());
- if (val == null)
- map.put(key, val = valSupplier.get());
+ reverseIdMap.put(element.internalId, key);
+ }
else
- map.put(key, val = (N)val.copy());
+ element = element.copy();
+
+ map.put(key, element);
+
+ valConsumer.accept(element.value);
+
+ return this;
+ }
- valConsumer.accept(val);
+ /** {@inheritDoc} */
+ @Override public NamedListChange<N> rename(String oldKey, String newKey) {
+ Objects.requireNonNull(oldKey, "oldKey");
+ Objects.requireNonNull(newKey, "newKey");
+
+ if (!map.containsKey(oldKey))
+ throw new IllegalArgumentException("Element with name " + oldKey + " does not exist.");
+
+ ElementDescriptor<N> element = map.get(oldKey);
+
+ if (element.value == null) {
+ throw new IllegalArgumentException(
+ "Can't rename entity that has just been deleted [key=" + oldKey + ']'
+ );
+ }
+
+ checkNewKey(newKey);
+
+ map.rename(oldKey, newKey);
+
+ reverseIdMap.put(element.internalId, newKey);
return this;
}
@@ -187,7 +245,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
Objects.requireNonNull(key, "key");
if (map.containsKey(key))
- map.put(key, null);
+ map.get(key).value = null;
return this;
}
@@ -202,12 +260,70 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
}
/**
+ * Sets an internal id for the value associated with the passed key. Should not be used in arbitrary code. Refer
+ * to {@link ConfigurationUtil#fillFromPrefixMap(ConstructableTreeNode, Map)} for further details on the usage.
+ *
+ * @param key Key to update. Should be present in the named list. Nothing will happen if the key is missing.
+ * @param internalId New id to associate with the key.
+ */
+ public void setInternalId(String key, String internalId) {
+ ElementDescriptor<N> element = map.get(key);
+
+ if (element != null) {
+ reverseIdMap.remove(element.internalId);
+
+ element.internalId = internalId;
+
+ reverseIdMap.put(internalId, key);
+ }
+ }
+
+ /**
+ * Returns internal id for the value associated with the passed key.
+ *
+ * @param key Key.
+ * @return Internal id.
+ *
+ * @throws IllegalArgumentException If {@code key} is not found in the named list.
+ */
+ public String internalId(String key) {
+ ElementDescriptor<N> element = map.get(key);
+
+ if (element == null)
+ throw new IllegalArgumentException("Element with name '" + key + "' does not exist.");
+
+ return element.internalId;
+ }
+
+ /**
+ * Returns public key associated with the internal id.
+ *
+ * @param internalId Internat id.
+ * @return Key.
+ */
+ public String keyByInternalId(String internalId) {
+ return reverseIdMap.get(internalId);
+ }
+
+ /**
+ * Returns collection of internal ids in this named list node.
+ *
+ * @return Set of internal ids.
+ */
+ public Collection<String> internalIds() {
+ return Collections.unmodifiableSet(reverseIdMap.keySet());
+ }
+
+ /**
* Deletes named list element.
*
* @param key Element's key.
*/
public void forceDelete(String key) {
- map.remove(key);
+ ElementDescriptor<N> removed = map.remove(key);
+
+ if (removed != null)
+ reverseIdMap.remove(removed.internalId);
}
/**
@@ -222,20 +338,68 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/** {@inheritDoc} */
@Override public void construct(String key, ConfigurationSource src) {
if (src == null)
- map.put(key, null);
- else {
- N val = map.get(key);
-
- val = val == null ? valSupplier.get() : (N)val.copy();
-
- map.put(key, val);
-
- src.descend(val);
- }
+ delete(key);
+ else
+ createOrUpdate(key, src::descend);
}
/** {@inheritDoc} */
@Override public NamedListNode<N> copy() {
return new NamedListNode<>(this);
}
+
+ /**
+ * Descriptor for internal named list element representation. Has node itself and its internal id.
+ *
+ * @param <N> Type of the node.
+ */
+ private static class ElementDescriptor<N extends InnerNode> {
+ /** Element's internal id. */
+ public String internalId;
+
+ /** Element node value. */
+ public N value;
+
+ /**
+ * Constructor.
+ *
+ * @param value Node instance.
+ */
+ ElementDescriptor(N value) {
+ this.value = value;
+ // Remove dashes so that id would be a bit shorter and easier to validate in tests.
+ // This string won't be visible by end users anyway.
+ internalId = UUID.randomUUID().toString().replace("-", "");
+ }
+
+ /**
+ * Private constructor with entire fields list.
+ *
+ * @param internalId Internal id.
+ * @param value Node instance.
+ */
+ private ElementDescriptor(String internalId, N value) {
+ this.internalId = internalId;
+ this.value = value;
+ }
+
+ /**
+ * Makes a copy of the element descriptor. Not to be confused with {@link #shallowCopy()}.
+ *
+ * @return New instance with the same internal id but copied node instance.
+ * @see InnerNode#copy()
+ */
+ public ElementDescriptor<N> copy() {
+ return new ElementDescriptor<>(internalId, (N)value.copy());
+ }
+
+ /**
+ * Makes a copy of the element descriptor, preserving same fields values.
+ *
+ * @return New instance with the same internal id and node instance.
+ */
+ public ElementDescriptor<N> shallowCopy() {
+ return new ElementDescriptor<>(internalId, value);
+ }
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
index 61600c9..a9f2739 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -148,10 +148,14 @@ public class ConfigurationFlattener {
Map<String, Integer> newKeysToOrderIdxMap = deletion ? null
: keysToOrderIdx(newNode);
- for (String namedListKey : newNode.namedListKeys()) {
- withTracking(namedListKey, true, false, () -> {
- InnerNode oldNamedElement = oldNode.get(namedListKey);
- InnerNode newNamedElement = newNode.get(namedListKey);
+ for (String newNodeKey : newNode.namedListKeys()) {
+ String newNodeInternalId = newNode.internalId(newNodeKey);
+
+ withTracking(newNodeInternalId, false, false, () -> {
+ InnerNode newNamedElement = newNode.get(newNodeKey);
+
+ String oldNodeKey = oldNode.keyByInternalId(newNodeInternalId);
+ InnerNode oldNamedElement = oldNode.get(oldNodeKey);
// Deletion of nonexistent element.
if (oldNamedElement == null && newNamedElement == null)
@@ -173,8 +177,8 @@ public class ConfigurationFlattener {
}
}
- Integer newIdx = newKeysToOrderIdxMap == null ? null : newKeysToOrderIdxMap.get(namedListKey);
- Integer oldIdx = oldKeysToOrderIdxMap == null ? null : oldKeysToOrderIdxMap.get(namedListKey);
+ Integer newIdx = newKeysToOrderIdxMap == null ? null : newKeysToOrderIdxMap.get(newNodeKey);
+ Integer oldIdx = oldKeysToOrderIdxMap == null ? null : oldKeysToOrderIdxMap.get(newNodeKey);
// We should "persist" changed indexes only.
if (newIdx != oldIdx || singleTreeTraversal || newNamedElement == null) {
@@ -183,6 +187,15 @@ public class ConfigurationFlattener {
resMap.put(orderKey, deletion || newNamedElement == null ? null : newIdx);
}
+ // If it's creation / deletion / rename.
+ if (singleTreeTraversal || oldNamedElement == null || newNamedElement == null
+ || !oldNodeKey.equals(newNodeKey)
+ ) {
+ String idKey = currentKey() + NamedListNode.NAME;
+
+ resMap.put(idKey, deletion || newNamedElement == null ? null : newNodeKey);
+ }
+
return null;
});
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
index 34b0558..48b6cfa 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
@@ -18,15 +18,17 @@
package org.apache.ignite.internal.configuration.util;
import java.io.Serializable;
+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.stream.Collectors;
+import java.util.function.BiFunction;
import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.NamedListView;
import org.apache.ignite.configuration.notifications.ConfigurationListener;
+import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
import org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
import org.apache.ignite.internal.configuration.DynamicConfiguration;
import org.apache.ignite.internal.configuration.DynamicProperty;
@@ -60,7 +62,14 @@ public class ConfigurationNotificationsUtil {
if (oldInnerNode == null || oldInnerNode == newInnerNode)
return;
- notifyPublicListeners(cfgNode.listeners(), oldInnerNode, newInnerNode, storageRevision, futures);
+ notifyPublicListeners(
+ cfgNode.listeners(),
+ oldInnerNode,
+ newInnerNode,
+ storageRevision,
+ futures,
+ ConfigurationListener::onUpdate
+ );
oldInnerNode.traverseChildren(new ConfigurationVisitor<Void>() {
/** {@inheritDoc} */
@@ -70,7 +79,14 @@ public class ConfigurationNotificationsUtil {
if (newLeaf != oldLeaf) {
var dynProperty = (DynamicProperty<Serializable>)cfgNode.members().get(key);
- notifyPublicListeners(dynProperty.listeners(), oldLeaf, newLeaf, storageRevision, futures);
+ notifyPublicListeners(
+ dynProperty.listeners(),
+ oldLeaf,
+ newLeaf,
+ storageRevision,
+ futures,
+ ConfigurationListener::onUpdate
+ );
}
return null;
@@ -94,46 +110,100 @@ public class ConfigurationNotificationsUtil {
if (newNamedList != oldNamedList) {
var namedListCfg = (NamedListConfiguration<?, InnerNode, ?>)cfgNode.members().get(key);
- notifyPublicListeners(namedListCfg.listeners(), (NamedListView<InnerNode>)oldNamedList, newNamedList, storageRevision, futures);
+ notifyPublicListeners(
+ namedListCfg.listeners(),
+ (NamedListView<InnerNode>)oldNamedList,
+ newNamedList,
+ storageRevision,
+ futures,
+ ConfigurationListener::onUpdate
+ );
// This is optimization, we could use "NamedListConfiguration#get" directly, but we don't want to.
-
List<String> oldNames = oldNamedList.namedListKeys();
List<String> newNames = newNamedList.namedListKeys();
+ //TODO https://issues.apache.org/jira/browse/IGNITE-15193
Map<String, ConfigurationProperty<?, ?>> namedListCfgMembers = namedListCfg.members();
Set<String> created = new HashSet<>(newNames);
created.removeAll(oldNames);
- if (!created.isEmpty()) {
- List<ConfigurationListener<InnerNode>> list = namedListCfg.extendedListeners()
- .stream()
- .map(l -> (ConfigurationListener<InnerNode>)l::onCreate)
- .collect(Collectors.toList());
+ Set<String> deleted = new HashSet<>(oldNames);
+ deleted.removeAll(newNames);
- for (String name : created)
- notifyPublicListeners(list, null, newNamedList.get(name), storageRevision, futures);
+ Map<String, String> renamed = new HashMap<>();
+ if (!created.isEmpty() && !deleted.isEmpty()) {
+ Map<String, String> createdIds = new HashMap<>();
+
+ for (String createdKey : created)
+ createdIds.put(newNamedList.internalId(createdKey), createdKey);
+
+ // Avoiding ConcurrentModificationException.
+ for (String deletedKey : Set.copyOf(deleted)) {
+ String internalId = oldNamedList.internalId(deletedKey);
+
+ String maybeRenamedKey = createdIds.get(internalId);
+
+ if (maybeRenamedKey == null)
+ continue;
+
+ deleted.remove(deletedKey);
+ created.remove(maybeRenamedKey);
+ renamed.put(deletedKey, maybeRenamedKey);
+ }
}
- Set<String> deleted = new HashSet<>(oldNames);
- deleted.removeAll(newNames);
+ if (!created.isEmpty()) {
+ for (String name : created)
+ notifyPublicListeners(
+ namedListCfg.extendedListeners(),
+ null,
+ newNamedList.get(name),
+ storageRevision,
+ futures,
+ ConfigurationNamedListListener::onCreate
+ );
+ }
if (!deleted.isEmpty()) {
- List<ConfigurationListener<InnerNode>> list = namedListCfg.extendedListeners()
- .stream()
- .map(l -> (ConfigurationListener<InnerNode>)l::onDelete)
- .collect(Collectors.toList());
+ for (String name : deleted) {
+ notifyPublicListeners(
+ namedListCfg.extendedListeners(),
+ oldNamedList.get(name),
+ null,
+ storageRevision,
+ futures,
+ ConfigurationNamedListListener::onDelete
+ );
+ }
+ }
- for (String name : deleted)
- notifyPublicListeners(list, oldNamedList.get(name), null, storageRevision, futures);
+ if (!renamed.isEmpty()) {
+ for (Map.Entry<String, String> entry : renamed.entrySet()) {
+ notifyPublicListeners(
+ namedListCfg.extendedListeners(),
+ oldNamedList.get(entry.getKey()),
+ newNamedList.get(entry.getValue()),
+ storageRevision,
+ futures,
+ (listener, evt) -> listener.onRename(entry.getKey(), entry.getValue(), evt)
+ );
+ }
}
for (String name : newNames) {
if (!oldNames.contains(name))
continue;
- notifyPublicListeners(namedListCfg.extendedListeners(), oldNamedList.get(name), newNamedList.get(name), storageRevision, futures);
+ notifyPublicListeners(
+ namedListCfg.extendedListeners(),
+ oldNamedList.get(name),
+ newNamedList.get(name),
+ storageRevision,
+ futures,
+ ConfigurationListener::onUpdate
+ );
var dynCfg = (DynamicConfiguration<InnerNode, ?>)namedListCfgMembers.get(name);
@@ -149,19 +219,23 @@ public class ConfigurationNotificationsUtil {
/**
* Invoke {@link ConfigurationListener#onUpdate(ConfigurationNotificationEvent)} on all passed listeners and put
* results in {@code futures}. Not recursively.
+ *
* @param listeners List o flisteners.
* @param oldVal Old configuration value.
* @param newVal New configuration value.
* @param storageRevision Storage revision.
* @param futures Write-only list of futures.
+ * @param updater Update closure to be invoked on the listener instance.
* @param <V> Type of the node.
+ * @param <L> Type of the configuration listener.
*/
- private static <V> void notifyPublicListeners(
- List<? extends ConfigurationListener<V>> listeners,
+ private static <V, L extends ConfigurationListener<V>> void notifyPublicListeners(
+ List<? extends L> listeners,
V oldVal,
V newVal,
long storageRevision,
- List<CompletableFuture<?>> futures
+ List<CompletableFuture<?>> futures,
+ BiFunction<L, ConfigurationNotificationEvent<V>, CompletableFuture<?>> updater
) {
ConfigurationNotificationEvent<V> evt = new ConfigurationNotificationEventImpl<>(
oldVal,
@@ -169,9 +243,9 @@ public class ConfigurationNotificationsUtil {
storageRevision
);
- for (ConfigurationListener<V> listener : listeners) {
+ for (L listener : listeners) {
try {
- CompletableFuture<?> future = listener.onUpdate(evt);
+ CompletableFuture<?> future = updater.apply(listener, evt);
if (future != null && (future.isCompletedExceptionally() || future.isCancelled() || !future.isDone()))
futures.add(future);
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
index 58e3a9d..5b33d68 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
@@ -27,7 +27,6 @@ import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.stream.Collectors;
import org.apache.ignite.configuration.NamedListView;
-import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -37,6 +36,9 @@ import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
/** */
public class ConfigurationUtil {
+ /** Configuration source that copies values without modifying tham. */
+ private static final ConfigurationSource EMPTY_CFG_SRC = new ConfigurationSource() {};
+
/**
* Replaces all {@code .} and {@code \} characters with {@code \.} and {@code \\} respectively.
*
@@ -263,7 +265,7 @@ public class ConfigurationUtil {
assert val == null || val instanceof Map || val instanceof Serializable;
// Ordering of indexes must be skipped here because they make no sense in this context.
- if (key.equals(NamedListNode.ORDER_IDX))
+ if (key.equals(NamedListNode.ORDER_IDX) || key.equals(NamedListNode.NAME))
continue;
if (val == null)
@@ -289,21 +291,54 @@ public class ConfigurationUtil {
var orderedKeys = new ArrayList<>(((NamedListView<?>)node).namedListKeys());
for (Map.Entry<String, ?> entry : map.entrySet()) {
- String key = entry.getKey();
+ String internalId = entry.getKey();
Object val = entry.getValue();
assert val == null || val instanceof Map || val instanceof Serializable;
+ String oldKey = node.keyByInternalId(internalId);
+
if (val == null) {
// Given that this particular method is applied to modify existing trees rather than
// creating new trees, a "hack" is required in this place. "construct" is designed to create
// "change" objects, thus it would just nullify named list element instead of deleting it.
- node.forceDelete(key);
+ node.forceDelete(oldKey);
}
else if (val instanceof Map) {
+ Map<String, ?> map = (Map<String, ?>)val;
+ int sizeDiff = 0;
+
// For every named list entry modification we must take its index into account.
// We do this by modifying "orderedKeys" when index is explicitly passed.
- Object idxObj = ((Map<?, ?>)val).get(NamedListNode.ORDER_IDX);
+ Object idxObj = map.get(NamedListNode.ORDER_IDX);
+
+ if (idxObj != null)
+ sizeDiff++;
+
+ String newKey = (String)map.get(NamedListNode.NAME);
+
+ if (newKey != null)
+ sizeDiff++;
+
+ boolean construct = map.size() != sizeDiff;
+
+ if (oldKey == null) {
+ node.construct(newKey, new InnerConfigurationSource(map));
+
+ node.setInternalId(newKey, internalId);
+ }
+ else if (newKey != null) {
+ node.rename(oldKey, newKey);
+
+ if (construct)
+ node.construct(newKey, new InnerConfigurationSource(map));
+ }
+ else if (construct)
+ node.construct(oldKey, new InnerConfigurationSource(map));
+ // Else it's just index adjustment after new elements insertion.
+
+ if (newKey == null)
+ newKey = oldKey;
if (idxObj != null) {
assert idxObj instanceof Integer : val;
@@ -319,18 +354,16 @@ public class ConfigurationUtil {
while (idx != orderedKeys.size())
orderedKeys.add(null);
- orderedKeys.add(key);
+ orderedKeys.add(newKey);
}
else
- orderedKeys.set(idx, key);
+ orderedKeys.set(idx, newKey);
}
-
- node.construct(key, new InnerConfigurationSource((Map<String, ?>)val));
}
else {
assert val instanceof Serializable;
- node.construct(key, new LeafConfigurationSource((Serializable)val));
+ node.construct(oldKey, new LeafConfigurationSource((Serializable)val));
}
}
@@ -365,111 +398,70 @@ public class ConfigurationUtil {
}
/**
- * Convert tree node into patching configuration source.
+ * Fill {@code node} node with default values where nodes are {@code null}.
*
- * @param changes Tree node that contains prepared changes.
- * @return Configuration source.
+ * @param node Node.
*/
- public static ConfigurationSource nodePatcher(TraversableTreeNode changes) {
- var scrVisitor = new ConfigurationVisitor<ConfigurationSource>() {
- @Override public ConfigurationSource visitInnerNode(String key, InnerNode node) {
- return new PatchInnerConfigurationSource(node);
- }
+ public static void addDefaults(InnerNode node) {
+ node.traverseChildren(new ConfigurationVisitor<>() {
+ @Override public Object visitLeafNode(String key, Serializable val) {
+ // If source value is null then inititalise the same value on the destination node.
+ if (val == null)
+ node.constructDefault(key);
- @Override public <N extends InnerNode> ConfigurationSource visitNamedListNode(String key, NamedListNode<N> node) {
- return new PatchNamedListConfigurationSource(node);
+ return null;
}
- };
- return changes.accept(null, scrVisitor);
- }
+ @Override public Object visitInnerNode(String key, InnerNode innerNode) {
+ // Instantiate field in destination node before doing something else or copy it if it wasn't null.
+ node.construct(key, EMPTY_CFG_SRC);
- /**
- * Convert root node into patching configuration source for the super root.
- *
- * @param rootKey Root key.
- * @param changes Root node.
- * @return Configuration source.
- */
- public static ConfigurationSource superRootPatcher(RootKey rootKey, TraversableTreeNode changes) {
- ConfigurationSource rootSrc = nodePatcher(changes);
+ addDefaults(node.traverseChild(key, innerNodeVisitor()));
- return new ConfigurationSource() {
- @Override public void descend(ConstructableTreeNode node) {
- node.construct(rootKey.key(), rootSrc);
+ return null;
}
- };
- }
-
- /**
- * Apply changes on top of existing node. Creates completely new object while reusing parts of the original tree
- * that weren't modified.
- *
- * @param root Immutable configuration node.
- * @param changes Change or Init object to be applied.
- * @param <C> Type of the root.
- * @return Patched root.
- */
- public static <C extends ConstructableTreeNode> C patch(C root, TraversableTreeNode changes) {
- assert root.getClass() == changes.getClass(); // Yes.
- ConfigurationSource src = nodePatcher(changes);
+ @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> namedList) {
+ // Copy internal map.
+ node.construct(key, EMPTY_CFG_SRC);
- assert src != null;
+ namedList = (NamedListNode<N>)node.traverseChild(key, namedListNodeVisitor());
- C copy = (C)root.copy();
+ for (String namedListKey : namedList.namedListKeys()) {
+ if (namedList.get(namedListKey) != null) {
+ // Copy the element.
+ namedList.construct(namedListKey, EMPTY_CFG_SRC);
- src.descend(copy);
+ addDefaults(namedList.get(namedListKey));
+ }
+ }
- return copy;
+ return null;
+ }
+ });
}
/**
- * Fill {@code dst} node with default values, required to complete {@code src} node.
- * These two objects can be the same, this would mean that all {@code null} values of {@code scr} will be
- * replaced with defaults if it's possible.
+ * Recursively removes all nullified named list elements.
*
- * @param src Source node.
- * @param dst Destination node.
+ * @param node Inner node for processing.
*/
- public static void addDefaults(InnerNode src, InnerNode dst) {
- assert src.getClass() == dst.getClass();
-
- src.traverseChildren(new ConfigurationVisitor<>() {
- @Override public Object visitLeafNode(String key, Serializable val) {
- // If source value is null then inititalise the same value on the destination node.
- if (val == null)
- dst.constructDefault(key);
+ public static void dropNulls(InnerNode node) {
+ node.traverseChildren(new ConfigurationVisitor<>() {
+ @Override public Object visitInnerNode(String key, InnerNode innerNode) {
+ dropNulls(innerNode);
return null;
}
- @Override public Object visitInnerNode(String key, InnerNode srcNode) {
- // Instantiate field in destination node before doing something else.
- // Not a big deal if it wasn't null.
- dst.construct(key, new ConfigurationSource() {});
-
- // Get that inner node from destination to continue the processing.
- InnerNode dstNode = dst.traverseChild(key, innerNodeVisitor());
+ @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> namedList) {
+ for (String namedListKey : namedList.namedListKeys()) {
+ N element = namedList.get(namedListKey);
- // "dstNode" is guaranteed to not be null even if "src" and "dst" match.
- // Null in "srcNode" means that we should initialize everything that we can in "dstNode"
- // unconditionally. It's only possible if we pass it as a source as well.
- addDefaults(srcNode == null ? dstNode : srcNode, dstNode);
-
- return null;
- }
-
- @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> srcNamedList) {
- // Here we don't need to preemptively initialise corresponsing field, because it can never be null.
- NamedListNode<?> dstNamedList = dst.traverseChild(key, namedListNodeVisitor());
-
- for (String namedListKey : srcNamedList.namedListKeys()) {
- // But, in order to get non-null value from "dstNamedList.get(namedListKey)" we must explicitly
- // ensure its existance.
- dstNamedList.construct(namedListKey, new ConfigurationSource() {});
-
- addDefaults(srcNamedList.get(namedListKey), dstNamedList.get(namedListKey));
+ if (element == null)
+ namedList.forceDelete(namedListKey);
+ else
+ dropNulls(element);
}
return null;
@@ -510,113 +502,4 @@ public class ConfigurationUtil {
}
};
}
-
- /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
- private static class PatchLeafConfigurationSource implements ConfigurationSource {
- /** */
- private final Serializable val;
-
- /**
- * @param val Value.
- */
- PatchLeafConfigurationSource(Serializable val) {
- this.val = val;
- }
-
- /** {@inheritDoc} */
- @Override public <T> T unwrap(Class<T> clazz) {
- assert clazz.isInstance(val);
-
- return clazz.cast(val);
- }
-
- /** {@inheritDoc} */
- @Override public void descend(ConstructableTreeNode node) {
- throw new UnsupportedOperationException("descend");
- }
- }
-
- /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
- private static class PatchInnerConfigurationSource implements ConfigurationSource {
- /** */
- private final InnerNode srcNode;
-
- /**
- * @param srcNode Inner node.
- */
- PatchInnerConfigurationSource(InnerNode srcNode) {
- this.srcNode = srcNode;
- }
-
- /** {@inheritDoc} */
- @Override public <T> T unwrap(Class<T> clazz) {
- throw new UnsupportedOperationException("unwrap");
- }
-
- /** {@inheritDoc} */
- @Override public void descend(ConstructableTreeNode dstNode) {
- assert srcNode.getClass() == dstNode.getClass() : srcNode.getClass() + " : " + dstNode.getClass();
-
- if (srcNode == dstNode)
- return;
-
- srcNode.traverseChildren(new ConfigurationVisitor<>() {
- @Override public Void visitLeafNode(String key, Serializable val) {
- if (val != null)
- dstNode.construct(key, new PatchLeafConfigurationSource(val));
-
- return null;
- }
-
- @Override public Void visitInnerNode(String key, InnerNode node) {
- if (node != null)
- dstNode.construct(key, new PatchInnerConfigurationSource(node));
-
- return null;
- }
-
- @Override public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> node) {
- if (node != null)
- dstNode.construct(key, new PatchNamedListConfigurationSource(node));
-
- return null;
- }
- });
- }
- }
-
- /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
- private static class PatchNamedListConfigurationSource implements ConfigurationSource {
- /** */
- private final NamedListNode<?> srcNode;
-
- /**
- * @param srcNode Named list node.
- */
- PatchNamedListConfigurationSource(NamedListNode<?> srcNode) {
- this.srcNode = srcNode;
- }
-
- /** {@inheritDoc} */
- @Override public <T> T unwrap(Class<T> clazz) {
- throw new UnsupportedOperationException("unwrap");
- }
-
- /** {@inheritDoc} */
- @Override public void descend(ConstructableTreeNode dstNode) {
- assert srcNode.getClass() == dstNode.getClass();
-
- if (srcNode == dstNode)
- return;
-
- for (String key : srcNode.namedListKeys()) {
- InnerNode node = srcNode.get(key);
-
- if (node == null)
- ((NamedListNode<?>)dstNode).forceDelete(key); // Same as in fillFromPrefixMap.
- else if (((NamedListView<?>)dstNode).get(key) != node)
- dstNode.construct(key, new PatchInnerConfigurationSource(node));
- }
- }
- }
}
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
index 46c7584..4485a6d 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
@@ -43,6 +43,7 @@ import org.apache.ignite.internal.affinity.AffinityManager;
import org.apache.ignite.internal.affinity.event.AffinityEvent;
import org.apache.ignite.internal.affinity.event.AffinityEventParameters;
import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
import org.apache.ignite.internal.manager.EventListener;
import org.apache.ignite.internal.manager.IgniteComponent;
@@ -63,6 +64,7 @@ import org.apache.ignite.internal.table.distributed.raft.PartitionListener;
import org.apache.ignite.internal.table.distributed.storage.InternalTableImpl;
import org.apache.ignite.internal.table.event.TableEvent;
import org.apache.ignite.internal.table.event.TableEventParameters;
+import org.apache.ignite.internal.util.ByteUtils;
import org.apache.ignite.internal.util.Cursor;
import org.apache.ignite.lang.ByteArray;
import org.apache.ignite.lang.IgniteBiTuple;
@@ -708,26 +710,20 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
*
* @return A set of table names.
*/
- private HashSet<String> tableNamesConfigured() {
+ //TODO This is an egregious violation of encapsulation. Current approach has to be revisited.
+ private Set<String> tableNamesConfigured() {
IgniteBiTuple<ByteArray, ByteArray> range = toRange(new ByteArray(PUBLIC_PREFIX));
- HashSet<String> tableNames = new HashSet<>();
+ Set<String> tableNames = new HashSet<>();
try (Cursor<Entry> cursor = metaStorageMgr.range(range.get1(), range.get2())) {
while (cursor.hasNext()) {
Entry entry = cursor.next();
- String keyTail = entry.key().toString().substring(PUBLIC_PREFIX.length());
+ List<String> keySplit = ConfigurationUtil.split(entry.key().toString());
- int idx = -1;
-
- //noinspection StatementWithEmptyBody
- while ((idx = keyTail.indexOf('.', idx + 1)) > 0 && keyTail.charAt(idx - 1) == '\\')
- ;
-
- String tablName = keyTail.substring(0, idx);
-
- tableNames.add(ConfigurationUtil.unescape(tablName));
+ if (keySplit.size() == 5 && NamedListNode.NAME.equals(keySplit.get(4)))
+ tableNames.add(ByteUtils.fromBytes(entry.value()).toString());
}
}
catch (Exception e) {
@@ -846,11 +842,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
* @return True if table configured, false otherwise.
*/
private boolean isTableConfigured(String name) {
- return metaStorageMgr.invoke(Conditions.exists(
- new ByteArray(PUBLIC_PREFIX + ConfigurationUtil.escape(name) + ".name")),
- Operations.noop(),
- Operations.noop()
- ).join();
+ return tableNamesConfigured().contains(name);
}
/**
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
index aa67576..fe52cab 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
@@ -36,6 +36,7 @@ import org.apache.ignite.internal.affinity.AffinityManager;
import org.apache.ignite.internal.affinity.event.AffinityEvent;
import org.apache.ignite.internal.affinity.event.AffinityEventParameters;
import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
import org.apache.ignite.internal.manager.EventListener;
import org.apache.ignite.internal.metastorage.MetaStorageManager;
@@ -49,6 +50,7 @@ import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConver
import org.apache.ignite.internal.schema.event.SchemaEvent;
import org.apache.ignite.internal.schema.event.SchemaEventParameters;
import org.apache.ignite.internal.table.distributed.TableManager;
+import org.apache.ignite.internal.util.ByteUtils;
import org.apache.ignite.internal.util.Cursor;
import org.apache.ignite.lang.ByteArray;
import org.apache.ignite.lang.IgniteLogger;
@@ -67,6 +69,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.commons.util.ReflectionUtils;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -87,6 +91,7 @@ import static org.mockito.Mockito.when;
* Tests scenarios for table manager.
*/
@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
public class TableManagerTest {
/** The logger. */
private static final IgniteLogger LOG = IgniteLogger.forClass(TableManagerTest.class);
@@ -488,8 +493,9 @@ public class TableManagerTest {
Entry mockEntry = mock(Entry.class);
- when(mockEntry.key()).thenReturn(new ByteArray(PUBLIC_PREFIX +
- ConfigurationUtil.escape(schemaTable.canonicalName()) + ".name"));
+ when(mockEntry.key()).thenReturn(new ByteArray(PUBLIC_PREFIX + "uuid." + NamedListNode.NAME));
+
+ when(mockEntry.value()).thenReturn(ByteUtils.toBytes(schemaTable.canonicalName()));
when(cursor.next()).thenReturn(mockEntry);