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/03/19 10:03:20 UTC
[ignite-3] branch main updated: IGNITE-14183 Reimplemented
validation in new configuration framework. (#67)
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 55359c9 IGNITE-14183 Reimplemented validation in new configuration framework. (#67)
55359c9 is described below
commit 55359c987014c3374c4315836bfae70b5f9c522b
Author: ibessonov <be...@gmail.com>
AuthorDate: Fri Mar 19 13:03:11 2021 +0300
IGNITE-14183 Reimplemented validation in new configuration framework. (#67)
---
.../processor/internal/Processor.java | 100 ++++---
.../configuration/processor/internal/Utils.java | 2 +-
.../internal/validation/ValidationGenerator.java | 174 ------------
.../internal/util/ConfigurationUtilTest.java | 1 -
.../internal/validation/ValidationUtilTest.java | 174 ++++++++++++
.../sample/AutoAdjustConfigurationSchema.java | 13 +-
.../sample/ConfigurationArrayTest.java | 11 +-
.../sample/ConstructableTreeNodeTest.java | 3 -
.../sample/DiscoveryConfigurationSchema.java | 8 +-
.../sample/NodeConfigurationSchema.java | 7 +-
.../sample/TraversableTreeNodeTest.java | 3 -
.../sample/storage/ConfigurationChangerTest.java | 116 +++-----
.../sample/validation/AutoAdjustValidator.java | 41 ---
.../sample/validation/AutoAdjustValidator2.java | 41 ---
.../sample/validation/NodeValidator.java | 40 ---
modules/configuration/pom.xml | 5 +
.../ignite/configuration/ConfigurationChanger.java | 311 +++++++--------------
.../configuration/ConfigurationRegistry.java | 27 +-
.../apache/ignite/configuration/Configurator.java | 94 -------
.../org/apache/ignite/configuration/RootKey.java | 2 +-
.../ignite/configuration/annotation/Validate.java | 75 -----
.../configuration/internal/ConfigurationNode.java | 8 +-
.../internal/DynamicConfiguration.java | 28 +-
.../configuration/internal/DynamicProperty.java | 7 +-
.../internal/NamedListConfiguration.java | 5 +-
.../ignite/configuration/internal/RootKeyImpl.java | 6 +-
.../ignite/configuration/internal/SuperRoot.java | 118 ++++++++
.../internal/util/AnyNodeConfigurationVisitor.java | 49 ++++
.../internal/util/ConfigurationUtil.java | 178 +++++++-----
.../util/KeysTrackingConfigurationVisitor.java | 165 +++++++++++
.../internal/validation/MaxValidator.java | 29 +-
.../internal/validation/MinValidator.java | 29 +-
.../internal/validation/NotNullValidator.java | 41 ---
.../internal/validation/ValidationContextImpl.java | 123 ++++++++
.../internal/validation/ValidationUtil.java | 178 ++++++++++++
.../configuration/tree/ConstructableTreeNode.java | 9 -
.../ignite/configuration/tree/InnerNode.java | 13 +-
.../ignite/configuration/tree/NamedListNode.java | 5 -
.../ConfigurationValidationException.java | 2 +
.../configuration/validation/FieldValidator.java | 46 ---
.../validation/ValidationContext.java | 67 +++++
.../configuration/validation/ValidationIssue.java | 18 ++
.../ignite/configuration/validation/Validator.java | 40 +++
.../java/org/apache/ignite/rest/RestModule.java | 6 +-
.../rest/presentation/json/JsonPresentation.java | 11 +-
parent/pom.xml | 2 +-
46 files changed, 1335 insertions(+), 1096 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 31924bc..81395d6 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
@@ -39,8 +39,6 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
@@ -66,7 +64,6 @@ import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.configuration.internal.DynamicProperty;
import org.apache.ignite.configuration.internal.NamedListConfiguration;
-import org.apache.ignite.configuration.processor.internal.validation.ValidationGenerator;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.InnerNode;
@@ -95,9 +92,6 @@ public class Processor extends AbstractProcessor {
/** Inherit doc javadoc. */
private static final String INHERIT_DOC = "{@inheritDoc}";
- /** Class file writer. */
- private Filer filer;
-
/**
* Constructor.
*/
@@ -105,13 +99,6 @@ public class Processor extends AbstractProcessor {
}
/** {@inheritDoc} */
- @Override public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
-
- filer = processingEnv.getFiler();
- }
-
- /** {@inheritDoc} */
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
final Elements elementUtils = processingEnv.getElementUtils();
@@ -167,7 +154,7 @@ public class Processor extends AbstractProcessor {
TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
.addSuperinterface(configInterface)
- .addModifiers(PUBLIC, FINAL);
+ .addModifiers(FINAL);
TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
.addModifiers(PUBLIC);
@@ -258,12 +245,10 @@ public class Processor extends AbstractProcessor {
configurationClassBuilder.addField(generatedField);
- final CodeBlock validatorsBlock = ValidationGenerator.generateValidators(field);
-
// Constructor statement
constructorBodyBuilder.addStatement(
- "add($L = new $T(keys, $S, rootKey, changer), $L)",
- fieldName, fieldType, fieldName, validatorsBlock
+ "add($L = new $T(keys, $S, rootKey, changer))",
+ fieldName, fieldType, fieldName
);
}
@@ -281,6 +266,12 @@ public class Processor extends AbstractProcessor {
TypeMirror storageType = null;
try {
+ // From JavaDocs: The annotation returned by this method could contain an element whose value is of type Class.
+ // This value cannot be returned directly: information necessary to locate and load a class
+ // (such as the class loader to use) is not available, and the class might not be loadable at all.
+ // Attempting to read a Class object by invoking the relevant method on the returned annotation will
+ // result in a MirroredTypeException, from which the corresponding TypeMirror may be extracted.
+ // Similarly, attempting to read a Class[]-valued element will result in a MirroredTypesException.
rootAnnotation.storage();
}
catch (MirroredTypesException e) {
@@ -291,7 +282,7 @@ public class Processor extends AbstractProcessor {
}
// Create constructors for configuration class
- createConstructors(configurationClassBuilder, constructorBodyBuilder);
+ createConstructors(isRoot, configName, configurationClassBuilder, constructorBodyBuilder);
// Write configuration interface
buildClass(packageName, configurationInterfaceBuilder.build());
@@ -303,22 +294,23 @@ public class Processor extends AbstractProcessor {
}
/** */
- private void createRootKeyField(ClassName configInterface,
+ private void createRootKeyField(
+ ClassName configInterface,
TypeSpec.Builder configurationClassBuilder,
ConfigurationDescription configDesc,
TypeMirror storageType,
ClassName schemaClassName
) {
- ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ClassName.get(RootKey.class), configInterface);
+ ClassName viewClassName = Utils.getViewName(schemaClassName);
+ ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ClassName.get(RootKey.class), configInterface, viewClassName);
ClassName nodeClassName = Utils.getNodeName(schemaClassName);
- FieldSpec keyField = FieldSpec.builder(
- fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
+ FieldSpec keyField = FieldSpec.builder(fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
.initializer(
- "$T.newRootKey($S, $T.class, $T::new, (rootKey, changer) -> new $T($T.emptyList(), $S, rootKey, changer))",
+ "$T.newRootKey($S, $T.class, $T::new, $T::new)",
ConfigurationRegistry.class, configDesc.getName(), storageType, nodeClassName,
- Utils.getConfigurationName(schemaClassName), Collections.class, configDesc.getName()
+ Utils.getConfigurationName(schemaClassName)
)
.build();
@@ -483,24 +475,38 @@ public class Processor extends AbstractProcessor {
/**
* Create configuration class constructors.
*
- * @param configuratorClassName Configurator (configuration wrapper) class name.
- * @param copyConstructorBodyBuilder Copy constructor body.
+ * @param isRoot Flag that indincates whether current configuration is root or not.
+ * @param configName Name of the root if configuration is root, {@code null} otherwise.
* @param configurationClassBuilder Configuration class builder.
* @param constructorBodyBuilder Constructor body.
*/
private void createConstructors(
+ boolean isRoot,
+ String configName,
TypeSpec.Builder configurationClassBuilder,
CodeBlock.Builder constructorBodyBuilder
) {
- final MethodSpec constructorWithName = MethodSpec.constructorBuilder()
- .addModifiers(PUBLIC)
- .addParameter(ParameterizedTypeName.get(List.class, String.class), "prefix")
- .addParameter(String.class, "key")
- .addParameter(ParameterizedTypeName.get(ClassName.get(RootKey.class), WILDCARD), "rootKey")
- .addParameter(ConfigurationChanger.class, "changer")
- .addStatement("super(prefix, key, rootKey, changer)")
+ MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(PUBLIC);
+
+ if (!isRoot) {
+ builder
+ .addParameter(ParameterizedTypeName.get(List.class, String.class), "prefix")
+ .addParameter(String.class, "key");
+ }
+
+ builder
+ .addParameter(ParameterizedTypeName.get(ClassName.get(RootKey.class), WILDCARD, WILDCARD), "rootKey")
+ .addParameter(ConfigurationChanger.class, "changer");
+
+ if (isRoot)
+ builder.addStatement("super($T.emptyList(), $S, rootKey, changer)", Collections.class, configName);
+ else
+ builder.addStatement("super(prefix, key, rootKey, changer)");
+
+ MethodSpec constructorWithName = builder
.addCode(constructorBodyBuilder.build())
.build();
+
configurationClassBuilder.addMethod(constructorWithName);
}
@@ -553,7 +559,7 @@ public class Processor extends AbstractProcessor {
.addModifiers(PUBLIC);
TypeSpec.Builder nodeClsBuilder = TypeSpec.classBuilder(nodeClsName)
- .addModifiers(PUBLIC, FINAL)
+ .addModifiers(FINAL)
.superclass(ClassName.get(InnerNode.class))
.addSuperinterface(viewClsName)
.addSuperinterface(changeClsName)
@@ -627,25 +633,13 @@ public class Processor extends AbstractProcessor {
boolean namedListField = field.getAnnotation(NamedConfigValue.class) != null;
- TypeName viewFieldType = leafField ? schemaFieldType : ClassName.get(
- ((ClassName)schemaFieldType).packageName(),
- ((ClassName)schemaFieldType).simpleName().replace("ConfigurationSchema", "View")
- );
+ TypeName viewFieldType = leafField ? schemaFieldType : Utils.getViewName((ClassName)schemaFieldType);
- TypeName changeFieldType = leafField ? schemaFieldType : ClassName.get(
- ((ClassName)schemaFieldType).packageName(),
- ((ClassName)schemaFieldType).simpleName().replace("ConfigurationSchema", "Change")
- );
+ TypeName changeFieldType = leafField ? schemaFieldType : Utils.getChangeName((ClassName)schemaFieldType);
- TypeName initFieldType = leafField ? schemaFieldType : ClassName.get(
- ((ClassName)schemaFieldType).packageName(),
- ((ClassName)schemaFieldType).simpleName().replace("ConfigurationSchema", "Init")
- );
+ TypeName initFieldType = leafField ? schemaFieldType : Utils.getInitName((ClassName)schemaFieldType);
- TypeName nodeFieldType = leafField ? schemaFieldType.box() : ClassName.get(
- ((ClassName)schemaFieldType).packageName() + (leafField ? "" : ".impl"),
- ((ClassName)schemaFieldType).simpleName().replace("ConfigurationSchema", "Node")
- );
+ TypeName nodeFieldType = leafField ? schemaFieldType.box() : Utils.getNodeName((ClassName)schemaFieldType);
TypeName namedListParamType = nodeFieldType;
@@ -849,7 +843,7 @@ public class Processor extends AbstractProcessor {
if (valAnnotation.hasDefault()) {
constructDefaultBuilder
- .addStatement("case $S: $L = _spec.$L", fieldName, fieldName, fieldName)
+ .addStatement("case $S: $L = _spec.$L" + (isArray ? ".clone()" : ""), fieldName, fieldName, fieldName)
.addStatement(INDENT + "return true");
}
else
@@ -927,7 +921,7 @@ public class Processor extends AbstractProcessor {
JavaFile.builder(packageName, cls)
.indent(INDENT)
.build()
- .writeTo(filer);
+ .writeTo(processingEnv.getFiler());
}
catch (IOException e) {
throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, e);
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
index f11fe9b..b63d1b9 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
@@ -146,7 +146,7 @@ public class Utils {
/** */
public static ClassName getNodeName(ClassName schemaClassName) {
return ClassName.get(
- schemaClassName.packageName() + ".impl",
+ schemaClassName.packageName(),
schemaClassName.simpleName().replace("ConfigurationSchema", "Node")
);
}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java
deleted file mode 100644
index 6b3cc44..0000000
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java
+++ /dev/null
@@ -1,174 +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.processor.internal.validation;
-
-import com.squareup.javapoet.CodeBlock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.MirroredTypesException;
-import javax.lang.model.type.TypeMirror;
-import javax.validation.constraints.Max;
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.annotation.Validate;
-import org.apache.ignite.configuration.internal.validation.MaxValidator;
-import org.apache.ignite.configuration.internal.validation.MinValidator;
-import org.apache.ignite.configuration.internal.validation.NotNullValidator;
-import org.apache.ignite.configuration.processor.internal.ProcessorException;
-
-/**
- * Class that handles validation generation.
- */
-public class ValidationGenerator {
- /**
- * Private constructor.
- */
- private ValidationGenerator() {
- }
-
- /**
- * Generate validation block.
- *
- * @param variableElement Configuration field.
- * @return Code block for field validation.
- */
- public static CodeBlock generateValidators(VariableElement variableElement) {
- List<CodeBlock> validators = new ArrayList<>();
-
- processMin(variableElement, validators);
-
- processMax(variableElement, validators);
-
- processNotNull(variableElement, validators);
-
- processCustomValidations(variableElement, validators);
-
- String text = validators.stream().map(v -> "$L").collect(Collectors.joining(","));
-
- final CodeBlock validatorsArguments = CodeBlock.builder().add(text, validators.toArray()).build();
-
- return CodeBlock.builder().add("$T.asList($L)", Arrays.class, validatorsArguments).build();
- }
-
- /**
- * Process {@link Min} annotations.
- * @param variableElement Field.
- * @param validators Validators code blocks.
- */
- private static void processMin(VariableElement variableElement, List<CodeBlock> validators) {
- final Min minAnnotation = variableElement.getAnnotation(Min.class);
- if (minAnnotation != null) {
- final long minValue = minAnnotation.value();
- final String message = minAnnotation.message();
- final CodeBlock build = CodeBlock.builder().add(
- "new $T<$T<?, ?, ?>>($L, $S)", MinValidator.class, DynamicConfiguration.class, minValue, message
- ).build();
- validators.add(build);
- }
- }
-
- /**
- * Process {@link Max} annotations.
- * @param variableElement Field.
- * @param validators Validators code blocks.
- */
- private static void processMax(VariableElement variableElement, List<CodeBlock> validators) {
- final Max maxAnnotation = variableElement.getAnnotation(Max.class);
-
- if (maxAnnotation != null) {
- final long maxValue = maxAnnotation.value();
- final String message = maxAnnotation.message();
-
- // new MaxValidator
- final CodeBlock build = CodeBlock.builder().add(
- "new $T<$T<?, ?, ?>>($L, $S)", MaxValidator.class, DynamicConfiguration.class, maxValue, message
- ).build();
-
- validators.add(build);
- }
- }
-
- /**
- * Process {@link NotNull} annotation.
- * @param variableElement Field.
- * @param validators Validators code blocks.
- */
- private static void processNotNull(VariableElement variableElement, List<CodeBlock> validators) {
- final NotNull notNull = variableElement.getAnnotation(NotNull.class);
-
- if (notNull != null) {
- final String message = notNull.message();
-
- final CodeBlock build = CodeBlock.builder().add(
- "new $T<$T<?, ?, ?>>($S)", NotNullValidator.class, DynamicConfiguration.class, message
- ).build();
-
- validators.add(build);
- }
- }
-
- /**
- * Process custom validations from {@link Validate} annotation.
- * @param variableElement Field.
- * @param validators Validators code blocks.
- */
- private static void processCustomValidations(VariableElement variableElement, List<CodeBlock> validators) {
- List<Validate> validateAnnotations = new ArrayList<>();
-
- // There can repeatable Validate annotation, hence Validate.List
- final Validate.List validateAnnotationsList = variableElement.getAnnotation(Validate.List.class);
-
- if (validateAnnotationsList != null)
- validateAnnotations.addAll(Arrays.asList(validateAnnotationsList.value()));
-
- // Or there is single Validate annotation
- final Validate validateAnnotationSingle = variableElement.getAnnotation(Validate.class);
-
- if (validateAnnotationSingle != null)
- validateAnnotations.add(validateAnnotationSingle);
-
- for (Validate validateAnnotation : validateAnnotations) {
- List<? extends TypeMirror> values = null;
- try {
- // From JavaDocs: The annotation returned by this method could contain an element whose value is of type Class.
- // This value cannot be returned directly: information necessary to locate and load a class
- // (such as the class loader to use) is not available, and the class might not be loadable at all.
- // Attempting to read a Class object by invoking the relevant method on the returned annotation will
- // result in a MirroredTypeException, from which the corresponding TypeMirror may be extracted.
- // Similarly, attempting to read a Class[]-valued element will result in a MirroredTypesException.
- validateAnnotation.value();
- } catch (MirroredTypesException e) {
- values = e.getTypeMirrors();
- }
-
- if (values == null)
- throw new ProcessorException("Failed to retrieve Validate annotation value");
-
- for (TypeMirror value : values) {
- final String message = validateAnnotation.message();
- final CodeBlock build = CodeBlock.builder().add("new $T($S)", value, message).build();
- validators.add(build);
- }
- }
- }
-
-}
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 0a0fa7a..34f7f68 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
@@ -23,7 +23,6 @@ import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.internal.util.impl.ParentNode;
import org.junit.jupiter.api.Test;
import static java.util.Collections.singletonMap;
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
new file mode 100644
index 0000000..bf00949
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.validation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
+import org.apache.ignite.configuration.sample.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.tree.NamedListView;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** */
+public class ValidationUtilTest {
+ /** */
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ @interface LeafValidation {
+ }
+
+ /** */
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ @interface InnerValidation {
+ }
+
+ /** */
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ @interface NamedListValidation {
+ }
+
+ /** */
+ @ConfigurationRoot(rootName = "root", storage = TestConfigurationStorage.class)
+ public static class ValidatedRootConfigurationSchema {
+ /** */
+ @InnerValidation
+ @ConfigValue
+ public ValidatedChildConfigurationSchema child;
+
+ /** */
+ @NamedListValidation
+ @NamedConfigValue
+ public ValidatedChildConfigurationSchema elements;
+ }
+
+ /** */
+ @Config
+ public static class ValidatedChildConfigurationSchema {
+ /** */
+ @LeafValidation
+ @Value(hasDefault = true)
+ public String str = "foo";
+ }
+
+ /** */
+ private final ValidatedRootNode root = new ValidatedRootNode();
+
+ /** */
+ @BeforeEach
+ public void before() {
+ ConfigurationUtil.addDefaults(root, root);
+ }
+
+ /** */
+ @Test
+ public void validateLeafNode() throws Exception {
+ var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+
+ Validator<LeafValidation, String> validator = new Validator<>() {
+ @Override public void validate(LeafValidation annotation, ValidationContext<String> ctx) {
+ assertEquals("root.child.str", ctx.currentKey());
+
+ assertEquals("foo", ctx.getOldValue());
+ assertEquals("foo", ctx.getNewValue());
+
+ ctx.addIssue(new ValidationIssue("bar"));
+ }
+ };
+
+ Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = Map.of(LeafValidation.class, Set.of(validator));
+
+ List<ValidationIssue> issues = ValidationUtil.validate(rootsNode, rootsNode, null, new HashMap<>(), validators);
+
+ assertEquals(1, issues.size());
+
+ assertEquals("bar", issues.get(0).message());
+ }
+
+ /** */
+ @Test
+ public void validateInnerNode() throws Exception {
+ var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+
+ Validator<InnerValidation, ValidatedChildView> validator = new Validator<>() {
+ @Override public void validate(InnerValidation annotation, ValidationContext<ValidatedChildView> ctx) {
+ assertEquals("root.child", ctx.currentKey());
+
+ assertEquals("foo", ctx.getOldValue().str());
+ assertEquals("foo", ctx.getNewValue().str());
+
+ ctx.addIssue(new ValidationIssue("bar"));
+ }
+ };
+
+ Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = Map.of(InnerValidation.class, Set.of(validator));
+
+ List<ValidationIssue> issues = ValidationUtil.validate(rootsNode, rootsNode, null, new HashMap<>(), validators);
+
+ assertEquals(1, issues.size());
+
+ assertEquals("bar", issues.get(0).message());
+ }
+
+ /** */
+ @Test
+ public void validateNamedListNode() throws Exception {
+ var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+
+ Validator<NamedListValidation, NamedListView<?>> validator = new Validator<>() {
+ @Override public void validate(NamedListValidation annotation, ValidationContext<NamedListView<?>> ctx) {
+ assertEquals("root.elements", ctx.currentKey());
+
+ assertEquals(emptySet(), ctx.getOldValue().namedListKeys());
+ assertEquals(emptySet(), ctx.getNewValue().namedListKeys());
+
+ ctx.addIssue(new ValidationIssue("bar"));
+ }
+ };
+
+ Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = Map.of(NamedListValidation.class, Set.of(validator));
+
+ List<ValidationIssue> issues = ValidationUtil.validate(rootsNode, rootsNode, null, new HashMap<>(), validators);
+
+ assertEquals(1, issues.size());
+
+ assertEquals("bar", issues.get(0).message());
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
index 86245a5..954fc97 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
@@ -19,10 +19,7 @@ package org.apache.ignite.configuration.sample;
import javax.validation.constraints.Min;
import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.Validate;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.sample.validation.AutoAdjustValidator;
-import org.apache.ignite.configuration.sample.validation.AutoAdjustValidator2;
/**
* Test auto adjust configuration schema.
@@ -30,13 +27,11 @@ import org.apache.ignite.configuration.sample.validation.AutoAdjustValidator2;
@Config
public class AutoAdjustConfigurationSchema {
/** Timeout. */
- @Value
+ @Value(hasDefault = true)
@Min(value = 0, message = "Minimal is 0")
- @Validate(value = AutoAdjustValidator.class, message = "a")
- @Validate(value = AutoAdjustValidator2.class, message = "b")
- public long timeout;
+ public long timeout = 0L;
/** Enabled. */
- @Value
- public boolean enabled;
+ @Value(hasDefault = true)
+ public boolean enabled = true;
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
index 4f9929e..961a8bb 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
@@ -17,14 +17,12 @@
package org.apache.ignite.configuration.sample;
-import java.io.Serializable;
import java.util.function.Supplier;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.sample.impl.TestArrayNode;
-import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.junit.jupiter.api.Test;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.leafNodeVisitor;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@@ -95,11 +93,6 @@ public class ConfigurationArrayTest {
* @return Array field value.
*/
private String[] getArray(TestArrayNode arrayNode) {
- return arrayNode.traverseChild("array", new ConfigurationVisitor<String[]>() {
- /** {@inheritDoc} */
- @Override public String[] visitLeafNode(String key, Serializable val) {
- return (String[]) val;
- }
- });
+ return (String[])arrayNode.traverseChild("array", leafNodeVisitor());
}
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
index 12f260d..3b8c274 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
@@ -19,9 +19,6 @@ package org.apache.ignite.configuration.sample;
import java.util.Collections;
import java.util.NoSuchElementException;
-import org.apache.ignite.configuration.sample.impl.ChildNode;
-import org.apache.ignite.configuration.sample.impl.NamedElementNode;
-import org.apache.ignite.configuration.sample.impl.ParentNode;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.configuration.tree.NamedListNode;
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
index e709c21..bf4d040 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/DiscoveryConfigurationSchema.java
@@ -26,10 +26,10 @@ import org.apache.ignite.configuration.annotation.Value;
@Config
public class DiscoveryConfigurationSchema {
/** Node failure detection timeout. */
- @Value
- public int failureDetectionTimeout;
+ @Value(hasDefault = true)
+ public int failureDetectionTimeout = 10_000;
/** Node join timeout. */
- @Value
- public int joinTimeout;
+ @Value(hasDefault = true)
+ public int joinTimeout = 5_000;
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
index df21d02..0cadba1 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
@@ -19,9 +19,7 @@ package org.apache.ignite.configuration.sample;
import javax.validation.constraints.NotNull;
import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.Validate;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.sample.validation.NodeValidator;
/**
* Test node configuration schema.
@@ -38,7 +36,6 @@ public class NodeConfigurationSchema {
public int port;
/** Auto adjust enabled. */
- @Value
- @Validate(NodeValidator.class)
- public boolean autoAdjustEnabled;
+ @Value(hasDefault = true)
+ public boolean autoAdjustEnabled = true;
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
index 6248654..7dbe609 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
@@ -25,9 +25,6 @@ import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.sample.impl.ChildNode;
-import org.apache.ignite.configuration.sample.impl.NamedElementNode;
-import org.apache.ignite.configuration.sample.impl.ParentNode;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.tree.NamedListNode;
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 d35f4c2..57d54a2 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
@@ -17,24 +17,28 @@
package org.apache.ignite.configuration.sample.storage;
import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.apache.ignite.configuration.ConfigurationChangeException;
import org.apache.ignite.configuration.ConfigurationChanger;
-import org.apache.ignite.configuration.Configurator;
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;
-import org.apache.ignite.configuration.sample.storage.impl.DefaultsNode;
import org.apache.ignite.configuration.storage.Data;
+import org.apache.ignite.configuration.validation.ValidationContext;
import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.configuration.sample.storage.AConfiguration.KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -45,11 +49,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* Test configuration changer.
*/
public class ConfigurationChangerTest {
+ /** Annotation used to test failing validation. */
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ @interface MaybeInvalid {
+ }
+
/** */
@ConfigurationRoot(rootName = "key", storage = TestConfigurationStorage.class)
public static class AConfigurationSchema {
/** */
@ConfigValue
+ @MaybeInvalid
private BConfigurationSchema child;
/** */
@@ -84,17 +95,12 @@ public class ConfigurationChangerTest {
public void testSimpleConfigurationChange() throws Exception {
final TestConfigurationStorage storage = new TestConfigurationStorage();
- final ConfiguratorController configuratorController = new ConfiguratorController();
- final Configurator<?> configurator = configuratorController.configurator();
-
ANode data = new ANode()
.initChild(init -> init.initIntCfg(1).initStrCfg("1"))
.initElements(change -> change.create("a", init -> init.initStrCfg("1")));
final ConfigurationChanger changer = new ConfigurationChanger(KEY);
- changer.init(storage);
-
- changer.registerConfiguration(KEY, configurator);
+ changer.register(storage);
changer.change(Collections.singletonMap(KEY, data)).get(1, SECONDS);
@@ -112,9 +118,6 @@ public class ConfigurationChangerTest {
public void testModifiedFromAnotherStorage() throws Exception {
final TestConfigurationStorage storage = new TestConfigurationStorage();
- final ConfiguratorController configuratorController = new ConfiguratorController();
- final Configurator<?> configurator = configuratorController.configurator();
-
ANode data1 = new ANode()
.initChild(init -> init.initIntCfg(1).initStrCfg("1"))
.initElements(change -> change.create("a", init -> init.initStrCfg("1")));
@@ -127,13 +130,10 @@ public class ConfigurationChangerTest {
);
final ConfigurationChanger changer1 = new ConfigurationChanger(KEY);
- changer1.init(storage);
+ changer1.register(storage);
final ConfigurationChanger changer2 = new ConfigurationChanger(KEY);
- changer2.init(storage);
-
- changer1.registerConfiguration(KEY, configurator);
- changer2.registerConfiguration(KEY, configurator);
+ changer2.register(storage);
changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
changer2.change(Collections.singletonMap(KEY, data2)).get(1, SECONDS);
@@ -160,9 +160,6 @@ public class ConfigurationChangerTest {
public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws Exception {
final TestConfigurationStorage storage = new TestConfigurationStorage();
- final ConfiguratorController configuratorController = new ConfiguratorController();
- final Configurator<?> configurator = configuratorController.configurator();
-
ANode data1 = new ANode()
.initChild(init -> init.initIntCfg(1).initStrCfg("1"))
.initElements(change -> change.create("a", init -> init.initStrCfg("1")));
@@ -175,17 +172,18 @@ public class ConfigurationChangerTest {
);
final ConfigurationChanger changer1 = new ConfigurationChanger(KEY);
- changer1.init(storage);
+ changer1.register(storage);
final ConfigurationChanger changer2 = new ConfigurationChanger(KEY);
- changer2.init(storage);
-
- changer1.registerConfiguration(KEY, configurator);
- changer2.registerConfiguration(KEY, configurator);
+ changer2.register(storage);
changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
- configuratorController.hasIssues(true);
+ changer2.addValidator(MaybeInvalid.class, new Validator<MaybeInvalid, Object>() {
+ @Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
+ ctx.addIssue(new ValidationIssue("foo"));
+ }
+ });
assertThrows(ExecutionException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)).get(1, SECONDS));
@@ -203,22 +201,17 @@ public class ConfigurationChangerTest {
public void testFailedToWrite() {
final TestConfigurationStorage storage = new TestConfigurationStorage();
- final ConfiguratorController configuratorController = new ConfiguratorController();
- final Configurator<?> configurator = configuratorController.configurator();
-
ANode data = new ANode().initChild(child -> child.initIntCfg(1));
final ConfigurationChanger changer = new ConfigurationChanger(KEY);
storage.fail(true);
- assertThrows(ConfigurationChangeException.class, () -> changer.init(storage));
+ assertThrows(ConfigurationChangeException.class, () -> changer.register(storage));
storage.fail(false);
- changer.init(storage);
-
- changer.registerConfiguration(KEY, configurator);
+ changer.register(storage);
storage.fail(true);
@@ -257,6 +250,10 @@ public class ConfigurationChangerTest {
/** */
@Value(hasDefault = true)
public String defStr = "bar";
+
+ /** */
+ @Value(hasDefault = true)
+ public String[] arr = {"xyz"};
}
@Test
@@ -265,12 +262,15 @@ public class ConfigurationChangerTest {
changer.addRootKey(DefaultsConfiguration.KEY);
- changer.init(new TestConfigurationStorage());
+ changer.register(new TestConfigurationStorage());
+
+ changer.initialize(TestConfigurationStorage.class);
DefaultsNode root = (DefaultsNode)changer.getRootNode(DefaultsConfiguration.KEY);
assertEquals("foo", root.defStr());
assertEquals("bar", root.child().defStr());
+ assertEquals(List.of("xyz"), Arrays.asList(root.child().arr()));
// This is not init, move it to another test =(
changer.change(Map.of(DefaultsConfiguration.KEY, new DefaultsNode().changeChildsList(childs ->
@@ -281,50 +281,4 @@ public class ConfigurationChangerTest {
assertEquals("bar", root.childsList().get("name").defStr());
}
-
- /**
- * Wrapper for Configurator mock to control validation.
- */
- private static class ConfiguratorController {
- /** Configurator. */
- final Configurator<?> configurator;
-
- /** Whether validate method should return issues. */
- private boolean hasIssues;
-
- /** Constructor. */
- private ConfiguratorController() {
- this(false);
- }
-
- /** Constructor. */
- private ConfiguratorController(boolean hasIssues) {
- this.hasIssues = hasIssues;
-
- configurator = Mockito.mock(Configurator.class);
-
- Mockito.when(configurator.validateChanges(Mockito.any())).then(mock -> {
- if (this.hasIssues)
- return Collections.singletonList(new ValidationIssue());
-
- return Collections.emptyList();
- });
- }
-
- /**
- * Set has issues flag.
- * @param hasIssues Has issues flag.
- */
- public void hasIssues(boolean hasIssues) {
- this.hasIssues = hasIssues;
- }
-
- /**
- * Get configurator.
- * @return Configurator.
- */
- public Configurator<?> configurator() {
- return configurator;
- }
- }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java
deleted file mode 100644
index fdd2d58..0000000
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java
+++ /dev/null
@@ -1,41 +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.sample.validation;
-
-import org.apache.ignite.configuration.sample.LocalConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
-
-/**
- * Test validator for AutoAdjust.
- */
-public class AutoAdjustValidator extends FieldValidator<Number, LocalConfiguration> {
- /** Constructor. */
- public AutoAdjustValidator(String message) {
- super(message);
- }
-
- /** {@inheritDoc} */
- @Override public void validate(Number value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
- final Boolean isEnabled = newRoot.baseline().autoAdjust().enabled().value();
-
- if (value.longValue() > 0 && !isEnabled)
- throw new ConfigurationValidationException(message);
- }
-
-}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java
deleted file mode 100644
index 5ab1bba..0000000
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java
+++ /dev/null
@@ -1,41 +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.sample.validation;
-
-import org.apache.ignite.configuration.sample.LocalConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
-
-/**
- * Test validator for AutoAdjust (works the same as the other one).
- */
-public class AutoAdjustValidator2 extends FieldValidator<Number, LocalConfiguration> {
- /** Constructor. */
- public AutoAdjustValidator2(String message) {
- super(message);
- }
-
- /** {@inheritDoc} */
- @Override public void validate(Number value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
- final Boolean isEnabled = newRoot.baseline().autoAdjust().enabled().value();
-
- if (value.longValue() > 0 && !isEnabled)
- throw new ConfigurationValidationException(message);
- }
-
-}
\ No newline at end of file
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.java
deleted file mode 100644
index 5c99658..0000000
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.java
+++ /dev/null
@@ -1,40 +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.sample.validation;
-
-import org.apache.ignite.configuration.sample.LocalConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
-
-/**
- * Test Node configuration validator.
- */
-public class NodeValidator extends FieldValidator<Boolean, LocalConfiguration> {
- /** Constructor. */
- public NodeValidator(String message) {
- super(message);
- }
-
- @Override public void validate(Boolean value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
- if (value != null && value) {
- if (!newRoot.baseline().autoAdjust().enabled().value()) {
- throw new ConfigurationValidationException("");
- }
- }
- }
-}
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index 68164f7..0da06b4 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -40,6 +40,11 @@
<artifactId>validation-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.jetbrains</groupId>
+ <artifactId>annotations</artifactId>
+ </dependency>
+
<!-- Test dependencies. -->
<dependency>
<groupId>log4j</groupId>
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 1934f72..67196ee 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
@@ -17,32 +17,34 @@
package org.apache.ignite.configuration;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
-import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+import org.apache.ignite.configuration.internal.validation.ValidationUtil;
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.ConfigurationSource;
-import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.InnerNode;
-import org.apache.ignite.configuration.tree.NamedListNode;
-import org.apache.ignite.configuration.tree.TraversableTreeNode;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.addDefaults;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.fillFromPrefixMap;
import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.nodeToFlatMap;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.patch;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.toPrefixMap;
/**
* Class that handles configuration changes, by validating them, passing to storage and listening to storage updates.
@@ -51,56 +53,66 @@ public class ConfigurationChanger {
/** */
private final ForkJoinPool pool = new ForkJoinPool(2);
- /** Map of configurations' configurators. */
- @Deprecated
- private final Map<RootKey<?>, Configurator<?>> configurators = new HashMap<>();
-
/** */
- private final Set<RootKey<?>> rootKeys = new HashSet<>();
+ private final Map<String, RootKey<?, ?>> rootKeys = new TreeMap<>();
/** Map that has all the trees in accordance to their storages. */
private final Map<Class<? extends ConfigurationStorage>, StorageRoots> storagesRootsMap = new ConcurrentHashMap<>();
+ /** Annotation classes mapped to validator objects. */
+ private Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = new HashMap<>();
+
/**
* Immutable data container to store version and all roots associated with the specific storage.
*/
- public static class StorageRoots {
+ private static class StorageRoots {
/** Immutable forest, so to say. */
- private final Map<RootKey<?>, InnerNode> roots;
+ private final SuperRoot 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);
+ private StorageRoots(SuperRoot roots, long version) {
+ this.roots = roots;
this.version = version;
}
}
+ /** Lazy annotations cache for configuration schema fields. */
+ private final Map<MemberKey, Annotation[]> cachedAnnotations = new ConcurrentHashMap<>();
+
/** Storage instances by their classes. Comes in handy when all you have is {@link RootKey}. */
private final Map<Class<? extends ConfigurationStorage>, ConfigurationStorage> storageInstances = new HashMap<>();
/** Constructor. */
- public ConfigurationChanger(RootKey<?>... rootKeys) {
- this.rootKeys.addAll(Arrays.asList(rootKeys));
+ public ConfigurationChanger(RootKey<?, ?>... rootKeys) {
+ for (RootKey<?, ?> rootKey : rootKeys)
+ this.rootKeys.put(rootKey.key(), rootKey);
}
/** */
- public void addRootKey(RootKey<?> rootKey) {
+ public <A extends Annotation> void addValidator(Class<A> annotationType, Validator<A, ?> validator) {
+ validators
+ .computeIfAbsent(annotationType, a -> new HashSet<>())
+ .add(validator);
+ }
+
+ /** */
+ public void addRootKey(RootKey<?, ?> rootKey) {
assert !storageInstances.containsKey(rootKey.getStorageType());
- rootKeys.add(rootKey);
+ rootKeys.put(rootKey.key(), rootKey);
}
/**
- * Initialize changer.
+ * Register changer.
*/
// ConfigurationChangeException, really?
- public void init(ConfigurationStorage configurationStorage) throws ConfigurationChangeException {
+ public void register(ConfigurationStorage configurationStorage) throws ConfigurationChangeException {
storageInstances.put(configurationStorage.getClass(), configurationStorage);
- Set<RootKey<?>> storageRootKeys = rootKeys.stream().filter(
+ Set<RootKey<?, ?>> storageRootKeys = rootKeys.values().stream().filter(
rootKey -> configurationStorage.getClass() == rootKey.getStorageType()
).collect(Collectors.toSet());
@@ -113,130 +125,79 @@ public class ConfigurationChanger {
throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
}
- Map<RootKey<?>, InnerNode> storageRootsMap = new HashMap<>();
- // Map to collect defaults for not initialized configurations.
- Map<RootKey<?>, InnerNode> storageDefaultsMap = new HashMap<>();
+ SuperRoot superRoot = new SuperRoot(rootKeys);
- Map<String, ?> dataValuesPrefixMap = ConfigurationUtil.toPrefixMap(data.values());
+ Map<String, ?> dataValuesPrefixMap = toPrefixMap(data.values());
- for (RootKey<?> rootKey : storageRootKeys) {
+ for (RootKey<?, ?> rootKey : storageRootKeys) {
Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
InnerNode rootNode = rootKey.createRootNode();
if (rootPrefixMap != null)
- ConfigurationUtil.fillFromPrefixMap(rootNode, rootPrefixMap);
-
- // Collecting defaults requires fresh new root.
- InnerNode defaultsNode = rootKey.createRootNode();
+ fillFromPrefixMap(rootNode, rootPrefixMap);
- addDefaults(rootNode, defaultsNode);
-
- storageRootsMap.put(rootKey, rootNode);
- storageDefaultsMap.put(rootKey, defaultsNode);
+ superRoot.addRoot(rootKey, rootNode);
}
- storagesRootsMap.put(configurationStorage.getClass(), new StorageRoots(storageRootsMap, data.version()));
+ StorageRoots storageRoots = new StorageRoots(superRoot, data.version());
+
+ storagesRootsMap.put(configurationStorage.getClass(), storageRoots);
configurationStorage.addListener(changedEntries -> updateFromListener(
configurationStorage.getClass(),
changedEntries
));
+ }
+
+ /** */
+ public void initialize(Class<? extends ConfigurationStorage> storageType) {
+ ConfigurationStorage configurationStorage = storageInstances.get(storageType);
+
+ assert configurationStorage != null : storageType;
+
+ StorageRoots storageRoots = storagesRootsMap.get(storageType);
+
+ SuperRoot superRoot = storageRoots.roots;
+ SuperRoot defaultsNode = new SuperRoot(rootKeys);
+
+ addDefaults(superRoot, defaultsNode);
+
+ List<ValidationIssue> validationIssues = ValidationUtil.validate(
+ superRoot,
+ defaultsNode,
+ this::getRootNode,
+ cachedAnnotations,
+ validators
+ );
+
+ if (!validationIssues.isEmpty())
+ throw new ConfigurationValidationException(validationIssues);
- // Do this strictly after adding listeners, otherwise we can lose these changes.
try {
- //TODO IGNITE-14183 Do not write defaults that have not been validated. This can ruin everything.
- change(storageDefaultsMap).get();
+ change(defaultsNode, storageType).get();
}
catch (InterruptedException | ExecutionException e) {
- throw new ConfigurationChangeException("too bad", e);
+ throw new ConfigurationChangeException(
+ "Failed to write defalut configuration values into the storage " + configurationStorage.getClass(), e
+ );
}
}
/**
- * Fill {@code dst} node with default values, required to complete {@code src} node.
- * These two objects can be the same, this would mean that all {@code null} values of {@code scr} will be
- * replaced with defaults if it's possible.
- *
- * @param src Source node.
- * @param dst Destination node.
- */
- private void addDefaults(InnerNode src, InnerNode dst) {
- src.traverseChildren(new ConfigurationVisitor<>() {
- @Override public Object visitLeafNode(String key, Serializable val) {
- // If source value is null then inititalise the same value on the destination node.
- if (val == null)
- dst.constructDefault(key);
-
- return null;
- }
-
- @Override public Object visitInnerNode(String key, InnerNode srcNode) {
- // Instantiate field in destination node before doing something else.
- // Not a big deal if it wasn't null.
- dst.construct(key, new ConfigurationSource() {});
-
- // Get that inner node from destination to continue the processing.
- InnerNode dstNode = dst.traverseChild(key, new ConfigurationVisitor<>() {
- @Override public InnerNode visitInnerNode(String key, InnerNode dstNode) {
- return dstNode;
- }
- });
-
- // "dstNode" is guaranteed to not be null even if "src" and "dst" match.
- // Null in "srcNode" means that we should initialize everything that we can in "dstNode"
- // unconditionally. It's only possible if we pass it as a source as well.
- addDefaults(srcNode == null ? dstNode : srcNode, dstNode);
-
- return null;
- }
-
- @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> srcNamedList) {
- // Here we don't need to preemptively initialise corresponsing field, because it can never be null.
- NamedListNode<?> dstNamedList = dst.traverseChild(key, new ConfigurationVisitor<>() {
- @Override public <N extends InnerNode> NamedListNode<?> visitNamedListNode(String key, NamedListNode<N> dstNode) {
- return dstNode;
- }
- });
-
- for (String namedListKey : srcNamedList.namedListKeys()) {
- // But, in order to get non-null value from "dstNamedList.get(namedListKey)" we must explicitly
- // ensure its existance.
- dstNamedList.construct(namedListKey, new ConfigurationSource() {});
-
- addDefaults(srcNamedList.get(namedListKey), dstNamedList.get(namedListKey));
- }
-
- return null;
- }
- });
- }
-
- /**
- * Add configurator.
- * @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) {
- 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);
+ public InnerNode getRootNode(RootKey<?, ?> rootKey) {
+ return storagesRootsMap.get(rootKey.getStorageType()).roots.getRoot(rootKey);
}
/**
* Change configuration.
* @param changes Map of changes by root key.
*/
- public CompletableFuture<Void> change(Map<RootKey<?>, ? extends TraversableTreeNode> changes) {
+ public CompletableFuture<Void> change(Map<RootKey<?, ?>, InnerNode> changes) {
if (changes.isEmpty())
return CompletableFuture.completedFuture(null);
@@ -252,8 +213,11 @@ public class ConfigurationChanger {
);
}
- Class<? extends ConfigurationStorage> storageType = storagesTypes.iterator().next();
+ return change(new SuperRoot(rootKeys, changes), storagesTypes.iterator().next());
+ }
+ /** */
+ private CompletableFuture<Void> change(SuperRoot changes, Class<? extends ConfigurationStorage> storageType) {
ConfigurationStorage storage = storageInstances.get(storageType);
CompletableFuture<Void> fut = new CompletableFuture<>();
@@ -270,36 +234,30 @@ public class ConfigurationChanger {
* @param fut Future, that must be completed after changes are written to the storage.
*/
private void change0(
- Map<RootKey<?>, ? extends TraversableTreeNode> changes,
+ SuperRoot changes,
ConfigurationStorage storage,
CompletableFuture<?> fut
) {
StorageRoots storageRoots = storagesRootsMap.get(storage.getClass());
+ SuperRoot curRoots = storageRoots.roots;
Map<String, Serializable> allChanges = new HashMap<>();
- for (Map.Entry<RootKey<?>, ? extends TraversableTreeNode> entry : changes.entrySet()) {
- RootKey<?> rootKey = entry.getKey();
- TraversableTreeNode change = entry.getValue();
+ //TODO IGNITE-14180 single putAll + remove matching value, this way "allChanges" will be fair.
+ // These are changes explicitly provided by the client.
+ allChanges.putAll(nodeToFlatMap(curRoots, changes));
- // It's important to get the root from "roots" object rather then "storageRootMap" or "getRootNode(...)".
- InnerNode currentRootNode = storageRoots.roots.get(rootKey);
+ // It is necessary to reinitialize default values every time.
+ // Possible use case that explicitly requires it: creation of the same named list entry with slightly
+ // different set of values and different dynamic defaults at the same time.
+ SuperRoot patchedSuperRoot = patch(curRoots, changes);
- // These are changes explicitly provided by the client.
- allChanges.putAll(nodeToFlatMap(rootKey, currentRootNode, change));
+ SuperRoot defaultsNode = new SuperRoot(rootKeys);
- // It is necessary to reinitialize default values every time.
- // Possible use case that explicitly requires it: creation of the same named list entry with slightly
- // different set of values and different dynamic defaults at the same time.
- InnerNode patchedRootNode = ConfigurationUtil.patch(currentRootNode, change);
- InnerNode defaultsNode = rootKey.createRootNode();
+ addDefaults(patchedSuperRoot, defaultsNode);
- addDefaults(patchedRootNode, defaultsNode);
-
- // These are default values for non-initialized values, required to complete the configuration.
- //TODO IGNITE-14183 Take these defaults into account during validation.
- allChanges.putAll(nodeToFlatMap(rootKey, patchedRootNode, defaultsNode));
- }
+ // These are default values for non-initialized values, required to complete the configuration.
+ allChanges.putAll(nodeToFlatMap(patchedSuperRoot, defaultsNode));
// Unlikely but still possible.
if (allChanges.isEmpty()) {
@@ -308,9 +266,13 @@ public class ConfigurationChanger {
return;
}
- ValidationResult validationResult = validate(storageRoots, changes);
-
- List<ValidationIssue> validationIssues = validationResult.issues();
+ List<ValidationIssue> validationIssues = ValidationUtil.validate(
+ storageRoots.roots,
+ patch(patchedSuperRoot, defaultsNode),
+ this::getRootNode,
+ cachedAnnotations,
+ validators
+ );
if (!validationIssues.isEmpty()) {
fut.completeExceptionally(new ConfigurationValidationException(validationIssues));
@@ -341,25 +303,15 @@ public class ConfigurationChanger {
) {
StorageRoots oldStorageRoots = this.storagesRootsMap.get(storageType);
- Map<RootKey<?>, InnerNode> storageRootsMap = new HashMap<>(oldStorageRoots.roots);
-
- Map<String, ?> dataValuesPrefixMap = ConfigurationUtil.toPrefixMap(changedEntries.values());
+ Map<String, ?> dataValuesPrefixMap = toPrefixMap(changedEntries.values());
compressDeletedEntries(dataValuesPrefixMap);
- for (RootKey<?> rootKey : oldStorageRoots.roots.keySet()) {
- Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
-
- if (rootPrefixMap != null) {
- InnerNode rootNode = oldStorageRoots.roots.get(rootKey).copy();
-
- ConfigurationUtil.fillFromPrefixMap(rootNode, rootPrefixMap);
+ SuperRoot newSuperRoot = oldStorageRoots.roots.copy();
- storageRootsMap.put(rootKey, rootNode);
- }
- }
+ fillFromPrefixMap(newSuperRoot, dataValuesPrefixMap);
- StorageRoots storageRoots = new StorageRoots(storageRootsMap, changedEntries.version());
+ StorageRoots storageRoots = new StorageRoots(newSuperRoot, changedEntries.version());
storagesRootsMap.put(storageType, storageRoots);
@@ -391,59 +343,4 @@ public class ConfigurationChanger {
compressDeletedEntries((Map<String, ?>)value);
}
}
-
- /**
- * Validate configuration changes.
- *
- * @param storageRoots Storage roots.
- * @param changes Configuration changes.
- * @return Validation results.
- */
- // Will be used in the future, I promise (IGNITE-14183).
- @SuppressWarnings("unused")
- private ValidationResult validate(
- StorageRoots storageRoots,
- Map<RootKey<?>, ? extends TraversableTreeNode> changes
- ) {
- List<ValidationIssue> issues = new ArrayList<>();
-
- for (Map.Entry<RootKey<?>, ? extends TraversableTreeNode> entry : changes.entrySet()) {
- RootKey<?> rootKey = entry.getKey();
- TraversableTreeNode changesForRoot = entry.getValue();
-
- final Configurator<?> configurator = configurators.get(rootKey);
-
- // TODO IGNITE-14183 This will be fixed later
- if (configurator != null) {
- List<ValidationIssue> list = configurator.validateChanges(changesForRoot);
- issues.addAll(list);
- }
- }
-
- return new ValidationResult(issues);
- }
-
- /**
- * Results of the validation.
- */
- private static final class ValidationResult {
- /** List of issues. */
- private final List<ValidationIssue> issues;
-
- /**
- * Constructor.
- * @param issues List of issues.
- */
- private ValidationResult(List<ValidationIssue> issues) {
- this.issues = issues;
- }
-
- /**
- * Get issues.
- * @return Issues.
- */
- public List<ValidationIssue> issues() {
- return issues;
- }
- }
}
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 5c0c7c5..8d4bcc5 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,15 +17,21 @@
package org.apache.ignite.configuration;
+import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;
+import javax.validation.constraints.Max;
+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.validation.MaxValidator;
+import org.apache.ignite.configuration.internal.validation.MinValidator;
import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.validation.Validator;
/** */
public class ConfigurationRegistry {
@@ -35,8 +41,14 @@ public class ConfigurationRegistry {
/** */
private final ConfigurationChanger changer = new ConfigurationChanger();
+ {
+ // Default vaildators implemented in current module.
+ changer.addValidator(Min.class, new MinValidator());
+ changer.addValidator(Max.class, new MaxValidator());
+ }
+
/** */
- public void registerRootKey(RootKey<?> rootKey) {
+ public void registerRootKey(RootKey<?, ?> rootKey) {
changer.addRootKey(rootKey);
configs.put(rootKey.key(), (DynamicConfiguration<?, ?, ?>)rootKey.createPublicRoot(changer));
@@ -45,12 +57,17 @@ public class ConfigurationRegistry {
}
/** */
+ public <A extends Annotation> void registerValidator(Class<A> annotationType, Validator<A, ?> validator) {
+ changer.addValidator(annotationType, validator);
+ }
+
+ /** */
public void registerStorage(ConfigurationStorage configurationStorage) {
- changer.init(configurationStorage);
+ changer.register(configurationStorage);
}
/** */
- public <V, C, T extends ConfigurationTree<V, C>> T getConfiguration(RootKey<T> rootKey) {
+ public <V, C, T extends ConfigurationTree<V, C>> T getConfiguration(RootKey<T, V> rootKey) {
return (T)configs.get(rootKey.key());
}
@@ -63,11 +80,11 @@ public class ConfigurationRegistry {
* @param rootSupplier Closure to instantiate internal configuration tree roots.
* @param publicRootCreator Function to create public user-facing tree instance.
*/
- public static <T extends ConfigurationTree<?, ?>> RootKey<T> newRootKey(
+ public static <T extends ConfigurationTree<V, ?>, V> RootKey<T, V> newRootKey(
String rootName,
Class<? extends ConfigurationStorage> storageType,
Supplier<InnerNode> rootSupplier,
- BiFunction<RootKey<T>, ConfigurationChanger, T> publicRootCreator
+ BiFunction<RootKey<T, V>, ConfigurationChanger, T> publicRootCreator
) {
return new RootKeyImpl<>(rootName, storageType, rootSupplier, publicRootCreator);
}
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
index a3af965..05c1ebe 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
@@ -17,18 +17,7 @@
package org.apache.ignite.configuration;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.internal.DynamicProperty;
-import org.apache.ignite.configuration.internal.validation.MemberKey;
-import org.apache.ignite.configuration.tree.TraversableTreeNode;
-import org.apache.ignite.configuration.validation.FieldValidator;
-import org.apache.ignite.configuration.validation.ValidationIssue;
/**
* Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions
@@ -36,87 +25,4 @@ import org.apache.ignite.configuration.validation.ValidationIssue;
* @param <T> Type of configuration root.
*/
public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
- /** Root of the configuration tree. */
- private final T root;
-
- /** Configuration property validators. */
- private final Map<MemberKey, List<FieldValidator<? extends Serializable, T>>> fieldValidators = new HashMap<>();
-
- /**
- *
- * @param rootBuilder
- * @param <VIEW>
- * @param <INIT>
- * @param <CHANGE>
- * @param <CONF>
- * @return
- */
- public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
- Function<Configurator<CONF>, CONF> rootBuilder
- ) {
- return new Configurator<>(rootBuilder);
- }
-
- /**
- * Constructor.
- * @param rootBuilder Function, that creates configuration root.
- */
- private <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator(
- Function<Configurator<CONF>, CONF> rootBuilder
- ) {
- final CONF built = rootBuilder.apply((Configurator<CONF>) this);
-
- root = (T) built;
- }
-
- /**
- *
- * @param aClass
- * @param key
- * @param validators
- * @param <PROP>
- */
- public <PROP extends Serializable> void addValidations(
- Class<? extends ConfigurationTree<?, ?>> aClass,
- String key,
- List<FieldValidator<? super PROP, ? extends ConfigurationTree<?, ?>>> validators
- ) {
- fieldValidators.put(new MemberKey(aClass, key), (List) validators);
- }
-
- /**
- * Get all validators for given member key (class + field).
- * @param key Member key.
- * @return Validators.
- */
- public List<FieldValidator<? extends Serializable, T>> validators(MemberKey key) {
- return fieldValidators.getOrDefault(key, Collections.emptyList());
- }
-
- /**
- * Get configuration root.
- * @return Configuration root.
- */
- public T getRoot() {
- return root;
- }
-
- /**
- * Execute on property attached to configurator.
- * @param property Property.
- * @param <PROP> Type of the property.
- */
- public <PROP extends Serializable> void onAttached(DynamicProperty<PROP> property) {
- property.addListener(new PropertyListener<PROP, PROP>() {
- /** {@inheritDoc} */
- @Override public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
-// storage.save(key, newValue);
- }
- });
-// storage.listen(key, property::setSilently);
- }
-
- public List<ValidationIssue> validateChanges(TraversableTreeNode changes) {
- return Collections.emptyList();
- }
}
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 e49db7b..bd84c16 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
@@ -21,7 +21,7 @@ import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.configuration.tree.InnerNode;
/** */
-public abstract class RootKey<T extends ConfigurationTree<?, ?>> {
+public abstract class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
/** */
public abstract String key();
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java
deleted file mode 100644
index 2c2c34d..0000000
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java
+++ /dev/null
@@ -1,75 +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.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-import org.apache.ignite.configuration.validation.FieldValidator;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-/**
- * This annotation applies custom validation to configuration field, for example:
- * <pre name="code" class="java">
- * public class ConfSchema {
- * {@literal @}Validate(SomeCustomValidator.class)
- * private String value;
- * }
- * </pre>
- *
- * If you need multiple custom validations:
- * <pre name="code" class="java">
- * public class ConfSchema {
- * {@literal @}Validate(SomeCustomValidator.class),
- * {@literal @}Validate(SomeOtherCustomValidator.class)
- * private String value;
- * }
- * </pre>
- */
-@Target({ FIELD })
-@Retention(SOURCE)
-@Repeatable(Validate.List.class)
-@Documented
-public @interface Validate {
- /**
- * @return Validation class that is going to be instantiated for the field validation.
- */
- Class<? extends FieldValidator<?, ?>> value();
-
- /**
- * @return Validation error message.
- */
- String message() default "";
-
- /**
- * Defines several {@link Validate} annotations on the same element.
- *
- * @see Validate
- */
- @Target({ FIELD })
- @Retention(SOURCE)
- @Documented
- @interface List {
-
- Validate[] value();
- }
-
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
index bcee71a..67f875c 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
@@ -37,7 +37,7 @@ public abstract class ConfigurationNode<VIEW> {
protected final String key;
/** Root key instance for the current trees root. */
- protected final RootKey<?> rootKey;
+ protected final RootKey<?, ?> rootKey;
/** Configuration changer instance to get latest value of the root. */
protected final ConfigurationChanger changer;
@@ -64,7 +64,7 @@ public abstract class ConfigurationNode<VIEW> {
* @param rootKey Root key.
* @param changer Configuration changer.
*/
- protected ConfigurationNode(List<String> prefix, String key, RootKey<?> rootKey, ConfigurationChanger changer) {
+ protected ConfigurationNode(List<String> prefix, String key, RootKey<?, ?> rootKey, ConfigurationChanger changer) {
this.keys = ConfigurationUtil.appendKey(prefix, key);
this.key = key;
this.rootKey = rootKey;
@@ -131,5 +131,7 @@ public abstract class ConfigurationNode<VIEW> {
*
* @param newValue New configuration value.
*/
- protected abstract void beforeRefreshValue(VIEW newValue);
+ protected void beforeRefreshValue(VIEW newValue) {
+ // No-op.
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
index 97c217f..e2ca703 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
@@ -17,7 +17,6 @@
package org.apache.ignite.configuration.internal;
-import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -34,7 +33,6 @@ import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.configuration.tree.InnerNode;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
/**
* This class represents configuration root or node.
@@ -54,7 +52,7 @@ public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends Configura
protected DynamicConfiguration(
List<String> prefix,
String key,
- RootKey<?> rootKey,
+ RootKey<?, ?> rootKey,
ConfigurationChanger changer
) {
super(prefix, key, rootKey, changer);
@@ -65,29 +63,22 @@ public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends Configura
* @param member Configuration member (leaf or node).
* @param <P> Type of member.
*/
- protected <P extends ConfigurationProperty<?, ?>> void add(P member) {
+ protected final <P extends ConfigurationProperty<?, ?>> void add(P member) {
members.put(member.key(), member);
}
/**
- * Add new configuration member with validators.
+ * Add new configuration member.
+ *
* @param member Configuration member (leaf or node).
- * @param validators Validators for new member.
- * @param <PROP> Type of {@link DynamicProperty}.
* @param <M> Type of member.
*/
- protected <PROP extends Serializable, M extends DynamicProperty<PROP>> void add(
- M member,
- List<FieldValidator<? super PROP, ? extends ConfigurationTree<?, ?>>> validators
- ) {
+ protected final <M extends DynamicProperty<?>> void add(M member) {
members.put(member.key(), member);
-
- //TODO IGNITE-14183
-// configurator.addValidations((Class<? extends ConfigurationTree<?, ?>>) getClass(), member.key(), validators);
}
/** {@inheritDoc} */
- @Override public Future<Void> change(Consumer<CHANGE> change) throws ConfigurationValidationException {
+ @Override public final Future<Void> change(Consumer<CHANGE> change) throws ConfigurationValidationException {
Objects.requireNonNull(change, "Configuration consumer cannot be null.");
InnerNode rootNodeChange = ((RootKeyImpl)rootKey).createRootNode();
@@ -127,12 +118,7 @@ public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> extends Configura
}
/** {@inheritDoc} */
- @Override public Map<String, ConfigurationProperty<?, ?>> members() {
+ @Override public final Map<String, ConfigurationProperty<?, ?>> members() {
return Collections.unmodifiableMap(members);
}
-
- /** {@inheritDoc} */
- @Override protected void beforeRefreshValue(VIEW newValue) {
- // No-op.
- }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
index 1beb83a..d74913a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
@@ -51,7 +51,7 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
public DynamicProperty(
List<String> prefix,
String key,
- RootKey<?> rootKey,
+ RootKey<?, ?> rootKey,
ConfigurationChanger changer
) {
super(prefix, key, rootKey, changer);
@@ -106,9 +106,4 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
@Override public String key() {
return key;
}
-
- /** {@inheritDoc} */
- @Override protected void beforeRefreshValue(T newValue) {
- // No-op.
- }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
index 107f06f..9c9e53a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
@@ -53,9 +53,10 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW, CHANGE
public NamedListConfiguration(
List<String> prefix,
String key,
- RootKey<?> rootKey,
+ RootKey<?, ?> rootKey,
ConfigurationChanger changer,
- BiFunction<List<String>, String, T> creator) {
+ BiFunction<List<String>, String, T> creator
+ ) {
super(prefix, key, rootKey, changer);
this.creator = creator;
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
index 66c04ea..8710f4d 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
@@ -26,7 +26,7 @@ import org.apache.ignite.configuration.storage.ConfigurationStorage;
import org.apache.ignite.configuration.tree.InnerNode;
/** */
-public class RootKeyImpl<T extends ConfigurationTree<?, ?>> extends RootKey<T> {
+public class RootKeyImpl<T extends ConfigurationTree<VIEW, ?>, VIEW> extends RootKey<T, VIEW> {
/** */
private final String rootName;
@@ -37,14 +37,14 @@ public class RootKeyImpl<T extends ConfigurationTree<?, ?>> extends RootKey<T> {
private final Supplier<InnerNode> rootSupplier;
/** */
- private final BiFunction<RootKey<T>, ConfigurationChanger, T> publicRootCreator;
+ private final BiFunction<RootKey<T, VIEW>, ConfigurationChanger, T> publicRootCreator;
/** */
public RootKeyImpl(
String rootName,
Class<? extends ConfigurationStorage> storageType,
Supplier<InnerNode> rootSupplier,
- BiFunction<RootKey<T>, ConfigurationChanger, T> publicRootCreator
+ BiFunction<RootKey<T, VIEW>, ConfigurationChanger, T> publicRootCreator
) {
this.rootName = rootName;
this.storageType = storageType;
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
new file mode 100644
index 0000000..a83852b
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal;
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+
+/** */
+public final class SuperRoot extends InnerNode {
+ /** */
+ private final SortedMap<String, InnerNode> roots = new TreeMap<>();
+
+ /** */
+ private final Map<String, RootKey<?, ?>> allRootKeys;
+
+ /** Copy constructor. */
+ private SuperRoot(SuperRoot superRoot) {
+ roots.putAll(superRoot.roots);
+
+ allRootKeys = superRoot.allRootKeys;
+ }
+
+ /** */
+ public SuperRoot(Map<String, RootKey<?, ?>> rootKeys) {
+ allRootKeys = rootKeys;
+ }
+
+ /** */
+ public SuperRoot(Map<String, RootKey<?, ?>> rootKeys, Map<RootKey<?, ?>, InnerNode> roots) {
+ allRootKeys = rootKeys;
+
+ for (Map.Entry<RootKey<?, ?>, InnerNode> entry : roots.entrySet())
+ this.roots.put(entry.getKey().key(), entry.getValue());
+ }
+
+ /** */
+ public void addRoot(RootKey<?, ?> rootKey, InnerNode root) {
+ assert !roots.containsKey(rootKey.key()) : rootKey.key() + " : " + roots;
+ assert allRootKeys.get(rootKey.key()) == rootKey : rootKey.key() + " : " + allRootKeys;
+
+ roots.put(rootKey.key(), root);
+ }
+
+ /** */
+ public InnerNode getRoot(RootKey<?, ?> rootKey) {
+ return roots.get(rootKey.key());
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> void traverseChildren(ConfigurationVisitor<T> visitor) {
+ for (Map.Entry<String, InnerNode> entry : roots.entrySet())
+ visitor.visitInnerNode(entry.getKey(), entry.getValue());
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T traverseChild(String key, ConfigurationVisitor<T> visitor) throws NoSuchElementException {
+ InnerNode root = roots.get(key);
+
+ if (root == null)
+ throw new NoSuchElementException(key);
+
+ return visitor.visitInnerNode(key, root);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void construct(String key, ConfigurationSource src) throws NoSuchElementException {
+ assert src != null;
+
+ RootKeyImpl<?, ?> rootKey = (RootKeyImpl<?, ?>)allRootKeys.get(key);
+
+ if (rootKey == null)
+ throw new NoSuchElementException(key);
+
+ InnerNode root = roots.get(key);
+
+ root = root == null ? rootKey.createRootNode() : root.copy();
+
+ roots.put(key, root);
+
+ src.descend(root);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean constructDefault(String key) throws NoSuchElementException {
+ throw new NoSuchElementException(key);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Class<?> schemaType() {
+ return Object.class;
+ }
+
+ /** {@inheritDoc} */
+ @Override public SuperRoot copy() {
+ return new SuperRoot(this);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/AnyNodeConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/AnyNodeConfigurationVisitor.java
new file mode 100644
index 0000000..c91fd03
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/AnyNodeConfigurationVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.util;
+
+import java.io.Serializable;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+
+/** Visitor with just a single method to override. */
+public abstract class AnyNodeConfigurationVisitor<T> implements ConfigurationVisitor<T> {
+ /** {@inheritDoc} */
+ @Override public final T visitLeafNode(String key, Serializable val) {
+ return visitNode(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public final T visitInnerNode(String key, InnerNode node) {
+ return visitNode(key, node);
+ }
+
+ /** {@inheritDoc} */
+ @Override public final <N extends InnerNode> T visitNamedListNode(String key, NamedListNode<N> node) {
+ return visitNode(key, node);
+ }
+
+ /**
+ * Visit tree node.
+ *
+ * @param key Name of the node.
+ * @param node {@link InnerNode}, {@link NamedListNode<?>} or {@link Serializable} leaf.
+ */
+ protected abstract T visitNode(String key, Object node);
+}
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 1c09c97..53fd2f2 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
@@ -26,7 +26,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.stream.Collectors;
-import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.internal.SuperRoot;
import org.apache.ignite.configuration.tree.ConfigurationSource;
import org.apache.ignite.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.configuration.tree.ConstructableTreeNode;
@@ -282,99 +282,52 @@ public class ConfigurationUtil {
* @return Map of changes.
*/
public static Map<String, Serializable> nodeToFlatMap(
- RootKey<?> rootKey,
- TraversableTreeNode curRoot,
- TraversableTreeNode updates
+ SuperRoot curRoots,
+ SuperRoot updates
) {
- return updates.accept(rootKey.key(), new ConfigurationVisitor<>() {
- /** Resulting flat map. */
- private Map<String, Serializable> values = new HashMap<>();
-
- /** Current key, aggregated by visitor. */
- private StringBuilder currentKey = new StringBuilder();
-
- /** Current keys list, almost the same as {@link #currentKey}. */
- private List<String> currentPath = new ArrayList<>();
+ Map<String, Serializable> values = new HashMap<>();
+ updates.traverseChildren(new KeysTrackingConfigurationVisitor<>() {
/** Write nulls instead of actual values. Makes sense for deletions from named lists. */
private boolean writeNulls;
/** {@inheritDoc} */
- @Override public Map<String, Serializable> visitLeafNode(String key, Serializable val) {
+ @Override public Map<String, Serializable> doVisitLeafNode(String key, Serializable val) {
if (val != null)
- values.put(currentKey.toString() + key, writeNulls ? null : val);
+ values.put(currentKey(), writeNulls ? null : val);
return values;
}
/** {@inheritDoc} */
- @Override public Map<String, Serializable> visitInnerNode(String key, InnerNode node) {
+ @Override public Map<String, Serializable> doVisitInnerNode(String key, InnerNode node) {
if (node == null)
return null;
- int previousKeyLength = startVisit(key, false);
-
node.traverseChildren(this);
- endVisit(previousKeyLength);
-
return values;
}
/** {@inheritDoc} */
- @Override public <N extends InnerNode> Map<String, Serializable> visitNamedListNode(String key, NamedListNode<N> node) {
- int previousKeyLength = startVisit(key, false);
-
+ @Override public <N extends InnerNode> Map<String, Serializable> doVisitNamedListNode(String key, NamedListNode<N> node) {
for (String namedListKey : node.namedListKeys()) {
- int loopPreviousKeyLength = startVisit(namedListKey, true);
-
N namedElement = node.get(namedListKey);
- if (namedElement == null)
- visitDeletedNamedListElement();
- else
- namedElement.traverseChildren(this);
+ withTracking(namedListKey, true, false, () -> {
+ if (namedElement == null)
+ visitDeletedNamedListElement();
+ else
+ namedElement.traverseChildren(this);
- endVisit(loopPreviousKeyLength);
+ return null;
+ });
}
- endVisit(previousKeyLength);
-
return values;
}
/**
- * Prepares values of {@link #currentKey} and {@link #currentPath} for further processing.
- *
- * @param key Key.
- * @param escape Whether we need to escape the key before appending it to {@link #currentKey}.
- * @return Previous length of {@link #currentKey} so it can be passed to {@link #endVisit(int)} later.
- */
- private int startVisit(String key, boolean escape) {
- int previousKeyLength = currentKey.length();
-
- currentKey.append(escape ? ConfigurationUtil.escape(key) : key).append('.');
-
- if (!writeNulls)
- currentPath.add(key);
-
- return previousKeyLength;
- }
-
- /**
- * Puts {@link #currentKey} and {@link #currentPath} in the same state as they were before
- * {@link #startVisit(String, boolean)}.
- *
- * @param previousKeyLength Value return by corresponding {@link #startVisit(String, boolean)} invocation.
- */
- private void endVisit(int previousKeyLength) {
- currentKey.setLength(previousKeyLength);
-
- if (!writeNulls)
- currentPath.remove(currentPath.size() - 1);
- }
-
- /**
* Here we must list all joined keys belonging to deleted element. The only way to do it is to traverse
* cached configuration tree and write {@code null} into all values.
*/
@@ -384,11 +337,13 @@ public class ConfigurationUtil {
Object originalNamedElement = null;
+ List<String> currentPath = currentPath();
+
try {
// This code can in fact be better optimized for deletion scenario,
// but there's no point in doing that, since the operation is so rare and it will
// complicate code even more.
- originalNamedElement = find(currentPath.subList(1, currentPath.size()), curRoot);
+ originalNamedElement = find(currentPath, curRoots);
}
catch (KeyNotFoundException ignore) {
// May happen, not a big deal. This means that element never existed in the first place.
@@ -405,6 +360,8 @@ public class ConfigurationUtil {
}
}
});
+
+ return values;
}
/**
@@ -456,7 +413,94 @@ public class ConfigurationUtil {
return copy;
}
- /** */
+ /**
+ * Fill {@code dst} node with default values, required to complete {@code src} node.
+ * These two objects can be the same, this would mean that all {@code null} values of {@code scr} will be
+ * replaced with defaults if it's possible.
+ *
+ * @param src Source node.
+ * @param dst Destination node.
+ */
+ public static void addDefaults(InnerNode src, InnerNode dst) {
+ assert src.getClass() == dst.getClass();
+
+ src.traverseChildren(new ConfigurationVisitor<>() {
+ @Override public Object visitLeafNode(String key, Serializable val) {
+ // If source value is null then inititalise the same value on the destination node.
+ if (val == null)
+ dst.constructDefault(key);
+
+ return null;
+ }
+
+ @Override public Object visitInnerNode(String key, InnerNode srcNode) {
+ // Instantiate field in destination node before doing something else.
+ // Not a big deal if it wasn't null.
+ dst.construct(key, new ConfigurationSource() {});
+
+ // Get that inner node from destination to continue the processing.
+ InnerNode dstNode = dst.traverseChild(key, innerNodeVisitor());
+
+ // "dstNode" is guaranteed to not be null even if "src" and "dst" match.
+ // Null in "srcNode" means that we should initialize everything that we can in "dstNode"
+ // unconditionally. It's only possible if we pass it as a source as well.
+ addDefaults(srcNode == null ? dstNode : srcNode, dstNode);
+
+ return null;
+ }
+
+ @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> srcNamedList) {
+ // Here we don't need to preemptively initialise corresponsing field, because it can never be null.
+ NamedListNode<?> dstNamedList = dst.traverseChild(key, namedListNodeVisitor());
+
+ for (String namedListKey : srcNamedList.namedListKeys()) {
+ // But, in order to get non-null value from "dstNamedList.get(namedListKey)" we must explicitly
+ // ensure its existance.
+ dstNamedList.construct(namedListKey, new ConfigurationSource() {});
+
+ addDefaults(srcNamedList.get(namedListKey), dstNamedList.get(namedListKey));
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * @return Visitor that returns leaf value or {@code null} if node is not a leaf.
+ */
+ public static ConfigurationVisitor<Serializable> leafNodeVisitor() {
+ return new ConfigurationVisitor<>() {
+ @Override public Serializable visitLeafNode(String key, Serializable val) {
+ return val;
+ }
+ };
+ }
+
+ /**
+ * @return Visitor that returns inner node or {@code null} if node is not an inner node.
+ */
+ public static ConfigurationVisitor<InnerNode> innerNodeVisitor() {
+ return new ConfigurationVisitor<>() {
+ @Override public InnerNode visitInnerNode(String key, InnerNode node) {
+ return node;
+ }
+ };
+ }
+
+ /**
+ * @return Visitor that returns named list node or {@code null} if node is not a named list node.
+ */
+ public static ConfigurationVisitor<NamedListNode<?>> namedListNodeVisitor() {
+ return new ConfigurationVisitor<>() {
+ @Override
+ public <N extends InnerNode> NamedListNode<?> visitNamedListNode(String key, NamedListNode<N> node) {
+ return node;
+ }
+ };
+ }
+
+ /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
private static class PatchLeafConfigurationSource implements ConfigurationSource {
/** */
private final Serializable val;
@@ -481,7 +525,7 @@ public class ConfigurationUtil {
}
}
- /** */
+ /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
private static class PatchInnerConfigurationSource implements ConfigurationSource {
/** */
private final InnerNode srcNode;
@@ -527,7 +571,7 @@ public class ConfigurationUtil {
}
}
- /** */
+ /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
private static class PatchNamedListConfigurationSource implements ConfigurationSource {
/** */
private final NamedListNode<?> srcNode;
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/KeysTrackingConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/KeysTrackingConfigurationVisitor.java
new file mode 100644
index 0000000..76e1b8c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/KeysTrackingConfigurationVisitor.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+
+/** Visitor that accumulates keys while descending. */
+public abstract class KeysTrackingConfigurationVisitor<T> implements ConfigurationVisitor<T> {
+ /** Current key, aggregated by visitor. */
+ private StringBuilder currentKey = new StringBuilder();
+
+ /** Current keys list, almost the same as {@link #currentKey}. */
+ private List<String> currentPath = new ArrayList<>();
+
+ /** {@inheritDoc} */
+ @Override public final T visitLeafNode(String key, Serializable val) {
+ int prevPos = startVisit(key, false, true);
+
+ try {
+ return doVisitLeafNode(key, val);
+ }
+ finally {
+ endVisit(prevPos);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public final T visitInnerNode(String key, InnerNode node) {
+ int prevPos = startVisit(key, false, false);
+
+ try {
+ return doVisitInnerNode(key, node);
+ }
+ finally {
+ endVisit(prevPos);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public final <N extends InnerNode> T visitNamedListNode(String key, NamedListNode<N> node) {
+ int prevPos = startVisit(key, false, false);
+
+ try {
+ return doVisitNamedListNode(key, node);
+ }
+ finally {
+ endVisit(prevPos);
+ }
+ }
+
+ /** To be used instead of {@link ConfigurationVisitor#visitLeafNode(String, Serializable)}. */
+ protected T doVisitLeafNode(String key, Serializable val) {
+ return null;
+ }
+
+ /** To be used instead of {@link ConfigurationVisitor#visitInnerNode(String, InnerNode)}. */
+ protected T doVisitInnerNode(String key, InnerNode node) {
+ node.traverseChildren(this);
+
+ return null;
+ }
+
+ /** To be used instead of {@link ConfigurationVisitor#visitNamedListNode(String, NamedListNode)}. */
+ protected <N extends InnerNode> T doVisitNamedListNode(String key, NamedListNode<N> node) {
+ for (String namedListKey : node.namedListKeys()) {
+ int prevPos = startVisit(namedListKey, true, false);
+
+ try {
+ doVisitInnerNode(namedListKey, node.get(namedListKey));
+ }
+ finally {
+ endVisit(prevPos);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Tracks passed key to reflect it in {@link #currentKey()} and {@link #currentPath()}.
+ *
+ * @param key Key itself.
+ * @param escape Whether the key needs escaping or not.
+ * @param leaf Add dot at the end of {@link #currentKey()} if {@code leaf} is {@code false}.
+ * @param closure Closure to execute when {@link #currentKey()} and {@link #currentPath()} have updated values.
+ */
+ protected final T withTracking(String key, boolean escape, boolean leaf, Supplier<T> closure) {
+ int prevPos = startVisit(key, escape, leaf);
+
+ try {
+ return closure.get();
+ }
+ finally {
+ endVisit(prevPos);
+ }
+ }
+
+ /**
+ * @return Current key, with a dot at the end if it's not a leaf.
+ */
+ protected final String currentKey() {
+ return currentKey.toString();
+ }
+
+ /**
+ * @return List representation of the current key.
+ */
+ protected final List<String> currentPath() {
+ return Collections.unmodifiableList(currentPath);
+ }
+
+ /**
+ * Prepares values of {@link #currentKey} and {@link #currentPath} for further processing.
+ *
+ * @param key Key.
+ * @param escape Whether we need to escape the key before appending it to {@link #currentKey}.
+ * @return Previous length of {@link #currentKey} so it can be passed to {@link #endVisit(int)} later.
+ */
+ private int startVisit(String key, boolean escape, boolean leaf) {
+ int previousKeyLength = currentKey.length();
+
+ currentKey.append(escape ? ConfigurationUtil.escape(key) : key);
+
+ if (!leaf)
+ currentKey.append('.');
+
+ currentPath.add(key);
+
+ return previousKeyLength;
+ }
+
+ /**
+ * Puts {@link #currentKey} and {@link #currentPath} in the same state as they were before
+ * {@link #startVisit(String, boolean)}.
+ *
+ * @param previousKeyLength Value return by corresponding {@link #startVisit(String, boolean)} invocation.
+ */
+ private void endVisit(int previousKeyLength) {
+ currentKey.setLength(previousKeyLength);
+
+ currentPath.remove(currentPath.size() - 1);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
index 60620fe..48ff454 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
@@ -17,28 +17,21 @@
package org.apache.ignite.configuration.internal.validation;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
+import javax.validation.constraints.Max;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
/**
* Validate that field value is not greater than some maximum value.
- *
- * @param <C> Root configuration type.
*/
-public class MaxValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Number, C> {
- /** Maximum value. */
- private final long maxValue;
-
- /** Constructor. */
- public MaxValidator(long maxValue, String message) {
- super(message);
- this.maxValue = maxValue;
- }
-
+public class MaxValidator implements Validator<Max, Number> {
/** {@inheritDoc} */
- @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
- if (value.longValue() > maxValue)
- throw new ConfigurationValidationException(message);
+ @Override public void validate(Max annotation, ValidationContext<Number> ctx) {
+ if (ctx.getNewValue().longValue() > annotation.value()) {
+ ctx.addIssue(new ValidationIssue(
+ "Configuration value '" + ctx.currentKey() + "' must not be greater than " + annotation.value()
+ ));
+ }
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
index b94e65f..ba01c61 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
@@ -17,28 +17,21 @@
package org.apache.ignite.configuration.internal.validation;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
+import javax.validation.constraints.Min;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
/**
* Validate that field value is not less than some minimal value.
- *
- * @param <C> Root configuration type.
*/
-public class MinValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Number, C> {
- /** Minimal value. */
- private final long minValue;
-
- /** Constructor. */
- public MinValidator(long minValue, String message) {
- super(message);
- this.minValue = minValue;
- }
-
+public class MinValidator implements Validator<Min, Number> {
/** {@inheritDoc} */
- @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
- if (value.longValue() < minValue)
- throw new ConfigurationValidationException(message);
+ @Override public void validate(Min annotation, ValidationContext<Number> ctx) {
+ if (ctx.getNewValue().longValue() < annotation.value()) {
+ ctx.addIssue(new ValidationIssue(
+ "Configuration value '" + ctx.currentKey() + "' must not be less than " + annotation.value()
+ ));
+ }
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java
deleted file mode 100644
index f060f53..0000000
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java
+++ /dev/null
@@ -1,41 +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.internal.validation;
-
-import java.io.Serializable;
-import org.apache.ignite.configuration.internal.DynamicConfiguration;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.configuration.validation.FieldValidator;
-
-/**
- * Validate that field value is not null.
- *
- * @param <C> Root configuration type.
- */
-public class NotNullValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Serializable, C> {
- /** Constructor. */
- public NotNullValidator(String message) {
- super(message);
- }
-
- /** {@inheritDoc} */
- @Override public void validate(Serializable value, C newRoot, C oldRoot) throws ConfigurationValidationException {
- if (value == null)
- throw new ConfigurationValidationException("");
- }
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationContextImpl.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationContextImpl.java
new file mode 100644
index 0000000..ffb5aca
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationContextImpl.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.validation;
+
+import java.util.List;
+import java.util.function.Function;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.find;
+
+/**
+ * Validation context implementation.
+ */
+class ValidationContextImpl<VIEW> implements ValidationContext<VIEW> {
+ /** Cached storage roots with the current version of data. */
+ private final SuperRoot oldRoots;
+
+ /** Updated values that need to be validated. */
+ private final SuperRoot newRoots;
+
+ /** Provider for arbitrary roots that might not be accociated with the same storage. */
+ private final Function<RootKey<?, ?>, InnerNode> otherRoots;
+
+ /**
+ * Current node/configuration value.
+ *
+ * @see #getNewValue()
+ */
+ private final VIEW val;
+
+ /** */
+ private final String currentKey;
+
+ /** */
+ private final List<String> currentPath;
+
+ /** */
+ private final List<ValidationIssue> issues;
+
+ /**
+ * Constructor.
+ * @param oldRoots Old roots.
+ * @param newRoots New roots.
+ * @param otherRoots Provider for arbitrary roots that might not be accociated with the same storage.
+ * @param val New value of currently validated configuration.
+ * @param currentKey Key corresponding to the value.
+ * @param currentPath List representation of {@code currentKey}.
+ * @param issues List of issues, should be used as a write-only collection.
+ */
+ ValidationContextImpl(
+ SuperRoot oldRoots,
+ SuperRoot newRoots,
+ Function<RootKey<?, ?>, InnerNode> otherRoots,
+ VIEW val,
+ String currentKey,
+ List<String> currentPath,
+ List<ValidationIssue> issues
+ ) {
+ this.oldRoots = oldRoots;
+ this.newRoots = newRoots;
+ this.otherRoots = otherRoots;
+ this.val = val;
+ this.currentKey = currentKey;
+ this.currentPath = currentPath;
+ this.issues = issues;
+
+ assert !currentPath.isEmpty();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String currentKey() {
+ return currentKey;
+ }
+
+ /** {@inheritDoc} */
+ @Override public VIEW getOldValue() {
+ return (VIEW)find(currentPath, oldRoots);
+ }
+
+ /** {@inheritDoc} */
+ @Override public VIEW getNewValue() {
+ return val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <ROOT> ROOT getOldRoot(RootKey<?, ROOT> rootKey) {
+ InnerNode root = oldRoots.getRoot(rootKey);
+
+ return (ROOT)(root == null ? otherRoots.apply(rootKey) : root);
+ }
+
+ /** {@inheritDoc} */
+ @Override public <ROOT> ROOT getNewRoot(RootKey<?, ROOT> rootKey) {
+ TraversableTreeNode root = newRoots.getRoot(rootKey);
+
+ return (ROOT)(root == null ? otherRoots.apply(rootKey) : root);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void addIssue(ValidationIssue issue) {
+ issues.add(issue);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationUtil.java
new file mode 100644
index 0000000..b258b06
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/ValidationUtil.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.validation;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.util.AnyNodeConfigurationVisitor;
+import org.apache.ignite.configuration.internal.util.KeysTrackingConfigurationVisitor;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
+
+import static java.util.Collections.emptySet;
+import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.appendKey;
+
+/** */
+public class ValidationUtil {
+ /**
+ * Validate configuration changes.
+ *
+ * @param oldRoots Old known roots.
+ * @param newRoots New roots.
+ * @param otherRoots Provider for arbitrary roots that might not be accociated with the same storage.
+ * @param memberAnnotationsCache Mutable map that contains annotations associated with corresponding member keys.
+ * @param validators Current validators map to look into.
+ * @return List of validation results.
+ */
+ public static List<ValidationIssue> validate(
+ SuperRoot oldRoots,
+ SuperRoot newRoots,
+ Function<RootKey<?, ?>, InnerNode> otherRoots,
+ Map<MemberKey, Annotation[]> memberAnnotationsCache,
+ Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators
+ ) {
+ List<ValidationIssue> issues = new ArrayList<>();
+
+ newRoots.traverseChildren(new KeysTrackingConfigurationVisitor<>() {
+ /** {@inheritDoc} */
+ @Override protected Object doVisitInnerNode(String key, InnerNode innerNode) {
+ assert innerNode != null;
+
+ innerNode.traverseChildren(new AnyNodeConfigurationVisitor<Void>() {
+ @Override protected Void visitNode(String key, Object node) {
+ validate(innerNode, key, node);
+
+ return null;
+ }
+ });
+
+ return super.doVisitInnerNode(key, innerNode);
+ }
+
+ /**
+ * Perform validation on the node's subnode.
+ *
+ * @param lastInnerNode Inner node that contains validated field.
+ * @param fieldName Name of the field.
+ * @param val Value of the field.
+ */
+ private void validate(InnerNode lastInnerNode, String fieldName, Object val) {
+ if (val == null) {
+ String message = "'" + (currentKey() + fieldName) + "' configuration value is not initialized.";
+
+ issues.add(new ValidationIssue(message));
+
+ return;
+ }
+
+ MemberKey memberKey = new MemberKey(lastInnerNode.getClass(), fieldName);
+
+ Annotation[] fieldAnnotations = memberAnnotationsCache.computeIfAbsent(memberKey, k -> {
+ try {
+ Field field = lastInnerNode.schemaType().getDeclaredField(fieldName);
+
+ return field.getDeclaredAnnotations();
+ }
+ catch (NoSuchFieldException e) {
+ // Should be impossible.
+ return new Annotation[0];
+ }
+ });
+
+ if (fieldAnnotations.length == 0)
+ return;
+
+ String currentKey = currentKey() + fieldName;
+ List<String> currentPath = appendKey(currentPath(), fieldName);
+
+ for (Annotation annotation : fieldAnnotations) {
+ for (Validator<?, ?> validator : validators.getOrDefault(annotation.annotationType(), emptySet())) {
+ // Making this a compile-time check would be too expensive to implement.
+ assert assertValidatorTypesCoherence(validator.getClass(), annotation.annotationType(), val)
+ : "Validator coherence is violated [" +
+ "class=" + lastInnerNode.getClass().getCanonicalName() + ", " +
+ "field=" + fieldName + ", " +
+ "annotation=" + annotation.annotationType().getCanonicalName() + ", " +
+ "validator=" + validator.getClass().getName() + ']';
+
+ ValidationContextImpl<Object> ctx = new ValidationContextImpl<>(
+ oldRoots,
+ newRoots,
+ otherRoots,
+ val,
+ currentKey,
+ currentPath,
+ issues
+ );
+
+ ((Validator<Annotation, Object>)validator).validate(annotation, ctx);
+ }
+ }
+ }
+ });
+
+ return issues;
+ }
+
+ /** */
+ private static boolean assertValidatorTypesCoherence(
+ Class<?> validatorClass,
+ Class<? extends Annotation> annotationType,
+ Object val
+ ) {
+ // Find superclass that directly extends Validator.
+ if (!Arrays.asList(validatorClass.getInterfaces()).contains(Validator.class))
+ return assertValidatorTypesCoherence(validatorClass.getSuperclass(), annotationType, val);
+
+ Type genericSuperClass = Arrays.stream(validatorClass.getGenericInterfaces())
+ .filter(i -> i instanceof ParameterizedType && ((ParameterizedType)i).getRawType() == Validator.class)
+ .findAny()
+ .get();
+
+ if (!(genericSuperClass instanceof ParameterizedType))
+ return false;
+
+ ParameterizedType parameterizedSuperClass = (ParameterizedType)genericSuperClass;
+
+ Type[] actualTypeParameters = parameterizedSuperClass.getActualTypeArguments();
+
+ if (actualTypeParameters.length != 2)
+ return false;
+
+ if (actualTypeParameters[0] != annotationType)
+ return false;
+
+ Type sndParam = actualTypeParameters[1];
+
+ if (sndParam instanceof ParameterizedType)
+ sndParam = ((ParameterizedType)sndParam).getRawType();
+
+ return (sndParam instanceof Class) && (val == null || ((Class<?>)sndParam).isInstance(val));
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
index 2584006..44b77d8 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
@@ -32,15 +32,6 @@ public interface ConstructableTreeNode {
void construct(String key,/* boolean canMutate, */ ConfigurationSource src) throws NoSuchElementException;
/**
- * Assigns default value to the corresponding leaf. Defaults are gathered from configuration schema class.
- *
- * @param key Field name to be initialized.
- * @return {@code true} if default value has been assigned, {@code false} otherwise.
- * @throws NoSuchElementException If there's no such field or it is not a leaf value.
- */
- boolean constructDefault(String key) throws NoSuchElementException;
-
- /**
* Public equivalent of {@link Object#clone()} method. Creates a copy with effectively the same content.
* Helps to preserve trees immutability after construction is completed.
*
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 af8cf28..c898945 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
@@ -110,7 +110,18 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
*/
@Override public abstract void construct(String key, ConfigurationSource src) throws NoSuchElementException;
- /** */
+ /**
+ * Assigns default value to the corresponding leaf. Defaults are gathered from configuration schema class.
+ *
+ * @param fieldName Field name to be initialized.
+ * @return {@code true} if default value has been assigned, {@code false} otherwise.
+ * @throws NoSuchElementException If there's no such field or it is not a leaf value.
+ */
+ public abstract boolean constructDefault(String fieldName) throws NoSuchElementException;
+
+ /**
+ * @return Class of corresponding configuration schema.
+ */
public abstract Class<?> schemaType();
/** {@inheritDoc} */
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 609abfa..530544c 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
@@ -124,11 +124,6 @@ public final class NamedListNode<N extends InnerNode> implements NamedListView<N
}
/** {@inheritDoc} */
- @Override public boolean constructDefault(String key) {
- return false;
- }
-
- /** {@inheritDoc} */
@Override public NamedListNode<N> copy() {
return new NamedListNode<>(this);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
index 73af1f2..4ec7e05 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
@@ -32,6 +32,8 @@ public class ConfigurationValidationException extends RuntimeException {
}
public ConfigurationValidationException(List<ValidationIssue> issues) {
+ super(issues.toString());
+
this.issues = issues;
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java
deleted file mode 100644
index 6705cf6..0000000
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java
+++ /dev/null
@@ -1,46 +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.validation;
-
-import java.io.Serializable;
-import org.apache.ignite.configuration.ConfigurationTree;
-
-/**
- * Base class for field validator. Contains exception message.
- * @param <T> Field type.
- * @param <C> Root configuration type.
- */
-public abstract class FieldValidator<T extends Serializable, C extends ConfigurationTree<?, ?>> {
- /** Validation error message. */
- protected final String message;
-
- /** Constructor. */
- protected FieldValidator(String message) {
- this.message = message;
- }
-
- /**
- * Validate field.
- *
- * @param value New value.
- * @param newRoot New configuration root.
- * @param oldRoot Old configuration root.
- * @throws ConfigurationValidationException If validation failed.
- */
- public abstract void validate(T value, C newRoot, C oldRoot) throws ConfigurationValidationException;
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationContext.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationContext.java
new file mode 100644
index 0000000..6f9d84f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationContext.java
@@ -0,0 +1,67 @@
+/*
+ * 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.validation;
+
+import java.lang.annotation.Annotation;
+import org.apache.ignite.configuration.RootKey;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Validation context for the validators.
+ *
+ * @see Validator#validate(Annotation, ValidationContext)
+ */
+public interface ValidationContext<VIEW> {
+ /** @return String representation of currently validated value, i.e. {@code root.config.node} */
+ String currentKey();
+
+ /** @return Previous value of the configuration. Might be null for leaves only. */
+ @Nullable VIEW getOldValue();
+
+ /** @return Updated value of the configuration. Cannot be null. */
+ @NotNull VIEW getNewValue();
+
+ /**
+ * @param rootKey Root key.
+ * @return Configuration root view before updates. Guaranteed to return valid value only if root belongs to the same
+ * storage as currently validated value. Otherwise result of the method may very between invocations or even
+ * be {@code null} if corresponding storage is not initialized.
+ *
+ * @param <ROOT> Root view type derived from the root key.
+ */
+ @Nullable <ROOT> ROOT getOldRoot(RootKey<?, ROOT> rootKey);
+
+ /**
+ * @param rootKey Root key.
+ * @return Configuration root view after updates. Guaranteed to return valid value only if root belongs to the same
+ * storage as currently validated value. Otherwise result of the method may very between invocations or even
+ * be {@code null} if corresponding storage is not initialized.
+ *
+ * @param <ROOT> Root view type derived from the root key.
+ */
+ @Nullable <ROOT> ROOT getNewRoot(RootKey<?, ROOT> rootKey);
+
+ /**
+ * Signifies that there's something wrong. Values will be accumulated and passed to the user later.
+ *
+ * @param issue Validation issue object.
+ * @see ConfigurationValidationException
+ */
+ void addIssue(ValidationIssue issue);
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
index 4594a53..28dd677 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ValidationIssue.java
@@ -16,5 +16,23 @@
*/
package org.apache.ignite.configuration.validation;
+/** */
public class ValidationIssue {
+ /** */
+ private String message;
+
+ /** */
+ public ValidationIssue(String message) {
+ this.message = message;
+ }
+
+ /** */
+ public String message() {
+ return message;
+ }
+
+ /** */
+ @Override public String toString() {
+ return "ValidationIssue [message=" + message + ']';
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/Validator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/Validator.java
new file mode 100644
index 0000000..47b57ab
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/Validator.java
@@ -0,0 +1,40 @@
+/*
+ * 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.validation;
+
+import java.lang.annotation.Annotation;
+import org.apache.ignite.configuration.ConfigurationRegistry;
+
+/**
+ * Interface for all configuration validators. Recommended to be a stateless class.
+ *
+ * It is mandatory that all direct implementations of the interface explicitly specify types {@code A} and {@code VIEW}.
+ *
+ * @param <A> Type of the annotation that puts current validator to the field.
+ * @param <VIEW> Upper bound for field types that can be validated with this validator.
+ * @see ConfigurationRegistry#registerValidator(Class, Validator)
+ */
+public interface Validator<A extends Annotation, VIEW> {
+ /**
+ * Perform validation. All validation issues must be put into {@link ValidationContext#addIssue(ValidationIssue)}.
+ *
+ * @param annotation Specific annotation from currently validated value.
+ * @param ctx Validation context.
+ */
+ void validate(A annotation, ValidationContext<VIEW> ctx);
+}
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 c3a8326..5d6a270 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
@@ -23,7 +23,7 @@ import java.io.Reader;
import java.util.Collections;
import org.apache.ignite.configuration.ConfigurationRegistry;
import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.rest.configuration.RestConfigurationImpl;
+import org.apache.ignite.rest.configuration.RestConfiguration;
import org.apache.ignite.rest.presentation.ConfigurationPresentation;
import org.apache.ignite.rest.presentation.FormatConverter;
import org.apache.ignite.rest.presentation.json.JsonConverter;
@@ -128,8 +128,8 @@ public class RestModule {
/** */
private Javalin startRestEndpoint() {
- Integer port = sysConf.getConfiguration(RestConfigurationImpl.KEY).port().value();
- Integer portRange = sysConf.getConfiguration(RestConfigurationImpl.KEY).portRange().value();
+ Integer port = sysConf.getConfiguration(RestConfiguration.KEY).port().value();
+ Integer portRange = sysConf.getConfiguration(RestConfiguration.KEY).portRange().value();
Javalin app = null;
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 15b46fe..46de08b 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
@@ -17,8 +17,8 @@
package org.apache.ignite.rest.presentation.json;
+import java.util.Collections;
import java.util.Map;
-import java.util.stream.Collectors;
import org.apache.ignite.configuration.Configurator;
import org.apache.ignite.configuration.internal.DynamicConfiguration;
import org.apache.ignite.rest.presentation.ConfigurationPresentation;
@@ -38,10 +38,11 @@ public class JsonPresentation implements ConfigurationPresentation<String> {
/** {@inheritDoc} */
@Override public String represent() {
- Map<String, ?> preparedMap = configsMap.entrySet().stream().collect(Collectors.toMap(
- e -> e.getKey(),
- e -> e.getValue().getRoot().value()
- ));
+ Map<String, ?> preparedMap = Collections.emptyMap();
+// configsMap.entrySet().stream().collect(Collectors.toMap(
+// e -> e.getKey(),
+// e -> e.getValue().getRoot().value()
+// ));
return converter.convertTo(preparedMap);
}
diff --git a/parent/pom.xml b/parent/pom.xml
index 0c321b5..3eeee99 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -71,7 +71,7 @@
<mockito.junit.jupiter.version>3.3.3</mockito.junit.jupiter.version>
<picocli.version>4.5.2</picocli.version>
<slf4j.version>1.7.30</slf4j.version>
- <spoon.framework.version>8.3.0</spoon.framework.version>
+ <spoon.framework.version>8.4.0-beta-18</spoon.framework.version>
<typesafe.version>1.4.1</typesafe.version>
<hamcrest.version>2.2</hamcrest.version>
<scalecube.version>2.6.6</scalecube.version>