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 2e6ea88 IGNITE-14371 JSON representation for configuration & partial code for JSON update requests parsing. (#72) 2e6ea88 is described below commit 2e6ea8850509a59a686abac44f0be2c5918cd3f8 Author: ibessonov <bessonov...@gmail.com> AuthorDate: Wed Mar 24 11:51:14 2021 +0300 IGNITE-14371 JSON representation for configuration & partial code for JSON update requests parsing. (#72) --- .../ignite/configuration/ConfigurationChanger.java | 10 + .../configuration/ConfigurationRegistry.java | 42 ++++ .../apache/ignite/configuration/Configurator.java | 28 --- .../configuration/annotation/ConfigValue.java | 4 - .../ignite/configuration/internal/SuperRoot.java | 6 + .../internal/util/ConfigurationUtil.java | 17 +- .../configuration/tree/ConfigurationVisitor.java | 2 +- .../ignite/configuration/tree/NamedListNode.java | 12 +- modules/rest/pom.xml | 1 + .../java/org/apache/ignite/rest/RestModule.java | 31 +-- .../configuration/RestConfigurationSchema.java | 15 +- .../rest/presentation/json/JsonConverter.java | 236 ++++++++++++++++++++- .../rest/presentation/json/JsonPresentation.java | 14 +- .../rest/presentation/json/JsonConverterTest.java | 211 ++++++++++++++++++ .../json/TestConfigurationStorage.java | 63 ++++++ .../ignite/configuration/ConfigurationModule.java | 2 +- 16 files changed, 624 insertions(+), 70 deletions(-) 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 67196ee..7990151 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 @@ -217,6 +217,16 @@ public class ConfigurationChanger { } /** */ + public SuperRoot mergedSuperRoot() { + SuperRoot mergedSuperRoot = new SuperRoot(rootKeys); + + for (StorageRoots storageRoots : storagesRootsMap.values()) + mergedSuperRoot.append(storageRoots.roots); + + return mergedSuperRoot; + } + + /** */ private CompletableFuture<Void> change(SuperRoot changes, Class<? extends ConfigurationStorage> storageType) { ConfigurationStorage storage = storageInstances.get(storageType); 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 8d4bcc5..5770249 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 @@ -17,9 +17,12 @@ package org.apache.ignite.configuration; +import java.io.Serializable; import java.lang.annotation.Annotation; 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.Supplier; import javax.validation.constraints.Max; @@ -27,10 +30,16 @@ import javax.validation.constraints.Min; import org.apache.ignite.configuration.annotation.ConfigurationRoot; import org.apache.ignite.configuration.internal.DynamicConfiguration; import org.apache.ignite.configuration.internal.RootKeyImpl; +import org.apache.ignite.configuration.internal.SuperRoot; +import org.apache.ignite.configuration.internal.util.ConfigurationUtil; +import org.apache.ignite.configuration.internal.util.KeyNotFoundException; import org.apache.ignite.configuration.internal.validation.MaxValidator; import org.apache.ignite.configuration.internal.validation.MinValidator; import org.apache.ignite.configuration.storage.ConfigurationStorage; +import org.apache.ignite.configuration.tree.ConfigurationSource; +import org.apache.ignite.configuration.tree.ConfigurationVisitor; import org.apache.ignite.configuration.tree.InnerNode; +import org.apache.ignite.configuration.tree.TraversableTreeNode; import org.apache.ignite.configuration.validation.Validator; /** */ @@ -72,6 +81,39 @@ public class ConfigurationRegistry { } /** + * Convert configuration subtree into a user-defined representation. + * + * @param path Path to configuration subtree. Can be empty, can't be {@code null}. + * @param visitor Visitor that will be applied to the subtree and build the representation. + * @param <T> Type of the representation. + * @return User-defined representation constructed by {@code visitor}. + * @throws IllegalArgumentException If {@code path} is not found in current configuration. + */ + public <T> T represent(List<String> path, ConfigurationVisitor<T> visitor) throws IllegalArgumentException { + SuperRoot mergedSuperRoot = changer.mergedSuperRoot(); + + Object node; + try { + node = ConfigurationUtil.find(path, mergedSuperRoot); + } + catch (KeyNotFoundException e) { + throw new IllegalArgumentException(e.getMessage()); + } + + if (node instanceof TraversableTreeNode) + return ((TraversableTreeNode)node).accept(null, visitor); + + assert node == null || node instanceof Serializable; + + return visitor.visitLeafNode(null, (Serializable)node); + } + + /** */ + public CompletableFuture<?> change(List<String> path, ConfigurationSource changesSource) { + throw new UnsupportedOperationException("IGNITE-14372 Not implemented yet."); + } + + /** * 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/Configurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java deleted file mode 100644 index 05c1ebe..0000000 --- a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java +++ /dev/null @@ -1,28 +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 org.apache.ignite.configuration.internal.DynamicConfiguration; - -/** - * Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions - * on configuration such as initialized, change and view. - * @param <T> Type of configuration root. - */ -public class Configurator<T extends DynamicConfiguration<?, ?, ?>> { -} diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java index b593674..e4fbc42 100644 --- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java +++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java @@ -40,8 +40,4 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; @Retention(SOURCE) @Documented public @interface ConfigValue { - /** - * @return The name of the configuration. - */ - String value() default ""; } diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java index a83852b..326c3cd 100644 --- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java +++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java @@ -63,6 +63,12 @@ public final class SuperRoot extends InnerNode { } /** */ + public void append(SuperRoot otherRoot) { + //TODO IGNITE-14372 Revisit API of the super root. + roots.putAll(otherRoot.roots); + } + + /** */ public InnerNode getRoot(RootKey<?, ?> rootKey) { return roots.get(rootKey.key()); } 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 53fd2f2..e92659c 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 @@ -255,8 +255,16 @@ public class ConfigurationUtil { assert val == null || val instanceof Map || val instanceof Serializable; - if (val == null) - node.construct(key, null); + if (val == null) { + if (node instanceof NamedListNode) { + // Given that this particular method is applied to modify existing trees rather than + // creating new trees, a "hack" is required in this place. "construct" is designed to create + // "change" objects, thus it would just nullify named list element instead of deleting it. + ((NamedListNode<?>)node).forceDelete(key); + } + else + node.construct(key, null); + } else if (val instanceof Map) node.construct(key, new InnerConfigurationSource((Map<String, ?>)val)); else { @@ -595,7 +603,10 @@ public class ConfigurationUtil { for (String key : srcNode.namedListKeys()) { InnerNode node = srcNode.get(key); - dstNode.construct(key, node == null ? null : new PatchInnerConfigurationSource(node)); + if (node == null) + ((NamedListNode<?>)dstNode).forceDelete(key); // Same as in fillFromPrefixMap. + else + dstNode.construct(key, new PatchInnerConfigurationSource(node)); } } } diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java index bf2e697..646c649 100644 --- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java +++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConfigurationVisitor.java @@ -39,7 +39,7 @@ public interface ConfigurationVisitor<T> { * @param key Name of the node retrieved from its holder object. * @param node Inner configuration node. */ - default T visitInnerNode(String key, InnerNode node) { + default T visitInnerNode(String key, InnerNode node) { //TODO IGNITE-14372 Pass interface, not implementation. return null; } 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 530544c..718e0b0 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 @@ -95,6 +95,16 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N return this; } + /** + * Deletes named list element. + * + * @param key Element's key. + */ + public void forceDelete(String key) { + map.remove(key); + } + + /** {@inheritDoc} */ @Override public NamedListChange<N, N> create(String key, Consumer<N> valConsumer) { Objects.requireNonNull(valConsumer, "valConsumer"); @@ -111,7 +121,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N /** {@inheritDoc} */ @Override public void construct(String key, ConfigurationSource src) { if (src == null) - map.remove(key); + map.put(key, null); else { N val = map.get(key); diff --git a/modules/rest/pom.xml b/modules/rest/pom.xml index 8cf8376..3af59e5 100644 --- a/modules/rest/pom.xml +++ b/modules/rest/pom.xml @@ -83,6 +83,7 @@ <artifactId>netty-codec-http</artifactId> </dependency> + <!-- Test dependencies. --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java index 6124abd..a434eaa 100644 --- a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java +++ b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java @@ -17,6 +17,7 @@ package org.apache.ignite.rest; +import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -29,13 +30,16 @@ import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.io.Reader; import java.nio.charset.StandardCharsets; -import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.ignite.configuration.ConfigurationRegistry; +import org.apache.ignite.configuration.internal.util.ConfigurationUtil; import org.apache.ignite.configuration.validation.ConfigurationValidationException; import org.apache.ignite.rest.configuration.RestConfiguration; +import org.apache.ignite.rest.configuration.RestView; import org.apache.ignite.rest.netty.RestApiInitializer; import org.apache.ignite.rest.presentation.ConfigurationPresentation; +import org.apache.ignite.rest.presentation.json.JsonConverter; import org.apache.ignite.rest.presentation.json.JsonPresentation; import org.apache.ignite.rest.routes.Router; import org.slf4j.Logger; @@ -50,7 +54,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; */ public class RestModule { /** */ - private static final int DFLT_PORT = 10300; + public static final int DFLT_PORT = 10300; /** */ private static final String CONF_URL = "/management/v1/configuration/"; @@ -76,7 +80,7 @@ public class RestModule { public void prepareStart(ConfigurationRegistry sysCfg, Reader moduleConfReader) { sysConf = sysCfg; - presentation = new JsonPresentation(Collections.emptyMap()); + presentation = new JsonPresentation(); // FormatConverter converter = new JsonConverter(); // @@ -98,7 +102,11 @@ public class RestModule { .get(CONF_URL + ":" + PATH_PARAM, (req, resp) -> { String cfgPath = req.queryParams().get(PATH_PARAM); try { - resp.json(presentation.representByPath(cfgPath)); + List<String> path = ConfigurationUtil.split(cfgPath); + + JsonElement json = sysConf.represent(path, JsonConverter.jsonVisitor()); + + resp.json(json); } catch (IllegalArgumentException pathE) { ErrorResult eRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage()); @@ -149,14 +157,16 @@ public class RestModule { /** */ private void startRestEndpoint(Router router) throws InterruptedException { - Integer desiredPort = sysConf.getConfiguration(RestConfiguration.KEY).port().value(); - Integer portRange = sysConf.getConfiguration(RestConfiguration.KEY).portRange().value(); + RestView restConfigurationView = sysConf.getConfiguration(RestConfiguration.KEY).value(); + + int desiredPort = restConfigurationView.port(); + int portRange = restConfigurationView.portRange(); int port = 0; - if (portRange == null || portRange == 0) { + if (portRange == 0) { try { - port = (desiredPort != null ? desiredPort : DFLT_PORT); + port = desiredPort; } catch (RuntimeException e) { log.warn("Failed to start REST endpoint: ", e); @@ -209,9 +219,4 @@ public class RestModule { workerGrp.shutdownGracefully(); } } - - /** */ - public String configRootKey() { - return "rest"; - } } diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java index 5cd7bc1..0003eae 100644 --- a/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java +++ b/modules/rest/src/main/java/org/apache/ignite/rest/configuration/RestConfigurationSchema.java @@ -17,19 +17,26 @@ package org.apache.ignite.rest.configuration; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; import org.apache.ignite.configuration.annotation.ConfigurationRoot; import org.apache.ignite.configuration.annotation.Value; +import static org.apache.ignite.rest.RestModule.DFLT_PORT; + /** * Configuration schema for REST endpoint subtree. */ @ConfigurationRoot(rootName = "rest") public class RestConfigurationSchema { /** */ - @Value - public int port; + @Min(1024) + @Max(0xFFFF) + @Value(hasDefault = true) + public final int port = DFLT_PORT; /** */ - @Value - public int portRange; + @Min(0) + @Value(hasDefault = true) + public final int portRange = 0; } diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java index 555e406..abe070f 100644 --- a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java +++ b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonConverter.java @@ -17,13 +17,30 @@ package org.apache.ignite.rest.presentation.json; -import java.io.Reader; - import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.io.Reader; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; +import org.apache.ignite.configuration.tree.ConfigurationSource; +import org.apache.ignite.configuration.tree.ConfigurationVisitor; +import org.apache.ignite.configuration.tree.ConstructableTreeNode; +import org.apache.ignite.configuration.tree.InnerNode; +import org.apache.ignite.configuration.tree.NamedListNode; import org.apache.ignite.rest.presentation.FormatConverter; +import org.jetbrains.annotations.NotNull; /** */ public class JsonConverter implements FormatConverter { @@ -35,6 +52,221 @@ public class JsonConverter implements FormatConverter { return gson.toJson(obj); } + /** */ + public static ConfigurationVisitor<JsonElement> jsonVisitor() { + return new ConfigurationVisitor<JsonElement>() { + /** */ + private final Deque<JsonObject> jsonObjectsStack = new ArrayDeque<>(); + + /** {@inheritDoc} */ + @Override public JsonElement visitLeafNode(String key, Serializable val) { + JsonElement jsonLeaf = toJsonLeaf(val); + + addToParent(key, jsonLeaf); + + return jsonLeaf; + } + + /** {@inheritDoc} */ + @Override public JsonElement visitInnerNode(String key, InnerNode node) { + JsonObject innerJsonNode = new JsonObject(); + + jsonObjectsStack.push(innerJsonNode); + + node.traverseChildren(this); + + jsonObjectsStack.pop(); + + addToParent(key, innerJsonNode); + + return innerJsonNode; + } + + /** {@inheritDoc} */ + @Override public <N extends InnerNode> JsonElement visitNamedListNode(String key, NamedListNode<N> node) { + JsonObject namedListJsonNode = new JsonObject(); + + jsonObjectsStack.push(namedListJsonNode); + + for (String subkey : node.namedListKeys()) + node.get(subkey).accept(subkey, this); + + jsonObjectsStack.pop(); + + addToParent(key, namedListJsonNode); + + return namedListJsonNode; + } + + /** */ + @NotNull private JsonElement toJsonLeaf(Serializable val) { + if (val == null) + return JsonNull.INSTANCE; + + Class<? extends Serializable> valClass = val.getClass(); + + if (!valClass.isArray()) + return toJsonPrimitive(val); + + JsonArray jsonArray = new JsonArray(); + + for (int i = 0; i < Array.getLength(val); i++) + jsonArray.add(toJsonPrimitive(Array.get(val, i))); + + return jsonArray; + } + + /** */ + @NotNull private JsonElement toJsonPrimitive(Object val) { + if (val == null) + return JsonNull.INSTANCE; + + if (val instanceof Boolean) + return new JsonPrimitive((Boolean)val); + + if (val instanceof String) + return new JsonPrimitive((String)val); + + if (val instanceof Number) + return new JsonPrimitive((Number)val); + + assert false : val; + + throw new IllegalArgumentException(val.getClass().getCanonicalName()); + } + + /** + * Add subelement to the paretn JSON object if it exists. + * + * @param key Key for the passed JSON element. + * @param jsonElement JSON element to add to the parent. + */ + private void addToParent(String key, JsonElement jsonElement) { + if (!jsonObjectsStack.isEmpty()) + jsonObjectsStack.peek().add(key, jsonElement); + } + }; + } + + /** */ + public static ConfigurationSource jsonSource(JsonElement jsonElement) { + //TODO IGNITE-14372 Finish this implementation. + return null; + } + + private static class JsonObjectConfigurationSource implements ConfigurationSource { + /** Shared. */ + private final List<String> path; + + /** */ + private final JsonObject jsonObject; + + private JsonObjectConfigurationSource(List<String> path, JsonObject jsonObject) { + this.path = path; + this.jsonObject = jsonObject; + } + + @Override public <T> T unwrap(Class<T> clazz) { + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Implement. + } + + @Override public void descend(ConstructableTreeNode node) { + for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) { + String key = entry.getKey(); + + path.add(key); + + JsonElement jsonElement = entry.getValue(); + + try { + if (jsonElement.isJsonArray() || jsonElement.isJsonPrimitive()) + node.construct(key, new JsonPrimitiveConfigurationSource(path, jsonElement)); + else if (jsonElement.isJsonNull()) { + node.construct(key, null); + } + else { + assert jsonElement.isJsonObject(); + + List<String> path = new ArrayList<>(this.path.size() + 1); + path.addAll(this.path); + path.add(key); + + node.construct(key, new JsonObjectConfigurationSource(path, jsonElement.getAsJsonObject())); + } + } + catch (NoSuchElementException e) { + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + } + + path.remove(path.size() - 1); + } + } + } + + private static class JsonPrimitiveConfigurationSource implements ConfigurationSource { + private final List<String> path; + + private final JsonElement jsonLeaf; + + private JsonPrimitiveConfigurationSource(List<String> path, JsonElement jsonLeaf) { + this.path = path; + this.jsonLeaf = jsonLeaf; + } + + @Override public <T> T unwrap(Class<T> clazz) { + if (clazz.isArray() != jsonLeaf.isJsonArray()) + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + + return null; + } + + @Override public void descend(ConstructableTreeNode node) { + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + } + + private <T> T unwrap(JsonPrimitive jsonPrimitive, Class<T> clazz) { + assert !clazz.isArray(); + + if (clazz == String.class) { + if (!jsonPrimitive.isString()) + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + + return clazz.cast(jsonPrimitive.getAsString()); + } + + if (Number.class.isAssignableFrom(clazz)) { + if (!jsonPrimitive.isNumber()) + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + + if (clazz == Double.class) + return clazz.cast(jsonPrimitive.getAsDouble()); + + if (clazz == Long.class) + return clazz.cast(jsonPrimitive.getAsLong()); + + if (clazz == Integer.class) { + long longValue = jsonPrimitive.getAsLong(); + + if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE) + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + + return clazz.cast((int)longValue); + } + + throw new AssertionError(clazz); + } + + if (clazz == Boolean.class) { + if (!jsonPrimitive.isBoolean()) + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + + return clazz.cast(jsonPrimitive.getAsBoolean()); + } + + throw new IllegalArgumentException(""); //TODO IGNITE-14372 Update comment. + } + } + /** {@inheritDoc} */ @Override public String convertTo(String rootName, Object src) { Map<String, Object> res = new HashMap<>(); diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java index 46de08b..db5020d 100644 --- a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java +++ b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/json/JsonPresentation.java @@ -19,8 +19,6 @@ package org.apache.ignite.rest.presentation.json; import java.util.Collections; import java.util.Map; -import org.apache.ignite.configuration.Configurator; -import org.apache.ignite.configuration.internal.DynamicConfiguration; import org.apache.ignite.rest.presentation.ConfigurationPresentation; /** */ @@ -29,11 +27,7 @@ public class JsonPresentation implements ConfigurationPresentation<String> { private final JsonConverter converter = new JsonConverter(); /** */ - private final Map<String, Configurator<? extends DynamicConfiguration<?, ?, ?>>> configsMap; - - /** */ - public JsonPresentation(Map<String, Configurator<? extends DynamicConfiguration<?, ?, ?>>> configsMap) { - this.configsMap = configsMap; + public JsonPresentation() { } /** {@inheritDoc} */ @@ -70,12 +64,6 @@ public class JsonPresentation implements ConfigurationPresentation<String> { throw new IllegalArgumentException("Invalid request, no root in request: " + configUpdate); } - Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator = configsMap.get(root); - - if (configurator == null) { - throw new IllegalArgumentException("Invalid request, configuration root not found: " + configUpdate); - } - // Object updateObj = converter.convertFrom(configUpdate, root, configurator.getChangeType()); // configurator.set(BaseSelectors.find(root), updateObj); 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 new file mode 100644 index 0000000..f855ec8 --- /dev/null +++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/JsonConverterTest.java @@ -0,0 +1,211 @@ +/* + * 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.rest.presentation.json; + +import com.google.gson.JsonNull; +import java.util.List; +import org.apache.ignite.configuration.ConfigurationRegistry; +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.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.google.gson.JsonParser.parseString; +import static java.util.Collections.emptyList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.ignite.rest.presentation.json.JsonConverter.jsonVisitor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** */ +public class JsonConverterTest { + /** */ + @ConfigurationRoot(rootName = "root", storage = TestConfigurationStorage.class) + public static class JsonRootConfigurationSchema { + /** */ + @NamedConfigValue + public JsonArraysConfigurationSchema arraysList; + + /** */ + @NamedConfigValue + public JsonPrimitivesConfigurationSchema primitivesList; + } + + /** */ + @Config + public static class JsonArraysConfigurationSchema { + /** */ + @Value(hasDefault = true) + public boolean[] booleans = {false}; + + /** */ + @Value(hasDefault = true) + public int[] ints = {0}; + + /** */ + @Value(hasDefault = true) + public long[] longs = {0L}; + + /** */ + @Value(hasDefault = true) + public double[] doubles = {0d}; + + /** */ + @Value(hasDefault = true) + public String[] strings = {""}; + } + + /** */ + @Config + public static class JsonPrimitivesConfigurationSchema { + /** */ + @Value(hasDefault = true) + public boolean booleanVal = false; + + /** */ + @Value(hasDefault = true) + public int intVal = 0; + + /** */ + @Value(hasDefault = true) + public long longVal = 0L; + + /** */ + @Value(hasDefault = true) + public double doubleVal = 0d; + + /** */ + @Value(hasDefault = true) + public String stringVal = ""; + } + + /** */ + private final ConfigurationRegistry registry = new ConfigurationRegistry(); + + /** */ + private JsonRootConfiguration configuration; + + /** */ + @BeforeEach + public void before() { + registry.registerRootKey(JsonRootConfiguration.KEY); + + registry.registerStorage(new TestConfigurationStorage()); + + configuration = registry.getConfiguration(JsonRootConfiguration.KEY); + } + + /** */ + @Test + public void toJson() throws Exception { + assertEquals( + parseString("{'root':{'arraysList':{},'primitivesList':{}}}"), + registry.represent(emptyList(), jsonVisitor()) + ); + + assertEquals( + parseString("{'arraysList':{},'primitivesList':{}}"), + registry.represent(List.of("root"), jsonVisitor()) + ); + + assertEquals( + parseString("{}"), + registry.represent(List.of("root", "arraysList"), jsonVisitor()) + ); + + configuration.change(cfg -> cfg + .changeArraysList(arraysList -> arraysList + .create("name", arrays -> {}) + ) + .changePrimitivesList(primitivesList -> primitivesList + .create("name", primitives -> {}) + ) + ).get(1, SECONDS); + + assertEquals( + parseString("{'name':{'booleans':[false],'ints':[0],'longs':[0],'doubles':[0.0],'strings':['']}}"), + registry.represent(List.of("root", "arraysList"), jsonVisitor()) + ); + + assertEquals( + parseString("{'booleanVal':false,'intVal':0,'longVal':0,'doubleVal':0.0,'stringVal':''}"), + registry.represent(List.of("root", "primitivesList", "name"), jsonVisitor()) + ); + + assertEquals( + parseString("[false]"), + registry.represent(List.of("root", "arraysList", "name", "booleans"), jsonVisitor()) + ); + + assertEquals( + parseString("[0]"), + registry.represent(List.of("root", "arraysList", "name", "ints"), jsonVisitor()) + ); + + assertEquals( + parseString("[0]"), + registry.represent(List.of("root", "arraysList", "name", "longs"), jsonVisitor()) + ); + + assertEquals( + parseString("[0.0]"), + registry.represent(List.of("root", "arraysList", "name", "doubles"), jsonVisitor()) + ); + + assertEquals( + parseString("['']"), + registry.represent(List.of("root", "arraysList", "name", "strings"), jsonVisitor()) + ); + + assertEquals( + parseString("false"), + registry.represent(List.of("root", "primitivesList", "name", "booleanVal"), jsonVisitor()) + ); + + assertEquals( + parseString("0"), + registry.represent(List.of("root", "primitivesList", "name", "intVal"), jsonVisitor()) + ); + + assertEquals( + parseString("0"), + registry.represent(List.of("root", "primitivesList", "name", "longVal"), jsonVisitor()) + ); + + assertEquals( + parseString("0.0"), + registry.represent(List.of("root", "primitivesList", "name", "doubleVal"), jsonVisitor()) + ); + + assertEquals( + parseString("''"), + registry.represent(List.of("root", "primitivesList", "name", "stringVal"), jsonVisitor()) + ); + + assertThrows(IllegalArgumentException.class, () -> registry.represent(List.of("doot"), jsonVisitor())); + + assertThrows(IllegalArgumentException.class, () -> registry.represent(List.of("root", "x"), jsonVisitor())); + + assertEquals( + JsonNull.INSTANCE, + registry.represent(List.of("root", "primitivesList", "foo"), jsonVisitor()) + ); + } +} 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 new file mode 100644 index 0000000..e175d54 --- /dev/null +++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/json/TestConfigurationStorage.java @@ -0,0 +1,63 @@ +/* + * 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.rest.presentation.json; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +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; + +/** */ +public class TestConfigurationStorage implements ConfigurationStorage { + /** */ + private final Set<ConfigurationStorageListener> listeners = new HashSet<>(); + + /** {@inheritDoc} */ + @Override public Data readAll() throws StorageException { + return new Data(Collections.emptyMap(), 0); + } + + /** {@inheritDoc} */ + @Override public CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version) { + for (ConfigurationStorageListener listener : listeners) + listener.onEntriesChanged(new Data(newValues, version + 1)); + + return CompletableFuture.completedFuture(true); + } + + /** {@inheritDoc} */ + @Override public Set<String> keys() throws StorageException { + return null; + } + + /** {@inheritDoc} */ + @Override public void addListener(ConfigurationStorageListener listener) { + listeners.add(listener); + } + + /** {@inheritDoc} */ + @Override public void removeListener(ConfigurationStorageListener listener) { + listeners.remove(listener); + } +} diff --git a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java index 1847d6f..d6c9195 100644 --- a/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java +++ b/modules/runner/src/main/java/org/apache/ignite/configuration/ConfigurationModule.java @@ -23,7 +23,7 @@ import java.io.Reader; * Module is responsible for preparing configuration when module is started. * * Preparing configuration includes reading it from configuration file, parsing it and initializing - * {@link Configurator} object. + * {@link ConfigurationRegistry} object. */ public class ConfigurationModule { // /** */