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