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 <bessonov...@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: