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/04/02 11:19:36 UTC
[ignite-3] branch main updated: IGNITE-14180 Implemented the
ability to subscribe to configuration updates. (#76)
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 297468b IGNITE-14180 Implemented the ability to subscribe to configuration updates. (#76)
297468b is described below
commit 297468b6b5a819e48b7b9dacfeb5c06d5f6dd910
Author: ibessonov <be...@gmail.com>
AuthorDate: Fri Apr 2 14:19:30 2021 +0300
IGNITE-14180 Implemented the ability to subscribe to configuration updates. (#76)
---
modules/configuration-annotation-processor/pom.xml | 12 -
.../processor/internal/Processor.java | 31 +--
.../configuration/processor/internal/Utils.java | 109 +--------
.../storage => }/ConfigurationChangerTest.java | 28 ++-
.../internal/util/ConfigurationUtilTest.java | 82 ++++++-
.../internal/validation/ValidationUtilTest.java | 2 +-
.../notifications/ConfigurationListenerTest.java | 245 +++++++++++++++++++++
.../sample/LocalConfigurationSchema.java | 2 +-
.../sample/NetworkConfigurationSchema.java | 2 +-
.../ignite/configuration/sample/UsageTest.java | 16 +-
.../storage/TestConfigurationStorage.java | 25 +--
modules/configuration/pom.xml | 13 --
.../ignite/configuration/ConfigurationChanger.java | 195 +++++++++-------
.../configuration/ConfigurationProperty.java | 14 +-
.../configuration/ConfigurationRegistry.java | 55 ++++-
.../ignite/configuration/ConfigurationTree.java | 4 +-
.../ignite/configuration/ConfigurationValue.java | 6 +-
.../configuration/NamedConfigurationTree.java | 9 +-
.../ignite/configuration/PropertyListener.java | 61 -----
.../configuration/internal/ConfigurationNode.java | 19 +-
.../internal/DynamicConfiguration.java | 9 +-
.../configuration/internal/DynamicProperty.java | 16 +-
.../internal/NamedListConfiguration.java | 25 ++-
.../ConfigurationNotificationEventImpl.java | 50 +++++
.../util/ConfigurationNotificationsUtil.java | 184 ++++++++++++++++
.../internal/util/ConfigurationUtil.java | 42 ++++
.../ConfigurationListener.java} | 28 +--
.../ConfigurationNamedListListener.java | 44 ++++
.../ConfigurationNotificationEvent.java} | 45 ++--
.../storage/ConfigurationStorage.java | 16 +-
.../storage/ConfigurationStorageListener.java | 1 +
.../apache/ignite/configuration/storage/Data.java | 22 +-
.../ignite/configuration/tree/NamedListNode.java | 5 +
.../ignite/configuration/tree/NamedListView.java | 5 +
modules/network/pom.xml | 12 -
.../InMemoryConfigurationStorage.java | 25 +--
.../rest/presentation/json/JsonConverterTest.java | 7 +
.../json/TestConfigurationStorage.java | 14 +-
parent/pom.xml | 7 -
39 files changed, 1036 insertions(+), 451 deletions(-)
diff --git a/modules/configuration-annotation-processor/pom.xml b/modules/configuration-annotation-processor/pom.xml
index be4a44b..3e880b4 100644
--- a/modules/configuration-annotation-processor/pom.xml
+++ b/modules/configuration-annotation-processor/pom.xml
@@ -49,18 +49,6 @@
<!-- Test dependencies. -->
<dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<scope>test</scope>
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
index 81395d6..9b4f2e0 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -50,19 +50,12 @@ import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
-import org.apache.ignite.configuration.ConfigurationChanger;
-import org.apache.ignite.configuration.ConfigurationRegistry;
-import org.apache.ignite.configuration.ConfigurationTree;
-import org.apache.ignite.configuration.ConfigurationValue;
import org.apache.ignite.configuration.NamedConfigurationTree;
-import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.internal.DynamicProperty;
import org.apache.ignite.configuration.internal.NamedListConfiguration;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
@@ -92,6 +85,9 @@ public class Processor extends AbstractProcessor {
/** Inherit doc javadoc. */
private static final String INHERIT_DOC = "{@inheritDoc}";
+ /** */
+ private static final ClassName ROOT_KEY_CLASSNAME = ClassName.get("org.apache.ignite.configuration", "RootKey");
+
/**
* Constructor.
*/
@@ -302,14 +298,17 @@ public class Processor extends AbstractProcessor {
ClassName schemaClassName
) {
ClassName viewClassName = Utils.getViewName(schemaClassName);
- ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ClassName.get(RootKey.class), configInterface, viewClassName);
+
+ ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, configInterface, viewClassName);
ClassName nodeClassName = Utils.getNodeName(schemaClassName);
+ ClassName cfgRegistryClassName = ClassName.get("org.apache.ignite.configuration", "ConfigurationRegistry");
+
FieldSpec keyField = FieldSpec.builder(fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
.initializer(
"$T.newRootKey($S, $T.class, $T::new, $T::new)",
- ConfigurationRegistry.class, configDesc.getName(), storageType, nodeClassName,
+ cfgRegistryClassName, configDesc.getName(), storageType, nodeClassName,
Utils.getConfigurationName(schemaClassName)
)
.build();
@@ -394,8 +393,10 @@ public class Processor extends AbstractProcessor {
final Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
- ClassName dynPropClass = ClassName.get(DynamicProperty.class);
- ClassName confValueClass = ClassName.get(ConfigurationValue.class);
+ // It is necessary to use class names without loading classes so that we won't
+ // accidentally get NoClassDefFoundError
+ ClassName dynPropClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicProperty");
+ ClassName confValueClass = ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
TypeName genericType = baseType;
@@ -495,8 +496,8 @@ public class Processor extends AbstractProcessor {
}
builder
- .addParameter(ParameterizedTypeName.get(ClassName.get(RootKey.class), WILDCARD, WILDCARD), "rootKey")
- .addParameter(ConfigurationChanger.class, "changer");
+ .addParameter(ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, WILDCARD, WILDCARD), "rootKey")
+ .addParameter(ClassName.get("org.apache.ignite.configuration", "ConfigurationChanger"), "changer");
if (isRoot)
builder.addStatement("super($T.emptyList(), $S, rootKey, changer)", Collections.class, configName);
@@ -528,12 +529,12 @@ public class Processor extends AbstractProcessor {
final ClassName initClassName = Utils.getInitName(schemaClassName);
final ClassName changeClassName = Utils.getChangeName(schemaClassName);
- ClassName dynConfClass = ClassName.get(DynamicConfiguration.class);
+ ClassName dynConfClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicConfiguration");
TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, initClassName, changeClassName);
configurationClassBuilder.superclass(dynConfViewClassType);
- ClassName confTreeInterface = ClassName.get(ConfigurationTree.class);
+ ClassName confTreeInterface = ClassName.get("org.apache.ignite.configuration", "ConfigurationTree");
TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
index b63d1b9..1606a21 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
@@ -18,106 +18,22 @@ package org.apache.ignite.configuration.processor.internal;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.CodeBlock;
-import com.squareup.javapoet.FieldSpec;
-import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.lang.model.element.Modifier;
-import javax.lang.model.element.VariableElement;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.configuration.internal.NamedListConfiguration;
/**
* Annotation processing utilities.
*/
public class Utils {
+ /** */
+ private static final ClassName NAMED_LIST_CFG_CLASSNAME = ClassName.get("org.apache.ignite.configuration.internal", "NamedListConfiguration");
+
/** Private constructor. */
private Utils() {
}
/**
- * Create constructor for
- *
- * @param fieldSpecs List of fields.
- * @return Constructor method.
- */
- public static MethodSpec createConstructor(List<FieldSpec> fieldSpecs) {
- final MethodSpec.Builder builder = MethodSpec.constructorBuilder();
-
- for (FieldSpec field : fieldSpecs) {
- builder.addParameter(field.type, field.name);
- builder.addStatement("this.$L = $L", field.name, field.name);
- }
-
- return builder.build();
- }
-
- /**
- * Create getters for fields.
- *
- * @param fieldSpecs List of fields.
- * @return List of getter methods.
- */
- public static List<MethodSpec> createGetters(List<FieldSpec> fieldSpecs) {
- return fieldSpecs.stream().map(field ->
- MethodSpec.methodBuilder(field.name)
- .returns(field.type)
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addStatement("return $L", field.name)
- .build()).collect(Collectors.toList()
- );
- }
-
- /**
- * Create builder-style setters.
- *
- * @param fieldSpecs List of fields.
- * @return List of setter methods.
- */
- public static List<MethodSpec> createBuildSetters(List<FieldSpec> fieldSpecs) {
- return fieldSpecs.stream().map(field -> {
- return MethodSpec.methodBuilder("with" + field.name)
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addStatement("this.$L = $L", field.name, field.name)
- .build();
- }).collect(Collectors.toList());
- }
-
- /**
- * Create '{@code new SomeObject(arg1, arg2, ..., argN)}' code block.
- *
- * @param type Type of the new object.
- * @param fieldSpecs List of arguments.
- * @return New object code block.
- */
- public static CodeBlock newObject(TypeName type, List<VariableElement> fieldSpecs) {
- String args = fieldSpecs.stream().map(f -> f.getSimpleName().toString()).collect(Collectors.joining(", "));
- return CodeBlock.builder()
- .add("new $T($L)", type, args)
- .build();
- }
-
- /**
- * Get class with parameters, boxing them if necessary.
- *
- * @param clz Generic class.
- * @param types Generic parameters.
- * @return Parameterized type.
- */
- public static ParameterizedTypeName getParameterized(ClassName clz, TypeName... types) {
- types = Arrays.stream(types).map(t -> {
- if (t.isPrimitive())
- t = t.box();
- return t;
- }).toArray(TypeName[]::new);
- return ParameterizedTypeName.get(clz, types);
- }
-
- /**
* Get {@link ClassName} for configuration class.
*
* @param schemaClassName Configuration schema ClassName.
@@ -200,30 +116,13 @@ public class Utils {
if (type instanceof ParameterizedTypeName) {
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
- if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+ if (parameterizedTypeName.rawType.equals(NAMED_LIST_CFG_CLASSNAME))
return true;
}
return false;
}
/**
- * Get {@code DynamicConfiguration} inside of the named configuration.
- *
- * @param type Type name.
- * @return {@link DynamicConfiguration} class name.
- */
- public static TypeName unwrapNamedListConfigurationClass(TypeName type) {
- if (type instanceof ParameterizedTypeName) {
- ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
-
- if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
- return parameterizedTypeName.typeArguments.get(1);
- }
-
- throw new ProcessorException(type + " is not a NamedListConfiguration class");
- }
-
- /**
* @return {@code @SuppressWarnings("unchecked")} annotation spec object.
*/
public static AnnotationSpec suppressWarningsUnchecked() {
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
similarity index 87%
rename from modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
rename to modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
index 57d54a2..e621c7a 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/ConfigurationChangerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.configuration.sample.storage;
+package org.apache.ignite.configuration;
import java.io.Serializable;
import java.lang.annotation.Retention;
@@ -24,14 +24,13 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
-import org.apache.ignite.configuration.ConfigurationChangeException;
-import org.apache.ignite.configuration.ConfigurationChanger;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.configuration.validation.ValidationContext;
import org.apache.ignite.configuration.validation.ValidationIssue;
import org.apache.ignite.configuration.validation.Validator;
@@ -39,8 +38,9 @@ import org.junit.jupiter.api.Test;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.ignite.configuration.sample.storage.AConfiguration.KEY;
+import static org.apache.ignite.configuration.AConfiguration.KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -99,7 +99,8 @@ public class ConfigurationChangerTest {
.initChild(init -> init.initIntCfg(1).initStrCfg("1"))
.initElements(change -> change.create("a", init -> init.initStrCfg("1")));
- final ConfigurationChanger changer = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer.addRootKey(KEY);
changer.register(storage);
changer.change(Collections.singletonMap(KEY, data)).get(1, SECONDS);
@@ -129,10 +130,12 @@ public class ConfigurationChangerTest {
.create("b", init -> init.initStrCfg("2"))
);
- final ConfigurationChanger changer1 = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer1 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer1.addRootKey(KEY);
changer1.register(storage);
- final ConfigurationChanger changer2 = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer2 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer2.addRootKey(KEY);
changer2.register(storage);
changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
@@ -171,10 +174,12 @@ public class ConfigurationChangerTest {
.create("b", init -> init.initStrCfg("2"))
);
- final ConfigurationChanger changer1 = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer1 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer1.addRootKey(KEY);
changer1.register(storage);
- final ConfigurationChanger changer2 = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer2 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer2.addRootKey(KEY);
changer2.register(storage);
changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
@@ -203,7 +208,8 @@ public class ConfigurationChangerTest {
ANode data = new ANode().initChild(child -> child.initIntCfg(1));
- final ConfigurationChanger changer = new ConfigurationChanger(KEY);
+ ConfigurationChanger changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+ changer.addRootKey(KEY);
storage.fail(true);
@@ -258,7 +264,7 @@ public class ConfigurationChangerTest {
@Test
public void defaultsOnInit() throws Exception {
- var changer = new ConfigurationChanger();
+ var changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
changer.addRootKey(DefaultsConfiguration.KEY);
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
index 34f7f68..6e8061d 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
@@ -21,10 +21,14 @@ import java.util.List;
import java.util.Map;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
import org.junit.jupiter.api.Test;
+import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -72,7 +76,7 @@ public class ConfigurationUtilTest {
}
/** */
- @Config
+ @ConfigurationRoot(rootName = "root", storage = TestConfigurationStorage.class)
public static class ParentConfigurationSchema {
/** */
@NamedConfigValue
@@ -235,6 +239,56 @@ public class ConfigurationUtilTest {
/** */
@Test
+ public void nodeToFlatMap() {
+ var parentNode = new ParentNode();
+
+ var parentSuperRoot = new SuperRoot(emptyMap(), Map.of(
+ ParentConfiguration.KEY,
+ parentNode
+ ));
+
+ assertEquals(
+ emptyMap(),
+ ConfigurationUtil.nodeToFlatMap(null, parentSuperRoot)
+ );
+
+ // No defaults in this test so everything must be initialized explicitly.
+ parentNode.changeElements(elements ->
+ elements.create("name", element ->
+ element.initChild(child ->
+ child.initStr("foo")
+ )
+ )
+ );
+
+ assertEquals(
+ singletonMap("root.elements.name.child.str", "foo"),
+ ConfigurationUtil.nodeToFlatMap(null, parentSuperRoot)
+ );
+
+ assertEquals(
+ emptyMap(),
+ ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(emptyMap(), singletonMap(
+ ParentConfiguration.KEY,
+ new ParentNode().changeElements(elements ->
+ elements.delete("void")
+ )
+ )))
+ );
+
+ assertEquals(
+ singletonMap("root.elements.name.child.str", null),
+ ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(emptyMap(), singletonMap(
+ ParentConfiguration.KEY,
+ new ParentNode().changeElements(elements ->
+ elements.delete("name")
+ )
+ )))
+ );
+ }
+
+ /** */
+ @Test
public void patch() {
var originalRoot = new ParentNode().initElements(elements ->
elements.create("name1", element ->
@@ -285,4 +339,30 @@ public class ConfigurationUtilTest {
assertNull(shrinkedRoot.elements().get("name1"));
assertNotNull(shrinkedRoot.elements().get("name2"));
}
+
+ /** */
+ @Test
+ public void cleanupMatchingValues() {
+ var curParent = new ParentNode().initElements(elements -> elements
+ .create("missing", element -> {})
+ .create("match", element -> element.initChild(child -> child.initStr("match")))
+ .create("mismatch", element -> element.initChild(child -> child.initStr("foo")))
+ );
+
+ var newParent = new ParentNode().initElements(elements -> elements
+ .create("extra", element -> {})
+ .create("match", element -> element.initChild(child -> child.initStr("match")))
+ .create("mismatch", element -> element.initChild(child -> child.initStr("bar")))
+ );
+
+ ConfigurationUtil.cleanupMatchingValues(curParent, newParent);
+
+ // Old node stayed intact.
+ assertEquals("match", curParent.elements().get("match").child().str());
+ assertEquals("foo", curParent.elements().get("mismatch").child().str());
+
+ // New node was modified.
+ assertNull(newParent.elements().get("match").child().str());
+ assertEquals("bar", newParent.elements().get("mismatch").child().str());
+ }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
index bf00949..a289bde 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
@@ -31,7 +31,7 @@ import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.internal.SuperRoot;
import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
-import org.apache.ignite.configuration.sample.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.configuration.tree.NamedListView;
import org.apache.ignite.configuration.validation.ValidationContext;
import org.apache.ignite.configuration.validation.ValidationIssue;
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/notifications/ConfigurationListenerTest.java
new file mode 100644
index 0000000..8fa9320
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/notifications/ConfigurationListenerTest.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.notifications;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.ConfigurationRegistry;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static java.util.concurrent.CompletableFuture.completedFuture;
+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;
+
+/** */
+public class ConfigurationListenerTest {
+ /** */
+ @ConfigurationRoot(rootName = "parent", storage = TestConfigurationStorage.class)
+ public static class ParentConfigurationSchema {
+ /** */
+ @ConfigValue
+ private ChildConfigurationSchema child;
+
+ /** */
+ @NamedConfigValue
+ private ChildConfigurationSchema elements;
+ }
+
+ /** */
+ @Config
+ public static class ChildConfigurationSchema {
+ /** */
+ @Value(hasDefault = true)
+ public String str = "default";
+ }
+
+ /** */
+ private final ConfigurationRegistry registry = new ConfigurationRegistry();
+
+ /** */
+ private ParentConfiguration configuration;
+
+ /** */
+ @BeforeEach
+ public void before() {
+ registry.registerRootKey(ParentConfiguration.KEY);
+
+ registry.registerStorage(new TestConfigurationStorage());
+
+ registry.startStorageConfigurations(TestConfigurationStorage.class);
+
+ configuration = registry.getConfiguration(ParentConfiguration.KEY);
+ }
+
+ /** */
+ @AfterEach
+ public void after() {
+ registry.stop();
+ }
+
+ /** */
+ @Test
+ public void childNode() throws Exception {
+ List<String> log = new ArrayList<>();
+
+ configuration.listen(ctx -> {
+ assertEquals(ctx.oldValue().child().str(), "default");
+ assertEquals(ctx.newValue().child().str(), "foo");
+
+ log.add("parent");
+
+ return completedFuture(null);
+ });
+
+ configuration.child().listen(ctx -> {
+ assertEquals(ctx.oldValue().str(), "default");
+ assertEquals(ctx.newValue().str(), "foo");
+
+ log.add("child");
+
+ return completedFuture(null);
+ });
+
+ configuration.child().str().listen(ctx -> {
+ assertEquals(ctx.oldValue(), "default");
+ assertEquals(ctx.newValue(), "foo");
+
+ log.add("str");
+
+ return completedFuture(null);
+ });
+
+ configuration.elements().listen(ctx -> {
+ log.add("elements");
+
+ return completedFuture(null);
+ });
+
+ configuration.change(parent -> parent.changeChild(child -> child.changeStr("foo"))).get(1, SECONDS);
+
+ assertEquals(List.of("parent", "child", "str"), log);
+ }
+
+ /** */
+ @Test
+ public void namedListNode() throws Exception {
+ 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 -> {
+ if (ctx.oldValue().size() == 0) {
+ ChildView newValue = ctx.newValue().get("name");
+
+ assertNotNull(newValue);
+ assertEquals("default", newValue.str());
+ }
+ else if (ctx.newValue().size() == 0) {
+ ChildView oldValue = ctx.oldValue().get("name");
+
+ assertNotNull(oldValue);
+ assertEquals("foo", oldValue.str());
+ }
+ else {
+ 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");
+
+ 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);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<ChildView> ctx) {
+ ChildView oldValue = ctx.oldValue();
+
+ assertNotNull(oldValue);
+ assertEquals("default", oldValue.str());
+
+ ChildView newValue = ctx.newValue();
+
+ assertNotNull(newValue);
+ assertEquals("foo", newValue.str());
+
+ log.add("update");
+
+ return completedFuture(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<?> onDelete(ConfigurationNotificationEvent<ChildView> ctx) {
+ assertNull(ctx.newValue());
+
+ ChildView oldValue = ctx.oldValue();
+
+ assertNotNull(oldValue);
+ assertEquals("foo", oldValue.str());
+
+ 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);
+
+ log.clear();
+
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.update("name", element -> element.changeStr("foo")))
+ ).get(1, SECONDS);
+
+ assertEquals(List.of("parent", "elements", "update"), log);
+
+ log.clear();
+
+ configuration.change(parent ->
+ parent.changeElements(elements -> elements.delete("name"))
+ ).get(1, SECONDS);
+
+ assertEquals(List.of("parent", "elements", "delete"), log);
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
index 8f091fb..fe7c2d7 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
@@ -19,7 +19,7 @@ package org.apache.ignite.configuration.sample;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.sample.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
/**
* Test local configuration schema.
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NetworkConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NetworkConfigurationSchema.java
index bcb104d..d9a8101 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NetworkConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NetworkConfigurationSchema.java
@@ -19,7 +19,7 @@ package org.apache.ignite.configuration.sample;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.sample.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
/**
* Test network configuration schema.
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
index 4eb513c..41f14d9 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
@@ -18,7 +18,8 @@
package org.apache.ignite.configuration.sample;
import org.apache.ignite.configuration.ConfigurationRegistry;
-import org.apache.ignite.configuration.sample.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.storage.TestConfigurationStorage;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -31,13 +32,20 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* Simple usage test of generated configuration schema.
*/
public class UsageTest {
+ /** */
+ private final ConfigurationRegistry registry = new ConfigurationRegistry();
+
+ /** */
+ @AfterEach
+ public void after() {
+ registry.stop();
+ }
+
/**
* Test creation of configuration and calling configuration API methods.
*/
@Test
public void test() throws Exception {
- var registry = new ConfigurationRegistry();
-
registry.registerRootKey(LocalConfiguration.KEY);
registry.registerStorage(new TestConfigurationStorage());
@@ -92,8 +100,6 @@ public class UsageTest {
long autoAdjustTimeout = 30_000L;
- ConfigurationRegistry registry = new ConfigurationRegistry();
-
registry.registerRootKey(NetworkConfiguration.KEY);
registry.registerRootKey(LocalConfiguration.KEY);
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/storage/TestConfigurationStorage.java
similarity index 80%
rename from modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
rename to modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/storage/TestConfigurationStorage.java
index 5c2d405..f2c2982 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/storage/TestConfigurationStorage.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/storage/TestConfigurationStorage.java
@@ -14,21 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.configuration.sample.storage;
+package org.apache.ignite.configuration.storage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
-import org.apache.ignite.configuration.storage.ConfigurationStorage;
-import org.apache.ignite.configuration.storage.ConfigurationStorageListener;
-import org.apache.ignite.configuration.storage.Data;
-import org.apache.ignite.configuration.storage.StorageException;
/**
* Test configuration storage.
@@ -59,11 +54,11 @@ public class TestConfigurationStorage implements ConfigurationStorage {
if (fail)
throw new StorageException("Failed to read data");
- return new Data(new HashMap<>(map), version.get());
+ return new Data(new HashMap<>(map), version.get(), 0);
}
/** {@inheritDoc} */
- @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long sentVersion) throws StorageException {
+ @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long sentVersion) {
if (fail)
return CompletableFuture.failedFuture(new StorageException("Failed to write data"));
@@ -79,20 +74,12 @@ public class TestConfigurationStorage implements ConfigurationStorage {
version.incrementAndGet();
- listeners.forEach(listener -> listener.onEntriesChanged(new Data(newValues, version.get())));
+ listeners.forEach(listener -> listener.onEntriesChanged(new Data(newValues, version.get(), 0)));
return CompletableFuture.completedFuture(true);
}
/** {@inheritDoc} */
- @Override public synchronized Set<String> keys() throws StorageException {
- if (fail)
- throw new StorageException("Failed to get keys");
-
- return map.keySet();
- }
-
- /** {@inheritDoc} */
@Override public void addListener(ConfigurationStorageListener listener) {
listeners.add(listener);
}
@@ -101,4 +88,8 @@ public class TestConfigurationStorage implements ConfigurationStorage {
@Override public void removeListener(ConfigurationStorageListener listener) {
listeners.remove(listener);
}
+
+ /** {@inheritDoc} */
+ @Override public void notifyApplied(long storageRevision) {
+ }
}
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index 0da06b4..0b5484c 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -44,18 +44,5 @@
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
</dependency>
-
- <!-- Test dependencies. -->
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
</project>
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
index 0773fe1..8d92cc4 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
@@ -40,8 +40,11 @@ import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.configuration.validation.ValidationIssue;
import org.apache.ignite.configuration.validation.Validator;
+import org.jetbrains.annotations.NotNull;
+import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.addDefaults;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.cleanupMatchingValues;
import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.fillFromPrefixMap;
import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.nodeToFlatMap;
import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.patch;
@@ -50,7 +53,7 @@ import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.to
/**
* Class that handles configuration changes, by validating them, passing to storage and listening to storage updates.
*/
-public class ConfigurationChanger {
+public final class ConfigurationChanger {
/** */
private final ForkJoinPool pool = new ForkJoinPool(2);
@@ -64,6 +67,26 @@ public class ConfigurationChanger {
private Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = new HashMap<>();
/**
+ * Closure interface to be used by the configuration changer. An instance of this closure is passed into the constructor and
+ * invoked every time when there's an update from any of the storages.
+ */
+ @FunctionalInterface
+ public interface Notificator {
+ /**
+ * Invoked every time when the configuration is updated.
+ * @param oldRoot Old roots values. All these roots always belong to a single storage.
+ * @param newRoot New values for the same roots as in {@code oldRoot}.
+ * @param storageRevision Revision of the storage.
+ * @return Not-null future that must signify when processing is completed. Exceptional completion is not
+ * expected.
+ */
+ @NotNull CompletableFuture<Void> notify(SuperRoot oldRoot, SuperRoot newRoot, long storageRevision);
+ }
+
+ /** Closure to execute when an update from the storage is received. */
+ private final Notificator notificator;
+
+ /**
* Immutable data container to store version and all roots associated with the specific storage.
*/
private static class StorageRoots {
@@ -86,10 +109,11 @@ public class ConfigurationChanger {
/** Storage instances by their classes. Comes in handy when all you have is {@link RootKey}. */
private final Map<Class<? extends ConfigurationStorage>, ConfigurationStorage> storageInstances = new HashMap<>();
- /** Constructor. */
- public ConfigurationChanger(RootKey<?, ?>... rootKeys) {
- for (RootKey<?, ?> rootKey : rootKeys)
- this.rootKeys.put(rootKey.key(), rootKey);
+ /**
+ * @param notificator Closure to execute when update from the storage is received.
+ */
+ public ConfigurationChanger(Notificator notificator) {
+ this.notificator = notificator;
}
/** */
@@ -141,7 +165,7 @@ public class ConfigurationChanger {
superRoot.addRoot(rootKey, rootNode);
}
- StorageRoots storageRoots = new StorageRoots(superRoot, data.version());
+ StorageRoots storageRoots = new StorageRoots(superRoot, data.cfgVersion());
storagesRootsMap.put(configurationStorage.getClass(), storageRoots);
@@ -176,7 +200,7 @@ public class ConfigurationChanger {
throw new ConfigurationValidationException(validationIssues);
try {
- change(defaultsNode, storageType).get();
+ changeInternally(defaultsNode, storageInstances.get(storageType)).get();
}
catch (InterruptedException | ExecutionException e) {
throw new ConfigurationChangeException(
@@ -197,7 +221,12 @@ public class ConfigurationChanger {
source.descend(superRoot);
- return change(superRoot, storage.getClass());
+ return changeInternally(superRoot, storage);
+ }
+
+ /** Stop component. */
+ public void stop() {
+ pool.shutdownNow();
}
/**
@@ -215,7 +244,7 @@ public class ConfigurationChanger {
*/
public CompletableFuture<Void> change(Map<RootKey<?, ?>, InnerNode> changes) {
if (changes.isEmpty())
- return CompletableFuture.completedFuture(null);
+ return completedFuture(null);
Set<Class<? extends ConfigurationStorage>> storagesTypes = changes.keySet().stream()
.map(RootKey::getStorageType)
@@ -229,7 +258,7 @@ public class ConfigurationChanger {
);
}
- return change(new SuperRoot(rootKeys, changes), storagesTypes.iterator().next());
+ return changeInternally(new SuperRoot(rootKeys, changes), storageInstances.get(storagesTypes.iterator().next()));
}
/** */
@@ -242,80 +271,78 @@ public class ConfigurationChanger {
return mergedSuperRoot;
}
- /** */
- private CompletableFuture<Void> change(SuperRoot changes, Class<? extends ConfigurationStorage> storageType) {
- ConfigurationStorage storage = storageInstances.get(storageType);
-
- CompletableFuture<Void> fut = new CompletableFuture<>();
-
- pool.execute(() -> change0(changes, storage, fut));
-
- return fut;
- }
-
/**
* Internal configuration change method that completes provided future.
* @param changes Map of changes by root key.
* @param storage Storage instance.
- * @param fut Future, that must be completed after changes are written to the storage.
+ * @return fut Future that will be completed after changes are written to the storage.
*/
- private void change0(
+ private CompletableFuture<Void> changeInternally(
SuperRoot changes,
- ConfigurationStorage storage,
- CompletableFuture<?> fut
+ ConfigurationStorage storage
) {
StorageRoots storageRoots = storagesRootsMap.get(storage.getClass());
- SuperRoot curRoots = storageRoots.roots;
-
- Map<String, Serializable> allChanges = new HashMap<>();
-
- //TODO IGNITE-14180 single putAll + remove matching value, this way "allChanges" will be fair.
- // These are changes explicitly provided by the client.
- allChanges.putAll(nodeToFlatMap(curRoots, changes));
-
- // It is necessary to reinitialize default values every time.
- // Possible use case that explicitly requires it: creation of the same named list entry with slightly
- // different set of values and different dynamic defaults at the same time.
- SuperRoot patchedSuperRoot = patch(curRoots, changes);
- SuperRoot defaultsNode = new SuperRoot(rootKeys);
-
- addDefaults(patchedSuperRoot, defaultsNode);
-
- // These are default values for non-initialized values, required to complete the configuration.
- allChanges.putAll(nodeToFlatMap(patchedSuperRoot, defaultsNode));
-
- // Unlikely but still possible.
- if (allChanges.isEmpty()) {
- fut.complete(null);
-
- return;
- }
-
- List<ValidationIssue> validationIssues = ValidationUtil.validate(
- storageRoots.roots,
- patch(patchedSuperRoot, defaultsNode),
- this::getRootNode,
- cachedAnnotations,
- validators
- );
-
- if (!validationIssues.isEmpty()) {
- fut.completeExceptionally(new ConfigurationValidationException(validationIssues));
-
- return;
- }
-
- CompletableFuture<Boolean> writeFut = storage.write(allChanges, storageRoots.version);
-
- writeFut.whenCompleteAsync((casResult, throwable) -> {
- if (throwable != null)
- fut.completeExceptionally(new ConfigurationChangeException("Failed to change configuration", throwable));
- else if (casResult)
- fut.complete(null);
- else
- change0(changes, storage, fut);
- }, pool);
+ return CompletableFuture
+ .supplyAsync(() -> {
+ SuperRoot curRoots = storageRoots.roots;
+
+ // It is necessary to reinitialize default values every time.
+ // Possible use case that explicitly requires it: creation of the same named list entry with slightly
+ // different set of values and different dynamic defaults at the same time.
+ SuperRoot patchedSuperRoot = patch(curRoots, changes);
+
+ SuperRoot defaultsNode = new SuperRoot(rootKeys);
+
+ addDefaults(patchedSuperRoot, defaultsNode);
+
+ SuperRoot patchedChanges = patch(changes, defaultsNode);
+
+ cleanupMatchingValues(curRoots, changes);
+
+ Map<String, Serializable> allChanges = nodeToFlatMap(curRoots, patchedChanges);
+
+ // Unlikely but still possible.
+ if (allChanges.isEmpty())
+ return null;
+
+ List<ValidationIssue> validationIssues = ValidationUtil.validate(
+ storageRoots.roots,
+ patch(patchedSuperRoot, defaultsNode),
+ this::getRootNode,
+ cachedAnnotations,
+ validators
+ );
+
+ if (!validationIssues.isEmpty())
+ throw new ConfigurationValidationException(validationIssues);
+
+ return allChanges;
+ }, pool)
+ .thenCompose(allChanges -> {
+ if (allChanges == null)
+ return CompletableFuture.completedFuture(true);
+ return storage.write(allChanges, storageRoots.version)
+ .exceptionally(throwable -> {
+ throw new ConfigurationChangeException("Failed to change configuration", throwable);
+ });
+ })
+ .thenCompose(casResult -> {
+ if (casResult)
+ return CompletableFuture.completedFuture(null);
+ else {
+ try {
+ // Is this ok to have a busy wait on concurrent configuration updates?
+ // Maybe we'll fix it while implementing metastorage storage implementation.
+ Thread.sleep(10);
+ }
+ catch (InterruptedException e) {
+ return CompletableFuture.failedFuture(e);
+ }
+
+ return changeInternally(changes, storage);
+ }
+ });
}
/**
@@ -333,15 +360,25 @@ public class ConfigurationChanger {
compressDeletedEntries(dataValuesPrefixMap);
- SuperRoot newSuperRoot = oldStorageRoots.roots.copy();
+ SuperRoot oldSuperRoot = oldStorageRoots.roots;
+ SuperRoot newSuperRoot = oldSuperRoot.copy();
fillFromPrefixMap(newSuperRoot, dataValuesPrefixMap);
- StorageRoots storageRoots = new StorageRoots(newSuperRoot, changedEntries.version());
+ StorageRoots newStorageRoots = new StorageRoots(newSuperRoot, changedEntries.cfgVersion());
+
+ storagesRootsMap.put(storageType, newStorageRoots);
+
+ ConfigurationStorage storage = storageInstances.get(storageType);
- storagesRootsMap.put(storageType, storageRoots);
+ long storageRevision = changedEntries.storageRevision();
- //TODO IGNITE-14180 Notify listeners.
+ // This will also be updated during the metastorage integration.
+ notificator.notify(
+ oldSuperRoot,
+ newSuperRoot,
+ storageRevision
+ ).whenCompleteAsync((res, throwable) -> storage.notifyApplied(storageRevision), pool);
}
/**
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
index e61b720..f1a6729 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
@@ -17,12 +17,14 @@
package org.apache.ignite.configuration;
+import org.apache.ignite.configuration.notifications.ConfigurationListener;
+
/**
* Base interface for configuration.
- * @param <VALUE> Type of the value.
+ * @param <VIEW> Type of the value.
* @param <CHANGE> Type of the object that changes the value of configuration.
*/
-public interface ConfigurationProperty<VALUE, CHANGE> {
+public interface ConfigurationProperty<VIEW, CHANGE> {
/**
* Get key of this node.
* @return Key.
@@ -33,5 +35,11 @@ public interface ConfigurationProperty<VALUE, CHANGE> {
* Get value of this property.
* @return Value of this property.
*/
- VALUE value();
+ VIEW value();
+
+ /**
+ * Add configuration values listener.
+ * @param listener Listener.
+ */
+ void listen(ConfigurationListener<VIEW> listener);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
index b7931de..4087013 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
@@ -19,14 +19,17 @@ package org.apache.ignite.configuration;
import java.io.Serializable;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
+import java.util.function.Function;
import java.util.function.Supplier;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.configuration.internal.RootKeyImpl;
@@ -42,13 +45,19 @@ import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.tree.TraversableTreeNode;
import org.apache.ignite.configuration.validation.Validator;
+import static org.apache.ignite.configuration.internal.util.ConfigurationNotificationsUtil.notifyListeners;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.innerNodeVisitor;
+
/** */
public class ConfigurationRegistry {
/** */
+ private static final System.Logger logger = System.getLogger(ConfigurationRegistry.class.getName());
+
+ /** */
private final Map<String, DynamicConfiguration<?, ?, ?>> configs = new HashMap<>();
/** */
- private final ConfigurationChanger changer = new ConfigurationChanger();
+ private final ConfigurationChanger changer = new ConfigurationChanger(this::notificator);
{
// Default vaildators implemented in current module.
@@ -61,8 +70,6 @@ public class ConfigurationRegistry {
changer.addRootKey(rootKey);
configs.put(rootKey.key(), (DynamicConfiguration<?, ?, ?>)rootKey.createPublicRoot(changer));
-
- //TODO IGNITE-14180 link these two entities.
}
/** */
@@ -76,6 +83,11 @@ public class ConfigurationRegistry {
}
/** */
+ public void startStorageConfigurations(Class<? extends ConfigurationStorage> storageType) {
+ changer.initialize(storageType);
+ }
+
+ /** */
public <V, C, T extends ConfigurationTree<V, C>> T getConfiguration(RootKey<T, V> rootKey) {
return (T)configs.get(rootKey.key());
}
@@ -113,6 +125,43 @@ public class ConfigurationRegistry {
return changer.changeX(path, changesSource, storage);
}
+ /** */
+ public void stop() {
+ changer.stop();
+ }
+
+ /** */
+ private @NotNull CompletableFuture<Void> notificator(SuperRoot oldSuperRoot, SuperRoot newSuperRoot, long storageRevision) {
+ List<CompletableFuture<?>> futures = new ArrayList<>();
+
+ newSuperRoot.traverseChildren(new ConfigurationVisitor<Void>() {
+ @Override public Void visitInnerNode(String key, InnerNode newRoot) {
+ InnerNode oldRoot = oldSuperRoot.traverseChild(key, innerNodeVisitor());
+
+ var cfg = (DynamicConfiguration<InnerNode, ?, ?>)configs.get(key);
+
+ assert oldRoot != null && cfg != null : key;
+
+ if (oldRoot != newRoot)
+ notifyListeners(oldRoot, newRoot, cfg, storageRevision, futures);
+
+ return null;
+ }
+ });
+
+ // Map futures into a "suppressed" future that won't throw any exceptions on completion.
+ Function<CompletableFuture<?>, CompletableFuture<?>> mapping = fut -> fut.handle((res, throwable) -> {
+ if (throwable != null)
+ logger.log(System.Logger.Level.ERROR, "Failed to notify configuration listener.", throwable);
+
+ return res;
+ });
+
+ CompletableFuture[] resultFutures = futures.stream().map(mapping).toArray(CompletableFuture[]::new);
+
+ return CompletableFuture.allOf(resultFutures);
+ }
+
/**
* Method to instantiate a new {@link RootKey} for your configuration root. Invoked in generated code only.
* Does not register this root anywhere, used for static object initialization only.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
index 28e84de..bdab694 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
@@ -24,10 +24,10 @@ import org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
/**
* Configuration tree with configuration values and other configuration trees as child nodes.
- * @param <VALUE> Value type of the node.
+ * @param <VIEW> Value type of the node.
* @param <CHANGE> Type of the object that changes this node's value.
*/
-public interface ConfigurationTree<VALUE, CHANGE> extends ConfigurationProperty<VALUE, CHANGE> {
+public interface ConfigurationTree<VIEW, CHANGE> extends ConfigurationProperty<VIEW, CHANGE> {
/** Children of the tree. */
Map<String, ConfigurationProperty<?, ?>> members();
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
index 4739521..a9e0eec 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
@@ -22,9 +22,9 @@ import org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
/**
* Configuration value.
- * @param <VALUE> Type of the value.
+ * @param <VIEW> Type of the value.
*/
-public interface ConfigurationValue<VALUE> extends ConfigurationProperty<VALUE, VALUE> {
+public interface ConfigurationValue<VIEW> extends ConfigurationProperty<VIEW, VIEW> {
/**
* Update this configuration node value.
@@ -33,5 +33,5 @@ public interface ConfigurationValue<VALUE> extends ConfigurationProperty<VALUE,
* @returns Future that signifies end of the update operation. Can also be completed with
* {@link ConfigurationValidationException} and {@link ConfigurationChangeException}.
*/
- Future<Void> update(VALUE change) throws ConfigurationValidationException;
+ Future<Void> update(VIEW change) throws ConfigurationValidationException;
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/NamedConfigurationTree.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/NamedConfigurationTree.java
index e4f096c..eeac54b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/NamedConfigurationTree.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/NamedConfigurationTree.java
@@ -17,6 +17,7 @@
package org.apache.ignite.configuration;
+import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
import org.apache.ignite.configuration.tree.NamedListChange;
import org.apache.ignite.configuration.tree.NamedListView;
@@ -24,7 +25,7 @@ import org.apache.ignite.configuration.tree.NamedListView;
* Configuration tree representing arbitrary set of named underlying configuration tree of the same type.
*
* @param <T> Type of the underlying configuration tree.
- * @param <VALUE> Value type of the underlying node.
+ * @param <VIEW> Value type of the underlying node.
* @param <CHANGE> Type of the object that changes underlying nodes values.
*/
public interface NamedConfigurationTree<T extends ConfigurationProperty<VIEW, CHANGE>, VIEW, CHANGE, INIT>
@@ -36,4 +37,10 @@ public interface NamedConfigurationTree<T extends ConfigurationProperty<VIEW, CH
* @return Configuration.
*/
T get(String name);
+
+ /**
+ * Add named-list-specific configuration values listener.
+ * @param listener Listener.
+ */
+ void listen(ConfigurationNamedListListener<VIEW> listener);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
deleted file mode 100644
index 52f3522..0000000
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
+++ /dev/null
@@ -1,61 +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.configuration;
-
-import java.io.Serializable;
-
-/**
- * Configuration property change listener.
- *
- * @param <VIEW> VIEW type of property.
- * @param <CHANGE> CHANGE type of property.
- */
-public interface PropertyListener<VIEW extends Serializable, CHANGE extends Serializable> {
- /**
- * Called before property value is updated.
- *
- * @param oldValue Previous value.
- * @param newValue New value.
- * @param modifier Property itself.
- * @return {@code true} if changes are approved and {@code false} then property update must be aborted.
- */
- default boolean beforeUpdate(VIEW oldValue, VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
- return true;
- }
-
- /**
- * Called on property value update.
- *
- * @param newValue New value of the property.
- * @param modifier Property itself.
- */
- default void update(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
- /* No-op */
- }
-
- /**
- * Called after property value update.
- *
- * @param newValue New value of the property.
- * @param modifier Property itself.
- */
- default void afterUpdate(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
- /* No-op */
- }
-
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
index 67f875c..328ca10 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
@@ -17,19 +17,26 @@
package org.apache.ignite.configuration.internal;
+import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.ignite.configuration.ConfigurationChanger;
+import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
import org.apache.ignite.configuration.internal.util.KeyNotFoundException;
+import org.apache.ignite.configuration.notifications.ConfigurationListener;
import org.apache.ignite.configuration.tree.TraversableTreeNode;
/**
* Super class for dynamic configuration tree nodes. Has all common data and value retrieving algorithm in it.
*/
-public abstract class ConfigurationNode<VIEW> {
+abstract class ConfigurationNode<VIEW, CHANGE> implements ConfigurationProperty<VIEW, CHANGE> {
+ /** Listeners of property update. */
+ protected final List<ConfigurationListener<VIEW>> updateListeners = new CopyOnWriteArrayList<>();
+
/** Full path to the current node. */
protected final List<String> keys;
@@ -73,6 +80,16 @@ public abstract class ConfigurationNode<VIEW> {
assert Objects.equals(rootKey.key(), keys.get(0));
}
+ /** {@inheritDoc} */
+ @Override public void listen(ConfigurationListener<VIEW> listener) {
+ updateListeners.add(listener);
+ }
+
+ /** @return List of update listeners. */
+ public List<ConfigurationListener<VIEW>> listeners() {
+ return Collections.unmodifiableList(updateListeners);
+ }
+
/**
* Returns latest value of the configuration or throws exception.
*
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
index e2ca703..b2eb63f 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
@@ -37,10 +37,11 @@ import org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
/**
* This class represents configuration root or node.
*/
-public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends ConfigurationNode<VIEW> implements ConfigurationProperty<VIEW, CHANGE>, ConfigurationTree<VIEW, CHANGE> {
-
+public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends ConfigurationNode<VIEW, CHANGE>
+ implements ConfigurationTree<VIEW, CHANGE>
+{
/** Configuration members (leaves and nodes). */
- protected final Map<String, ConfigurationProperty<?, ?>> members = new HashMap<>();
+ private final Map<String, ConfigurationProperty<?, ?>> members = new HashMap<>();
/**
* Constructor.
@@ -118,7 +119,7 @@ public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends Configura
}
/** {@inheritDoc} */
- @Override public final Map<String, ConfigurationProperty<?, ?>> members() {
+ @Override public Map<String, ConfigurationProperty<?, ?>> members() {
return Collections.unmodifiableMap(members);
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
index d74913a..b163501 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
@@ -18,16 +18,13 @@
package org.apache.ignite.configuration.internal;
import java.io.Serializable;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.RandomAccess;
import java.util.concurrent.Future;
import org.apache.ignite.configuration.ConfigurationChanger;
-import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.ConfigurationValue;
-import org.apache.ignite.configuration.PropertyListener;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConstructableTreeNode;
@@ -37,10 +34,7 @@ import org.apache.ignite.configuration.validation.ConfigurationValidationExcepti
/**
* Holder for property value. Expected to be used with numbers, strings and other immutable objects, e.g. IP addresses.
*/
-public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T> implements ConfigurationProperty<T, T>, ConfigurationValue<T> {
- /** Listeners of property update. */
- private final List<PropertyListener<T, T>> updateListeners = new ArrayList<>();
-
+public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T, T> implements ConfigurationValue<T> {
/**
* Constructor.
* @param prefix Property prefix.
@@ -57,14 +51,6 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
super(prefix, key, rootKey, changer);
}
- /**
- * Add change listener to this property.
- * @param listener Property change listener.
- */
- public void addListener(PropertyListener<T, T> listener) {
- updateListeners.add(listener);
- }
-
/** {@inheritDoc} */
@Override public T value() {
return refreshValue();
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
index 9c9e53a..8a51b73 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
@@ -17,14 +17,17 @@
package org.apache.ignite.configuration.internal;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import org.apache.ignite.configuration.ConfigurationChanger;
import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.NamedConfigurationTree;
import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
import org.apache.ignite.configuration.tree.NamedListChange;
import org.apache.ignite.configuration.tree.NamedListInit;
import org.apache.ignite.configuration.tree.NamedListView;
@@ -32,10 +35,13 @@ import org.apache.ignite.configuration.tree.NamedListView;
/**
* Named configuration wrapper.
*/
-public class NamedListConfiguration<T extends ConfigurationProperty<VIEW, CHANGE>, VIEW, CHANGE, INIT>
+public final class NamedListConfiguration<T extends ConfigurationProperty<VIEW, CHANGE>, VIEW, CHANGE, INIT>
extends DynamicConfiguration<NamedListView<VIEW>, NamedListInit<INIT>, NamedListChange<CHANGE, INIT>>
implements NamedConfigurationTree<T, VIEW, CHANGE, INIT>
{
+ /** Listeners of property update. */
+ private final List<ConfigurationNamedListListener<VIEW>> extendedListeners = new CopyOnWriteArrayList<>();
+
/** Creator of named configuration. */
private final BiFunction<List<String>, String, T> creator;
@@ -84,4 +90,21 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW, CHANGE
this.values = newValues;
}
+
+ /** {@inheritDoc} */
+ @Override public Map<String, ConfigurationProperty<?, ?>> members() {
+ refreshValue();
+
+ return Collections.unmodifiableMap(values);
+ }
+
+ /** */
+ public List<ConfigurationNamedListListener<VIEW>> extendedListeners() {
+ return Collections.unmodifiableList(extendedListeners);
+ }
+
+ /** {@inheritDoc} */
+ @Override public final void listen(ConfigurationNamedListListener<VIEW> listener) {
+ extendedListeners.add(listener);
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/notifications/ConfigurationNotificationEventImpl.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/notifications/ConfigurationNotificationEventImpl.java
new file mode 100644
index 0000000..89a5776
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/notifications/ConfigurationNotificationEventImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.notifications;
+
+import org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
+import org.jetbrains.annotations.Nullable;
+
+public class ConfigurationNotificationEventImpl<VIEW> implements ConfigurationNotificationEvent<VIEW> {
+ private final VIEW oldValue;
+
+ private final VIEW newValue;
+
+ private final long storageRevision;
+
+ public ConfigurationNotificationEventImpl(VIEW oldValue, VIEW newValue, long storageRevision) {
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ this.storageRevision = storageRevision;
+ }
+
+ /** {@inheritDoc} */
+ @Override public @Nullable VIEW oldValue() {
+ return oldValue;
+ }
+
+ /** {@inheritDoc} */
+ @Override public @Nullable VIEW newValue() {
+ return newValue;
+ }
+
+ /** {@inheritDoc} */
+ @Override public long storageRevision() {
+ return storageRevision;
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationNotificationsUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationNotificationsUtil.java
new file mode 100644
index 0000000..ebf9772
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationNotificationsUtil.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.util;
+
+import java.io.Serializable;
+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 org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.DynamicProperty;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+import org.apache.ignite.configuration.internal.notifications.ConfigurationNotificationEventImpl;
+import org.apache.ignite.configuration.notifications.ConfigurationListener;
+import org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+import org.apache.ignite.configuration.tree.NamedListView;
+
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.innerNodeVisitor;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.leafNodeVisitor;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.namedListNodeVisitor;
+
+/** */
+public class ConfigurationNotificationsUtil {
+ /**
+ * Recursively notifies all public configuration listeners, accumulating resulting futures in {@code futures} list.
+ * @param oldInnerNode Old configuration values root.
+ * @param newInnerNode New configuration values root.
+ * @param cfgNode Public configuration tree node corresponding to the current inner nodes.
+ * @param storageRevision Storage revision.
+ * @param futures Write-only list of futures to accumulate results.
+ */
+ public static void notifyListeners(
+ InnerNode oldInnerNode,
+ InnerNode newInnerNode,
+ DynamicConfiguration<InnerNode, ?, ?> cfgNode,
+ long storageRevision,
+ List<CompletableFuture<?>> futures
+ ) {
+ if (oldInnerNode == null || oldInnerNode == newInnerNode)
+ return;
+
+ notifyPublicListeners(cfgNode.listeners(), oldInnerNode, newInnerNode, storageRevision, futures);
+
+ oldInnerNode.traverseChildren(new ConfigurationVisitor<Void>() {
+ /** {@inheritDoc} */
+ @Override public Void visitLeafNode(String key, Serializable oldLeaf) {
+ Serializable newLeaf = newInnerNode.traverseChild(key, leafNodeVisitor());
+
+ if (newLeaf != oldLeaf) {
+ var dynProperty = (DynamicProperty<Serializable>)cfgNode.members().get(key);
+
+ notifyPublicListeners(dynProperty.listeners(), oldLeaf, newLeaf, storageRevision, futures);
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Void visitInnerNode(String key, InnerNode oldNode) {
+ InnerNode newNode = newInnerNode.traverseChild(key, innerNodeVisitor());
+
+ var dynCfg = (DynamicConfiguration<InnerNode, ?, ?>)cfgNode.members().get(key);
+
+ notifyListeners(oldNode, newNode, dynCfg, storageRevision, futures);
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> oldNamedList) {
+ var newNamedList = (NamedListNode<InnerNode>)newInnerNode.traverseChild(key, namedListNodeVisitor());
+
+ if (newNamedList != oldNamedList) {
+ var namedListCfg = (NamedListConfiguration<?, InnerNode, ?, ?>)cfgNode.members().get(key);
+
+ notifyPublicListeners(namedListCfg.listeners(), (NamedListView<InnerNode>)oldNamedList, newNamedList, storageRevision, futures);
+
+ // This is optimization, we could use "NamedListConfiguration#get" directly, but we don't want to.
+
+ Set<String> oldNames = oldNamedList.namedListKeys();
+ Set<String> newNames = newNamedList.namedListKeys();
+
+ 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());
+
+ for (String name : created)
+ notifyPublicListeners(list, null, newNamedList.get(name), storageRevision, futures);
+ }
+
+ Set<String> deleted = new HashSet<>(oldNames);
+ deleted.removeAll(newNames);
+
+ if (!deleted.isEmpty()) {
+ List<ConfigurationListener<InnerNode>> list = namedListCfg.extendedListeners()
+ .stream()
+ .map(l -> (ConfigurationListener<InnerNode>)l::onDelete)
+ .collect(Collectors.toList());
+
+ for (String name : deleted)
+ notifyPublicListeners(list, oldNamedList.get(name), null, storageRevision, futures);
+ }
+
+ for (String name : newNames) {
+ if (!oldNames.contains(name))
+ continue;
+
+ notifyPublicListeners(namedListCfg.extendedListeners(), oldNamedList.get(name), newNamedList.get(name), storageRevision, futures);
+
+ var dynCfg = (DynamicConfiguration<InnerNode, ?, ?>)namedListCfgMembers.get(name);
+
+ notifyListeners(oldNamedList.get(name), newNamedList.get(name), dynCfg, storageRevision, futures);
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * 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 <V> Type of the node.
+ */
+ private static <V> void notifyPublicListeners(
+ List<? extends ConfigurationListener<V>> listeners,
+ V oldVal,
+ V newVal,
+ long storageRevision,
+ List<CompletableFuture<?>> futures
+ ) {
+ ConfigurationNotificationEvent<V> evt = new ConfigurationNotificationEventImpl<>(
+ oldVal,
+ newVal,
+ storageRevision
+ );
+
+ for (ConfigurationListener<V> listener : listeners) {
+ try {
+ CompletableFuture<?> future = listener.onUpdate(evt);
+
+ if (future != null && (future.isCompletedExceptionally() || future.isCancelled() || !future.isDone()))
+ futures.add(future);
+ }
+ catch (Throwable t) {
+ futures.add(CompletableFuture.failedFuture(t));
+ }
+ }
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
index e92659c..c1708b7 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.RandomAccess;
import java.util.stream.Collectors;
import org.apache.ignite.configuration.internal.SuperRoot;
@@ -508,6 +509,47 @@ public class ConfigurationUtil {
};
}
+ /**
+ * Nullifies leaves in {@code newNode} node that are equal to the corresponding leaf values in {@code curNode}.
+ * In this context we view {@code curNode} as a full configuration node with all the data, while {@code newNode}
+ * contains only updates that we plan to apply to the {@code curNode} in the future.
+ * @param curNode Node to look for definitive values.
+ * @param newNode Node to nullify matching values.
+ */
+ public static void cleanupMatchingValues(InnerNode curNode, InnerNode newNode) {
+ if (curNode == null || newNode == null)
+ return;
+
+ assert curNode.getClass() == newNode.getClass();
+
+ newNode.traverseChildren(new ConfigurationVisitor<Void>() {
+ /** {@inheritDoc} */
+ @Override public Void visitLeafNode(String key, Serializable newVal) {
+ if (Objects.deepEquals(curNode.traverseChild(key, leafNodeVisitor()), newVal))
+ newNode.construct(key, null);
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Void visitInnerNode(String key, InnerNode newInnerNode) {
+ cleanupMatchingValues(curNode.traverseChild(key, innerNodeVisitor()), newInnerNode);
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> newNamedList) {
+ NamedListNode<?> curNamedList = curNode.traverseChild(key, namedListNodeVisitor());
+
+ for (String name : newNamedList.namedListKeys())
+ cleanupMatchingValues(curNamedList.get(name), newNamedList.get(name));
+
+ return null;
+ }
+ });
+ }
+
/** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
private static class PatchLeafConfigurationSource implements ConfigurationSource {
/** */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
similarity index 60%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
index 6914df8..252a462 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationListener.java
@@ -15,22 +15,24 @@
* limitations under the License.
*/
-package org.apache.ignite.configuration.tree;
+package org.apache.ignite.configuration.notifications;
-import java.util.Set;
-
-/** */
-public interface NamedListView<T> {
- /**
- * @return Immutable collection of keys contained within this list.
- */
- Set<String> namedListKeys();
+import java.util.concurrent.CompletableFuture;
+import org.jetbrains.annotations.NotNull;
+/**
+ * Configuration property change listener.
+ *
+ * @param <VIEW> VIEW type configuration.
+ */
+@FunctionalInterface
+public interface ConfigurationListener<VIEW> {
/**
- * Returns value associated with the passed key.
+ * Called on property value update.
*
- * @param key Key string.
- * @return Requested value or {@code null} if it's not found.
+ * @param ctx Notification context.
+ * @return Future that signifies end of listener execution.
*/
- T get(String key);
+ @NotNull CompletableFuture<?> onUpdate(ConfigurationNotificationEvent<VIEW> ctx);
}
+
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java
new file mode 100644
index 0000000..028a8d8
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNamedListListener.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.notifications;
+
+import java.util.concurrent.CompletableFuture;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Configuration property change listener for named list configurations.
+ *
+ * @param <VIEW> VIEW type configuration.
+ */
+public interface ConfigurationNamedListListener<VIEW> extends ConfigurationListener<VIEW> {
+ /**
+ * Called when new named list element is created.
+ *
+ * @param ctx Notification context.
+ * @return Future that signifies end of listener execution.
+ */
+ @NotNull CompletableFuture<?> onCreate(ConfigurationNotificationEvent<VIEW> ctx);
+
+ /**
+ * Called when named list element is deleted.
+ *
+ * @param ctx Notification context.
+ * @return Future that signifies end of listener execution.
+ */
+ @NotNull CompletableFuture<?> onDelete(ConfigurationNotificationEvent<VIEW> ctx);
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNotificationEvent.java
similarity index 50%
copy from modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
copy to modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNotificationEvent.java
index 8c41d20..393ea83 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/notifications/ConfigurationNotificationEvent.java
@@ -14,44 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.configuration.storage;
-import java.io.Serializable;
-import java.util.Map;
+package org.apache.ignite.configuration.notifications;
+
+import org.apache.ignite.configuration.ConfigurationProperty;
+import org.jetbrains.annotations.Nullable;
/**
- * Represents data in configuration storage.
+ * Event object propogated on configuration change. Passed to listeners after configuration changes are applied.
+ *
+ * @see ConfigurationProperty#listen(ConfigurationListener)
+ * @see ConfigurationListener
+ * @see ConfigurationNotificationEvent
*/
-public class Data {
- /** Values. */
- private final Map<String, Serializable> values;
-
- /** Configuration storage version. */
- private final long version;
-
+public interface ConfigurationNotificationEvent<VIEW> {
/**
- * Constructor.
- * @param values Values.
- * @param version Version.
+ * Previous value of the updated configuration.
*/
- public Data(Map<String, Serializable> values, long version) {
- this.values = values;
- this.version = version;
- }
+ @Nullable VIEW oldValue();
/**
- * Get values.
- * @return Values.
+ * Updated value of the configuration.
*/
- public Map<String, Serializable> values() {
- return values;
- }
+ @Nullable VIEW newValue();
/**
- * Get version.
- * @return version.
+ * Monotonously increasing counter, linked to the specific storage for current configuration values. Gives you a
+ * unique change identifier inside a specific configuration storage.
*/
- public long version() {
- return version;
- }
+ long storageRevision();
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
index b0cc047..edd0edf 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
@@ -18,7 +18,6 @@ package org.apache.ignite.configuration.storage;
import java.io.Serializable;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
@@ -42,14 +41,7 @@ public interface ConfigurationStorage {
CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version);
/**
- * Get all the keys of the configuration storage.
- * @return Set of keys.
- * @throws StorageException If failed to retrieve keys.
- */
- Set<String> keys() throws StorageException;
-
- /**
- * Add listener to the storage that notifies of data changes..
+ * Add listener to the storage that notifies of data changes.
* @param listener Listener.
*/
void addListener(ConfigurationStorageListener listener);
@@ -59,4 +51,10 @@ public interface ConfigurationStorage {
* @param listener Listener.
*/
void removeListener(ConfigurationStorageListener listener);
+
+ /**
+ * Notify storage that this specific revision was successfully handled and it is not necessary to repeat the same
+ * notification on node restart.
+ */
+ void notifyApplied(long storageRevision);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
index f8cb7b4..ac0f5b7 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorageListener.java
@@ -19,6 +19,7 @@ package org.apache.ignite.configuration.storage;
/**
* Configuration storage listener for changes.
*/
+@FunctionalInterface
public interface ConfigurationStorageListener {
/**
* Method called when entries in storage change.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
index 8c41d20..0178520 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/Data.java
@@ -27,16 +27,21 @@ public class Data {
private final Map<String, Serializable> values;
/** Configuration storage version. */
- private final long version;
+ private final long cfgVersion;
+
+ /** */
+ private final long storageRevision;
/**
* Constructor.
* @param values Values.
- * @param version Version.
+ * @param cfgVersion Version.
+ * @param storageRevision Storage revision.
*/
- public Data(Map<String, Serializable> values, long version) {
+ public Data(Map<String, Serializable> values, long cfgVersion, long storageRevision) {
this.values = values;
- this.version = version;
+ this.cfgVersion = cfgVersion;
+ this.storageRevision = storageRevision;
}
/**
@@ -51,7 +56,12 @@ public class Data {
* Get version.
* @return version.
*/
- public long version() {
- return version;
+ public long cfgVersion() {
+ return cfgVersion;
+ }
+
+ /** */
+ public long storageRevision() {
+ return storageRevision;
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
index 718e0b0..c9d3836 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListNode.java
@@ -69,6 +69,11 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
}
/** {@inheritDoc} */
+ @Override public int size() {
+ return map.size();
+ }
+
+ /** {@inheritDoc} */
@Override public final NamedListChange<N, N> update(String key, Consumer<N> valConsumer) {
Objects.requireNonNull(valConsumer, "valConsumer");
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java
index 6914df8..f6e8ffc 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/NamedListView.java
@@ -33,4 +33,9 @@ public interface NamedListView<T> {
* @return Requested value or {@code null} if it's not found.
*/
T get(String key);
+
+ /**
+ * @return Number of elements.
+ */
+ int size();
}
diff --git a/modules/network/pom.xml b/modules/network/pom.xml
index a5bbf95..314a169 100644
--- a/modules/network/pom.xml
+++ b/modules/network/pom.xml
@@ -50,18 +50,6 @@
<!-- Test dependencies. -->
<dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/InMemoryConfigurationStorage.java b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/InMemoryConfigurationStorage.java
index a74ff59..00e06c5 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/InMemoryConfigurationStorage.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/InMemoryConfigurationStorage.java
@@ -17,13 +17,12 @@
package org.apache.ignite.rest.configuration;
import java.io.Serializable;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.configuration.storage.ConfigurationStorageListener;
@@ -38,19 +37,19 @@ public class InMemoryConfigurationStorage implements ConfigurationStorage {
private Map<String, Serializable> map = new ConcurrentHashMap<>();
/** Change listeners. */
- private List<ConfigurationStorageListener> listeners = new ArrayList<>();
+ private List<ConfigurationStorageListener> listeners = new CopyOnWriteArrayList<>();
/** Storage version. */
private AtomicLong version = new AtomicLong(0);
/** {@inheritDoc} */
@Override public synchronized Data readAll() throws StorageException {
- return new Data(new HashMap<>(map), version.get());
+ return new Data(new HashMap<>(map), version.get(), 0);
}
/** {@inheritDoc} */
- @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long sentVersion) throws StorageException {
- if (sentVersion != version.get())
+ @Override public synchronized CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version) {
+ if (version != this.version.get())
return CompletableFuture.completedFuture(false);
for (Map.Entry<String, Serializable> entry : newValues.entrySet()) {
@@ -60,19 +59,14 @@ public class InMemoryConfigurationStorage implements ConfigurationStorage {
map.remove(entry.getKey());
}
- version.incrementAndGet();
+ this.version.incrementAndGet();
- listeners.forEach(listener -> listener.onEntriesChanged(new Data(newValues, version.get())));
+ listeners.forEach(listener -> listener.onEntriesChanged(new Data(newValues, this.version.get(), 0)));
return CompletableFuture.completedFuture(true);
}
/** {@inheritDoc} */
- @Override public synchronized Set<String> keys() throws StorageException {
- return map.keySet();
- }
-
- /** {@inheritDoc} */
@Override public void addListener(ConfigurationStorageListener listener) {
listeners.add(listener);
}
@@ -81,4 +75,9 @@ public class InMemoryConfigurationStorage implements ConfigurationStorage {
@Override public void removeListener(ConfigurationStorageListener listener) {
listeners.remove(listener);
}
+
+ /** {@inheritDoc} */
+ @Override public void notifyApplied(long storageRevision) {
+ // No-op.
+ }
}
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java
index f855ec8..8326a07 100644
--- a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java
@@ -24,6 +24,7 @@ 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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -113,6 +114,12 @@ public class JsonConverterTest {
}
/** */
+ @AfterEach
+ public void after() {
+ registry.stop();
+ }
+
+ /** */
@Test
public void toJson() throws Exception {
assertEquals(
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java
index e175d54..3a6834e 100644
--- a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java
@@ -29,29 +29,24 @@ import org.apache.ignite.configuration.storage.Data;
import org.apache.ignite.configuration.storage.StorageException;
/** */
-public class TestConfigurationStorage implements ConfigurationStorage {
+class TestConfigurationStorage implements ConfigurationStorage {
/** */
private final Set<ConfigurationStorageListener> listeners = new HashSet<>();
/** {@inheritDoc} */
@Override public Data readAll() throws StorageException {
- return new Data(Collections.emptyMap(), 0);
+ return new Data(Collections.emptyMap(), 0, 0);
}
/** {@inheritDoc} */
@Override public CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version) {
for (ConfigurationStorageListener listener : listeners)
- listener.onEntriesChanged(new Data(newValues, version + 1));
+ listener.onEntriesChanged(new Data(newValues, version + 1, 0));
return CompletableFuture.completedFuture(true);
}
/** {@inheritDoc} */
- @Override public Set<String> keys() throws StorageException {
- return null;
- }
-
- /** {@inheritDoc} */
@Override public void addListener(ConfigurationStorageListener listener) {
listeners.add(listener);
}
@@ -60,4 +55,7 @@ public class TestConfigurationStorage implements ConfigurationStorage {
@Override public void removeListener(ConfigurationStorageListener listener) {
listeners.remove(listener);
}
+
+ @Override public void notifyApplied(long storageRevision) {
+ }
}
diff --git a/parent/pom.xml b/parent/pom.xml
index 2616325..2f32870 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -63,7 +63,6 @@
<jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
<jmh.framework.version>1.13</jmh.framework.version>
<junit.jupiter.version>5.7.0</junit.jupiter.version>
- <log4j.version>1.2.17</log4j.version>
<logback.version>1.2.3</logback.version>
<micronaut.version>2.1.2</micronaut.version>
<micronaut.test.junit5.version>2.3.1</micronaut.test.junit5.version>
@@ -230,12 +229,6 @@
</dependency>
<dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>${log4j.version}</version>
- </dependency>
-
- <dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<version>${compile.testing.library.version}</version>