This is an automated email from the ASF dual-hosted git repository.
apolovtsev pushed a commit to branch ignite-3.0
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/ignite-3.0 by this push:
new 8bfe47f2cf IGNITE-24014 Introduce a way to have key-value entries in
configuration (#4997)
8bfe47f2cf is described below
commit 8bfe47f2cfbae272c95d060a54004909da33b3d1
Author: Alexander Polovtcev <[email protected]>
AuthorDate: Fri Jan 10 17:24:32 2025 +0200
IGNITE-24014 Introduce a way to have key-value entries in configuration
(#4997)
(cherry picked from commit 7eb75ec7088ac3bcdb9e9cc8e8b1aea5b632612d)
---
examples/config/ignite-config.conf | 4 +-
.../management/topology/ItLogicalTopologyTest.java | 2 +-
.../NodeAttributeConfigurationSchema.java | 4 +-
.../processor/ItConfigurationProcessorTest.java | 60 +++++++
.../MultipleInjectedValuesConfigurationSchema.java | 30 ++++
.../UnsupportedFieldTypeConfigurationSchema.java | 27 +++
.../injectedvalue/ValidConfigurationSchema.java} | 19 +-
.../ValueAndInjectedValueConfigurationSchema.java} | 26 ++-
.../processor/ConfigurationProcessor.java | 71 ++++----
.../processor/ConfigurationProcessorUtils.java | 22 ++-
.../validators/InjectedValueValidator.java | 101 +++++++++++
.../configuration/annotation/InjectedValue.java | 79 +++++++++
.../SystemPropertyConfigurationSchema.java | 4 +-
...DistributedConfigurationPropertyHolderTest.java | 3 +-
.../asm/ConfigurationImplAsmGenerator.java | 3 +-
.../configuration/asm/InnerNodeAsmGenerator.java | 26 ++-
.../hocon/HoconListConfigurationSource.java | 44 ++---
.../hocon/HoconObjectConfigurationSource.java | 92 +++++-----
.../hocon/HoconPrimitiveConfigurationSource.java | 14 +-
.../configuration/tree/ConstructableTreeNode.java | 9 +
.../configuration/tree/ConverterToMapVisitor.java | 71 ++++++--
.../configuration/util/ConfigurationUtil.java | 23 ++-
.../InjectedValueConfigurationTest.java | 194 +++++++++++++++++++++
.../configuration/hocon/HoconConverterTest.java | 2 +-
.../ItDistributionZonesFiltersTest.java | 20 +--
.../rebalance/ItRebalanceTriggersRecoveryTest.java | 4 +-
.../BaseDistributionZoneManagerTest.java | 4 +-
modules/distribution-zones/tech-notes/filters.md | 15 +-
.../metastorage/TestMetasStorageUtils.java | 4 +-
...aStorageCompactionTriggerConfigurationTest.java | 4 +-
.../replicator/ItReplicaLifecycleTest.java | 6 +-
.../runner/app/ItIgniteNodeRestartTest.java | 4 +-
.../runner/app/ItReplicaStateManagerTest.java | 6 +-
.../sql/engine/ItUnstableTopologyTest.java | 2 +-
...ilablePartitionsRecoveryByFilterUpdateTest.java | 6 +-
35 files changed, 793 insertions(+), 212 deletions(-)
diff --git a/examples/config/ignite-config.conf
b/examples/config/ignite-config.conf
index adc047156a..6f95edcdcb 100644
--- a/examples/config/ignite-config.conf
+++ b/examples/config/ignite-config.conf
@@ -24,7 +24,7 @@ ignite {
]
}
nodeAttributes.nodeAttributes {
- region.attribute = US
- storage.attribute = SSD
+ region = US
+ storage = SSD
}
}
diff --git
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
index 3a7593d302..97e3816272 100644
---
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
+++
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
@@ -67,7 +67,7 @@ class ItLogicalTopologyTest extends
ClusterPerTestIntegrationTest {
+ " port: {},\n"
+ " nodeFinder.netClusterNodes: [ {} ]\n"
+ " },\n"
- + " nodeAttributes.nodeAttributes: {region.attribute = US,
storage.attribute = SSD},\n"
+ + " nodeAttributes.nodeAttributes: {region = US, storage =
SSD},\n"
+ " storage.profiles: {lru_rocks.engine = rocksdb,
segmented_aipersist.engine = aipersist},\n"
+ " clientConnector.port: {},\n"
+ " rest.port: {},\n"
diff --git
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
index cb52ebf3a2..3dc822dc4a 100644
---
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
@@ -19,7 +19,7 @@ package
org.apache.ignite.internal.cluster.management.configuration;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
/**
* Node's attribute configuration schema. User can specify any number of pairs
(key, attribute) for a node through the local configuration
@@ -35,6 +35,6 @@ public class NodeAttributeConfigurationSchema {
public String name;
/** Node attribute field. */
- @Value(hasDefault = true)
+ @InjectedValue(hasDefault = true)
public String attribute = "";
}
diff --git
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
index b47f017e45..ce2f61768c 100644
---
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
+++
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
@@ -615,6 +615,66 @@ public class ItConfigurationProcessorTest extends
AbstractProcessorTest {
configRootConfigurationInterfaceContent.contains("extends " +
getConfigurationInterfaceName(abstractRootConfigSchema).simpleName());
}
+ @Test
+ void testSuccessInjectedValueFieldCodeGeneration() {
+ String packageName =
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+ ClassName cls0 = ClassName.get(packageName,
"ValidConfigurationSchema");
+
+ BatchCompilation batchCompile = batchCompile(cls0);
+
+
assertThat(batchCompile.getCompilationStatus()).succeededWithoutWarnings();
+
+ assertEquals(3, batchCompile.generated().size());
+
+ assertTrue(batchCompile.getBySchema(cls0).allGenerated());
+ }
+
+ @Test
+ void testMultipleInjectedValuesUnsuccessfulGeneration() {
+ String packageName =
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+ ClassName cls0 = ClassName.get(packageName,
"MultipleInjectedValuesConfigurationSchema");
+
+ assertThrowsEx(
+ IllegalStateException.class,
+ () -> batchCompile(cls0),
+ "Field marked as @InjectedValue must be the only \"value\"
field in the schema "
+ +
"org.apache.ignite.internal.configuration.processor.injectedvalue.MultipleInjectedValuesConfigurationSchema,
"
+ + "found: [firstValue, secondValue]"
+ );
+ }
+
+ @Test
+ void testValuesAndInjectedValueUnsuccessfulGeneration() {
+ String packageName =
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+ ClassName cls0 = ClassName.get(packageName,
"ValueAndInjectedValueConfigurationSchema");
+
+ assertThrowsEx(
+ IllegalStateException.class,
+ () -> batchCompile(cls0),
+ "Field marked as @InjectedValue must be the only \"value\"
field in the schema "
+ +
"org.apache.ignite.internal.configuration.processor.injectedvalue.ValueAndInjectedValueConfigurationSchema,
"
+ + "found: [secondValue, firstValue, thirdValue]"
+ );
+ }
+
+ @Test
+ void testUnsupportedFieldTypeUnsuccessfulGeneration() {
+ String packageName =
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+ ClassName cls0 = ClassName.get(packageName,
"UnsupportedFieldTypeConfigurationSchema");
+
+ assertThrowsEx(
+ IllegalStateException.class,
+ () -> batchCompile(cls0),
+
"org.apache.ignite.internal.configuration.processor.injectedvalue.UnsupportedFieldTypeConfigurationSchema.firstValue
"
+ + "field must have one of the following types:
boolean, int, long, double, String, UUID "
+ + "or an array of aforementioned type."
+ );
+ }
+
/**
* Compile set of classes.
*
diff --git
a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
new file mode 100644
index 0000000000..02d7994e83
--- /dev/null
+++
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
@@ -0,0 +1,30 @@
+/*
+ * 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.internal.configuration.processor.injectedvalue;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+
+@Config
+public class MultipleInjectedValuesConfigurationSchema {
+ @InjectedValue
+ public String firstValue;
+
+ @InjectedValue
+ public String secondValue;
+}
diff --git
a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
new file mode 100644
index 0000000000..e869d08f03
--- /dev/null
+++
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.configuration.processor.injectedvalue;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+
+@Config
+public class UnsupportedFieldTypeConfigurationSchema {
+ @InjectedValue
+ public Object firstValue;
+}
diff --git
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
similarity index 54%
copy from
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
copy to
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
index cb52ebf3a2..0698a0768a 100644
---
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
@@ -15,26 +15,17 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cluster.management.configuration;
+package org.apache.ignite.internal.configuration.processor.injectedvalue;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
-/**
- * Node's attribute configuration schema. User can specify any number of pairs
(key, attribute) for a node through the local configuration
- * of a node, and also can specify a filter through the SQL syntax of
distribution zones. Any time data nodes will be recalculated,
- * corresponding filter will be applied to set of nodes with pre-defined
attributes.
- *
- * @see <a
href="https://github.com/apache/ignite-3/blob/main/modules/distribution-zones/tech-notes/filters.md">Filter
documentation</a>
- */
@Config
-public class NodeAttributeConfigurationSchema {
- /** Name of the node attribute. */
+public class ValidConfigurationSchema {
@InjectedName
public String name;
- /** Node attribute field. */
- @Value(hasDefault = true)
- public String attribute = "";
+ @InjectedValue
+ public String someValue;
}
diff --git
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
similarity index 51%
copy from
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
copy to
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
index cb52ebf3a2..f1e42d0356 100644
---
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
@@ -15,26 +15,20 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cluster.management.configuration;
+package org.apache.ignite.internal.configuration.processor.injectedvalue;
import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
import org.apache.ignite.configuration.annotation.Value;
-/**
- * Node's attribute configuration schema. User can specify any number of pairs
(key, attribute) for a node through the local configuration
- * of a node, and also can specify a filter through the SQL syntax of
distribution zones. Any time data nodes will be recalculated,
- * corresponding filter will be applied to set of nodes with pre-defined
attributes.
- *
- * @see <a
href="https://github.com/apache/ignite-3/blob/main/modules/distribution-zones/tech-notes/filters.md">Filter
documentation</a>
- */
@Config
-public class NodeAttributeConfigurationSchema {
- /** Name of the node attribute. */
- @InjectedName
- public String name;
+public class ValueAndInjectedValueConfigurationSchema {
+ @Value
+ public String firstValue;
+
+ @InjectedValue
+ public String secondValue;
- /** Node attribute field. */
- @Value(hasDefault = true)
- public String attribute = "";
+ @Value
+ public String thirdValue;
}
diff --git
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
index 76e630a6b7..3dc7f7007a 100644
---
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
+++
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
@@ -24,6 +24,7 @@ import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.collectFieldsWithAnnotation;
+import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.containsAnyAnnotation;
import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.findFirstPresentAnnotation;
import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.getChangeName;
import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.getConfigurationInterfaceName;
@@ -80,6 +81,7 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationExtension;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
import org.apache.ignite.configuration.annotation.InternalId;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.PolymorphicConfig;
@@ -87,6 +89,7 @@ import
org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Secret;
import org.apache.ignite.configuration.annotation.Value;
+import
org.apache.ignite.internal.configuration.processor.validators.InjectedValueValidator;
import org.jetbrains.annotations.Nullable;
/**
@@ -153,12 +156,16 @@ public class ConfigurationProcessor extends
AbstractProcessor {
return false;
}
+ var injectedValueValidator = new InjectedValueValidator(processingEnv);
+
for (TypeElement clazz : annotatedConfigs) {
// Find all the fields of the schema.
List<VariableElement> fields = fields(clazz);
validateConfigurationSchemaClass(clazz, fields);
+ injectedValueValidator.validate(clazz, fields);
+
// Get package name of the schema class
String packageName =
elementUtils.getPackageOf(clazz).getQualifiedName().toString();
@@ -175,10 +182,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
throw new ConfigurationProcessorException("Field " +
clazz.getQualifiedName() + "." + field + " must be public");
}
- final String fieldName = field.getSimpleName().toString();
-
- // Get configuration types (VIEW, CHANGE and so on)
- final TypeName interfaceGetMethodType =
getInterfaceGetMethodType(field);
+ String fieldName = field.getSimpleName().toString();
if (field.getAnnotation(ConfigValue.class) != null) {
checkConfigField(field, ConfigValue.class);
@@ -190,8 +194,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
checkConfigField(field, NamedConfigValue.class);
}
- Value valueAnnotation = field.getAnnotation(Value.class);
- if (valueAnnotation != null) {
+ if (field.getAnnotation(Value.class) != null) {
// Must be a primitive or an array of the primitives
(including java.lang.String, java.util.UUID).
if (!isValidValueAnnotationFieldType(field.asType())) {
throw new
ConfigurationProcessorException(String.format(
@@ -204,8 +207,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
}
}
- PolymorphicId polymorphicId =
field.getAnnotation(PolymorphicId.class);
- if (polymorphicId != null) {
+ if (field.getAnnotation(PolymorphicId.class) != null) {
if (!isClass(field.asType(), String.class)) {
throw new
ConfigurationProcessorException(String.format(
FIELD_MUST_BE_SPECIFIC_CLASS_ERROR_FORMAT,
@@ -237,6 +239,9 @@ public class ConfigurationProcessor extends
AbstractProcessor {
}
}
+ // Get configuration types (VIEW, CHANGE and so on)
+ TypeName interfaceGetMethodType =
getInterfaceGetMethodType(field);
+
createGetters(configurationInterfaceBuilder, fieldName,
interfaceGetMethodType);
}
@@ -355,13 +360,11 @@ public class ConfigurationProcessor extends
AbstractProcessor {
* @return Bundle with all types for configuration
*/
private static TypeName getInterfaceGetMethodType(VariableElement field) {
- TypeName interfaceGetMethodType = null;
-
TypeName baseType = TypeName.get(field.asType());
ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
if (confAnnotation != null) {
- interfaceGetMethodType = getConfigurationInterfaceName((ClassName)
baseType);
+ return getConfigurationInterfaceName((ClassName) baseType);
}
NamedConfigValue namedConfigAnnotation =
field.getAnnotation(NamedConfigValue.class);
@@ -371,7 +374,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
TypeName viewClassType = getViewName((ClassName) baseType);
TypeName changeClassType = getChangeName((ClassName) baseType);
- interfaceGetMethodType = ParameterizedTypeName.get(
+ return ParameterizedTypeName.get(
ClassName.get(NamedConfigurationTree.class),
interfaceGetType,
viewClassType,
@@ -379,13 +382,16 @@ public class ConfigurationProcessor extends
AbstractProcessor {
);
}
- Value valueAnnotation = field.getAnnotation(Value.class);
- PolymorphicId polymorphicIdAnnotation =
field.getAnnotation(PolymorphicId.class);
- InjectedName injectedNameAnnotation =
field.getAnnotation(InjectedName.class);
- InternalId internalIdAnnotation =
field.getAnnotation(InternalId.class);
+ boolean containsAnnotation = containsAnyAnnotation(
+ field,
+ Value.class,
+ PolymorphicId.class,
+ InjectedName.class,
+ InternalId.class,
+ InjectedValue.class
+ );
- if (valueAnnotation != null || polymorphicIdAnnotation != null ||
injectedNameAnnotation != null
- || internalIdAnnotation != null) {
+ if (containsAnnotation) {
// It is necessary to use class names without loading classes so
that we won't
// accidentally get NoClassDefFoundError
ClassName confValueClass =
ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
@@ -396,10 +402,10 @@ public class ConfigurationProcessor extends
AbstractProcessor {
genericType = genericType.box();
}
- interfaceGetMethodType = ParameterizedTypeName.get(confValueClass,
genericType);
+ return ParameterizedTypeName.get(confValueClass, genericType);
}
- return interfaceGetMethodType;
+ throw new IllegalArgumentException(String.format("Field \"%s\" does
not contain any supported annotations", field));
}
/**
@@ -503,8 +509,6 @@ public class ConfigurationProcessor extends
AbstractProcessor {
ClassName consumerClsName = ClassName.get(Consumer.class);
for (VariableElement field : fields) {
- Value valAnnotation = field.getAnnotation(Value.class);
-
String fieldName = field.getSimpleName().toString();
TypeMirror schemaFieldType = field.asType();
TypeName schemaFieldTypeName = TypeName.get(schemaFieldType);
@@ -512,15 +516,13 @@ public class ConfigurationProcessor extends
AbstractProcessor {
boolean leafField =
isValidValueAnnotationFieldType(schemaFieldType)
|| !((ClassName)
schemaFieldTypeName).simpleName().contains(CONFIGURATION_SCHEMA_POSTFIX);
- boolean namedListField =
field.getAnnotation(NamedConfigValue.class) != null;
-
TypeName viewFieldType =
leafField ? schemaFieldTypeName : getViewName((ClassName)
schemaFieldTypeName);
TypeName changeFieldType =
leafField ? schemaFieldTypeName :
getChangeName((ClassName) schemaFieldTypeName);
- if (namedListField) {
+ if (field.getAnnotation(NamedConfigValue.class) != null) {
changeFieldType = ParameterizedTypeName.get(
ClassName.get(NamedListChange.class),
viewFieldType,
@@ -540,8 +542,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
viewClsBuilder.addMethod(getMtdBuilder.build());
// Read only.
- if (field.getAnnotation(PolymorphicId.class) != null ||
field.getAnnotation(InjectedName.class) != null
- || field.getAnnotation(InternalId.class) != null) {
+ if (containsAnyAnnotation(field, PolymorphicId.class,
InjectedName.class, InternalId.class)) {
continue;
}
@@ -551,7 +552,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
.addModifiers(PUBLIC, ABSTRACT)
.returns(changeClsName);
- if (valAnnotation != null) {
+ if (containsAnyAnnotation(field, Value.class,
InjectedValue.class)) {
if (schemaFieldType.getKind() == TypeKind.ARRAY) {
changeMtdBuilder.varargs(true);
}
@@ -559,18 +560,16 @@ public class ConfigurationProcessor extends
AbstractProcessor {
changeMtdBuilder.addParameter(changeFieldType, fieldName);
} else {
changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName,
changeFieldType), fieldName);
- }
- changeClsBuilder.addMethod(changeMtdBuilder.build());
-
- // Create "FooChange changeFoo()" method with no parameters, if
it's a config value or named list value.
- if (valAnnotation == null) {
+ // Create "FooChange changeFoo()" method with no parameters,
if it's a config value or named list value.
MethodSpec.Builder shortChangeMtdBuilder =
MethodSpec.methodBuilder(changeMtdName)
.addModifiers(PUBLIC, ABSTRACT)
.returns(changeFieldType);
changeClsBuilder.addMethod(shortChangeMtdBuilder.build());
}
+
+ changeClsBuilder.addMethod(changeMtdBuilder.build());
}
if (isPolymorphicConfig) {
@@ -1112,8 +1111,8 @@ public class ConfigurationProcessor extends
AbstractProcessor {
* Checks for missing {@link
org.apache.ignite.configuration.annotation.Name} for nested schema with {@link
InjectedName}.
*
* @param field Class field.
- * @throws ConfigurationProcessorException If there is no {@link
org.apache.ignite.configuration.annotation.Name} for the nested schema
- * with {@link InjectedName}.
+ * @throws ConfigurationProcessorException If there is no {@link
org.apache.ignite.configuration.annotation.Name} for the nested
+ * schema with {@link InjectedName}.
*/
private void checkMissingNameForInjectedName(VariableElement field) {
TypeElement fieldType = (TypeElement)
processingEnv.getTypeUtils().asElement(field.asType());
@@ -1123,7 +1122,7 @@ public class ConfigurationProcessor extends
AbstractProcessor {
List<VariableElement> fields;
if (!isClass(superClassFieldType.asType(), Object.class)
- && findFirstPresentAnnotation(superClassFieldType,
AbstractConfiguration.class).isPresent()) {
+ &&
superClassFieldType.getAnnotation(AbstractConfiguration.class) != null) {
fields = concat(
collectFieldsWithAnnotation(fields(fieldType),
InjectedName.class),
collectFieldsWithAnnotation(fields(superClassFieldType),
InjectedName.class)
diff --git
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
index 5b3a2e7097..5061557ed1 100644
---
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
+++
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
@@ -27,13 +27,13 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
-import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
/**
* Annotation processing utilities.
*/
-class ConfigurationProcessorUtils {
+public class ConfigurationProcessorUtils {
/**
* Returns {@link ClassName} for configuration class public interface.
*
@@ -91,17 +91,25 @@ class ConfigurationProcessorUtils {
}
/**
- * Returns the first annotation found for the class.
+ * Returns the first annotation found for the given element.
*
- * @param clazz Class type.
- * @param annotationClasses Annotation classes that will be searched for
the class.
+ * @param element Element.
+ * @param annotationClasses Annotation classes that will be searched for
the element.
*/
@SafeVarargs
public static Optional<? extends Annotation> findFirstPresentAnnotation(
- TypeElement clazz,
+ Element element,
Class<? extends Annotation>... annotationClasses
) {
- return
Stream.of(annotationClasses).map(clazz::getAnnotation).filter(Objects::nonNull).findFirst();
+ return
Stream.of(annotationClasses).map(element::getAnnotation).filter(Objects::nonNull).findFirst();
+ }
+
+ /**
+ * Returns {@code true} if any of the given annotations are present on the
given element.
+ */
+ @SafeVarargs
+ public static boolean containsAnyAnnotation(Element element, Class<?
extends Annotation>... annotationClasses) {
+ return findFirstPresentAnnotation(element,
annotationClasses).isPresent();
}
/**
diff --git
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
new file mode 100644
index 0000000000..7963ff8124
--- /dev/null
+++
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
@@ -0,0 +1,101 @@
+/*
+ * 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.internal.configuration.processor.validators;
+
+import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.collectFieldsWithAnnotation;
+import static
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.simpleName;
+import static org.apache.ignite.internal.util.CollectionUtils.concat;
+
+import java.util.List;
+import java.util.UUID;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.apache.ignite.configuration.annotation.Value;
+import
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorException;
+
+/**
+ * Validator class for the {@link InjectedValue} annotation.
+ */
+public class InjectedValueValidator {
+ private final ProcessingEnvironment processingEnv;
+
+ public InjectedValueValidator(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ }
+
+ /**
+ * Validates invariants of the {@link InjectedValue} annotation. This
includes:
+ *
+ * <ol>
+ * <li>Type of InjectedValue field is either a primitive, or a String,
or a UUID;</li>
+ * <li>There is only a single InjectedValue field in the schema
(including {@link Value} fields).</li>
+ * </ol>
+ */
+ public void validate(TypeElement clazz, List<VariableElement> fields) {
+ List<VariableElement> injectedValueFields =
collectFieldsWithAnnotation(fields, InjectedValue.class);
+
+ if (injectedValueFields.isEmpty()) {
+ return;
+ }
+
+ List<VariableElement> valueFields =
collectFieldsWithAnnotation(fields, Value.class);
+
+ if (injectedValueFields.size() > 1 || !valueFields.isEmpty()) {
+ throw new ConfigurationProcessorException(String.format(
+ "Field marked as %s must be the only \"value\" field in
the schema %s, found: %s",
+ simpleName(InjectedValue.class),
+ clazz.getQualifiedName(),
+ concat(injectedValueFields, valueFields)
+ ));
+ }
+
+ VariableElement injectedValueField = injectedValueFields.get(0);
+
+ // Must be a primitive or an array of the primitives (including
java.lang.String, java.util.UUID).
+ if (!isValidValueAnnotationFieldType(injectedValueField.asType())) {
+ throw new ConfigurationProcessorException(String.format(
+ "%s.%s field must have one of the following types: "
+ + "boolean, int, long, double, String, UUID or an
array of aforementioned type.",
+ clazz.getQualifiedName(),
+ injectedValueField.getSimpleName()
+ ));
+ }
+ }
+
+ private boolean isValidValueAnnotationFieldType(TypeMirror type) {
+ if (type.getKind() == TypeKind.ARRAY) {
+ type = ((ArrayType) type).getComponentType();
+ }
+
+ return type.getKind().isPrimitive() || isClass(type, String.class) ||
isClass(type, UUID.class);
+ }
+
+ private boolean isClass(TypeMirror type, Class<?> clazz) {
+ TypeMirror classType = processingEnv
+ .getElementUtils()
+ .getTypeElement(clazz.getCanonicalName())
+ .asType();
+
+ return classType.equals(type);
+ }
+}
diff --git
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
new file mode 100644
index 0000000000..403f100a8e
--- /dev/null
+++
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
@@ -0,0 +1,79 @@
+/*
+ * 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 static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This annotations is intended to be placed on {@link NamedConfigValue}
elements to emulate key-value pairs in configuration schemas.
+ *
+ * <p>Therefore, this annotation induces the following constraints:
+ *
+ * <ol>
+ * <li>Must be placed on a field of a configuration schema that represents
a Named List element;</li>
+ * <li>Must be the only field in the configuration schema, apart from the
one marked with {@link InjectedName}.</li>
+ * </ol>
+ *
+ * <p>In all other aspects it behaves exactly like the {@link Value}
annotation.
+ *
+ * <p>For example, this annotation can be used to declare a configuration
schema with arbitrary {@code String} properties:
+ *
+ * <pre>{@code
+ * @Config
+ * class PropertyConfigurationSchema {
+ * @NamedConfigValue
+ * public PropertyEntryConfigurationSchema properties;
+ * }
+ *
+ * @Config
+ * class PropertyEntryConfigurationSchema {
+ * @InjectedName
+ * public String propertyName;
+ *
+ * @InjectedValue
+ * public String propertyValue;
+ * }
+ * }</pre>
+ *
+ * <p>This will allow to use the following HOCON to represent this
configuration:
+ *
+ * <pre>{@code
+ * root.properties {
+ * property1: "value1",
+ * property2: "value2",
+ * property3: "value3"
+ * }
+ * }</pre>
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface InjectedValue {
+ /**
+ * Indicates that the current configuration value has a default value.
Value itself is derived from the instantiated object of a
+ * corresponding schema type. This means that the default is not
necessarily a constant value.
+ *
+ * @return {@code hasDefault} flag value.
+ */
+ boolean hasDefault() default false;
+}
diff --git
a/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
index acbe11a4e2..ad7a6c08c9 100644
---
a/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
+++
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.configuration;
import org.apache.ignite.configuration.ConfigurationModule;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
import org.apache.ignite.configuration.validation.CamelCaseKeys;
import
org.apache.ignite.internal.configuration.validation.LongNumberSystemPropertyValueValidator;
@@ -41,6 +41,6 @@ public class SystemPropertyConfigurationSchema {
@InjectedName
public String name;
- @Value
+ @InjectedValue
public String propertyValue;
}
diff --git
a/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
b/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
index b9a9b600a0..96d9929907 100644
---
a/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
+++
b/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
@@ -60,8 +60,7 @@ public class SystemDistributedConfigurationPropertyHolderTest
extends BaseIgnite
@Test
void testValidSystemPropertiesOnStart(
- @InjectConfiguration("mock.properties = {"
- + PROPERTY_NAME + ".propertyValue = \"newValue\"}")
+ @InjectConfiguration("mock.properties." + PROPERTY_NAME + " =
newValue")
SystemDistributedConfiguration systemConfig
) {
var config = noopConfigHolder(systemConfig);
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
index a4a2d2fab4..8c0d45f8ef 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
@@ -54,6 +54,7 @@ import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.is
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfig;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isReadOnly;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
@@ -385,7 +386,7 @@ class ConfigurationImplAsmGenerator extends
AbstractAsmGenerator {
rootKeyVar,
changerVar,
listenOnlyVar,
- constantBoolean(isPolymorphicId(schemaField) ||
isInjectedName(schemaField) || isInternalId(schemaField))
+ constantBoolean(isReadOnly(schemaField))
);
} else {
SchemaClassesInfo fieldInfo =
cgen.schemaInfo(schemaField.getType());
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
index 506a34b602..d1841df1a5 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
@@ -59,10 +59,12 @@ import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.co
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.hasDefault;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isConfigValue;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isInjectedName;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isInjectedValue;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isNamedConfigValue;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfig;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isReadOnly;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
import static
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
import static org.apache.ignite.internal.util.CollectionUtils.concat;
@@ -177,6 +179,9 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
/** {@link ConstructableTreeNode#construct(String, ConfigurationSource,
boolean)} method name. */
private static final String CONSTRUCT_MTD_NAME = "construct";
+ /** {@link ConstructableTreeNode#injectedValueFieldName}. */
+ private static final String INJECTED_VALUE_FIELD_NAME_MTD_NAME =
"injectedValueFieldName";
+
/** Mapping for each configuration {@link Field} to a static constant with
this {@link Field} as value. */
private final Map<Field, FieldDefinition> fieldToFieldDefinitionMap = new
HashMap<>();
@@ -309,6 +314,9 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
// Field with @InjectedName.
FieldDefinition injectedNameFieldDef = null;
+ // Field with @InjectedValue.
+ Field injectedValueField = null;
+
for (Field schemaField : concat(schemaFields, publicExtensionFields,
internalExtensionFields, polymorphicFields)) {
FieldDefinition fieldDef = addInnerNodeField(schemaField);
@@ -318,6 +326,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
polymorphicTypeIdFieldDef = fieldDef;
} else if (isInjectedName(schemaField)) {
injectedNameFieldDef = fieldDef;
+ } else if (isInjectedValue(schemaField)) {
+ injectedValueField = schemaField;
}
}
@@ -444,6 +454,10 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
addInjectedNameFieldMethods(injectedNameFieldDef);
}
+ if (injectedValueField != null) {
+ implementInjectedValueFieldNameMethod(injectedValueField);
+ }
+
if (polymorphicTypeIdFieldDef != null) {
addIsPolymorphicMethod();
}
@@ -1578,6 +1592,16 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator
{
)).ret();
}
+ private void implementInjectedValueFieldNameMethod(Field
injectedValueField) {
+ MethodDefinition method = innerNodeClassDef.declareMethod(
+ EnumSet.of(PUBLIC),
+ INJECTED_VALUE_FIELD_NAME_MTD_NAME,
+ type(String.class)
+ );
+
+
method.getBody().append(constantString(publicName(injectedValueField))).retObject();
+ }
+
/**
* Adds an override for the {@link InnerNode#isPolymorphic} method that
returns {@code true}.
*/
@@ -1698,7 +1722,7 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
);
// Read only.
- if (isPolymorphicId(schemaField) || isInjectedName(schemaField)) {
+ if (isReadOnly(schemaField)) {
continue;
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
index bcae565b7c..e1c1486e9f 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
@@ -105,35 +105,39 @@ class HoconListConfigurationSource implements
ConfigurationSource {
String syntheticKeyName = ((NamedListNode<?>) node).syntheticKeyName();
- int idx = 0;
- for (Iterator<ConfigValue> iterator = hoconCfgList.iterator();
iterator.hasNext(); idx++) {
- ConfigValue next = iterator.next();
+ for (int idx = 0; idx < hoconCfgList.size(); idx++) {
+ ConfigValue next = hoconCfgList.get(idx);
if (next.valueType() != ConfigValueType.OBJECT) {
- throw new IllegalArgumentException(
- format(
- "'%s' is expected to be a composite
configuration node, not a single value",
- formatArrayPath(path, idx)
- )
- );
+ throw new IllegalArgumentException(format(
+ "'%s' is expected to be a composite configuration
node, not a single value",
+ formatArrayPath(path, idx)
+ ));
}
ConfigObject hoconCfg = (ConfigObject) next;
- ConfigValue keyValue = hoconCfg.get(syntheticKeyName);
+ String key;
- if (keyValue == null || keyValue.valueType() !=
ConfigValueType.STRING) {
- throw new IllegalArgumentException(
- format(
- "'%s' configuration value is mandatory and
must be a String",
- formatArrayPath(path, idx) + KEY_SEPARATOR +
syntheticKeyName
- )
- );
- }
+ List<String> path;
- String key = (String) keyValue.unwrapped();
+ ConfigValue keyValue = hoconCfg.get(syntheticKeyName);
- List<String> path = appendKey(this.path, key);
+ if (keyValue != null && keyValue.valueType() ==
ConfigValueType.STRING) {
+ // If the synthetic key is present, check that it has the
correct type and use it as the key.
+ key = (String) keyValue.unwrapped();
+ path = appendKey(this.path, key);
+ } else if (keyValue == null && hoconCfg.size() == 1) {
+ // If the synthetic key is not present explicitly, we need to
handle the case when a configuration uses InjectedValue.
+ // This means that this object must only have one key.
+ key = hoconCfg.entrySet().iterator().next().getKey();
+ path = this.path;
+ } else {
+ throw new IllegalArgumentException(format(
+ "'%s' configuration value is mandatory and must be a
String",
+ formatArrayPath(this.path, idx) + KEY_SEPARATOR +
syntheticKeyName
+ ));
+ }
node.construct(key, new
HoconObjectConfigurationSource(syntheticKeyName, path, hoconCfg), false);
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
index b6e97ec785..b6a3997860 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
@@ -27,7 +27,6 @@ import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
import java.util.List;
-import java.util.Map;
import java.util.NoSuchElementException;
import
org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -67,76 +66,81 @@ class HoconObjectConfigurationSource implements
ConfigurationSource {
this.hoconCfgObject = hoconCfgObject;
}
- /** {@inheritDoc} */
@Override
public <T> T unwrap(Class<T> clazz) {
throw wrongTypeException(clazz, path, -1);
}
- /** {@inheritDoc} */
@Override
public void descend(ConstructableTreeNode node) {
- for (Map.Entry<String, ConfigValue> entry : hoconCfgObject.entrySet())
{
- String key = entry.getKey();
+ String injectedValueFieldName = node.injectedValueFieldName();
- if (key.equals(ignoredKey)) {
- continue;
- }
+ if (injectedValueFieldName == null) {
+ hoconCfgObject.forEach((key, value) -> parseConfigEntry(key,
value, node));
+ } else {
+ assert hoconCfgObject.size() == 1; // User-friendly check must
have been performed outside this method.
- ConfigValue hoconCfgValue = entry.getValue();
+ ConfigValue value = hoconCfgObject.values().iterator().next();
- try {
- switch (hoconCfgValue.valueType()) {
- case NULL:
- node.construct(key, null, false);
+ parseConfigEntry(injectedValueFieldName, value, node);
+ }
+ }
- break;
+ private void parseConfigEntry(String key, ConfigValue hoconCfgValue,
ConstructableTreeNode node) {
+ if (key.equals(ignoredKey)) {
+ return;
+ }
- case OBJECT: {
- List<String> path = appendKey(this.path, key);
+ try {
+ switch (hoconCfgValue.valueType()) {
+ case NULL:
+ node.construct(key, null, false);
- node.construct(
- key,
- new HoconObjectConfigurationSource(null, path,
(ConfigObject) hoconCfgValue),
- false
- );
+ break;
- break;
- }
+ case OBJECT: {
+ List<String> path = appendKey(this.path, key);
- case LIST: {
- List<String> path = appendKey(this.path, key);
+ node.construct(
+ key,
+ new HoconObjectConfigurationSource(null, path,
(ConfigObject) hoconCfgValue),
+ false
+ );
- node.construct(key, new
HoconListConfigurationSource(path, (ConfigList) hoconCfgValue), false);
+ break;
+ }
- break;
- }
+ case LIST: {
+ List<String> path = appendKey(this.path, key);
- default: {
- List<String> path = appendKey(this.path, key);
+ node.construct(key, new HoconListConfigurationSource(path,
(ConfigList) hoconCfgValue), false);
- node.construct(key, new
HoconPrimitiveConfigurationSource(path, hoconCfgValue), false);
- }
+ break;
}
- } catch (NoSuchElementException e) {
- if (path.isEmpty()) {
- throw new IllegalArgumentException(
- format("'%s' configuration root doesn't exist",
key), e
- );
- } else {
- throw new IllegalArgumentException(
- format("'%s' configuration doesn't have the '%s'
sub-configuration", join(path), key), e
- );
+
+ default: {
+ List<String> path = appendKey(this.path, key);
+
+ node.construct(key, new
HoconPrimitiveConfigurationSource(path, hoconCfgValue), false);
}
- } catch (ConfigurationWrongPolymorphicTypeIdException e) {
+ }
+ } catch (NoSuchElementException e) {
+ if (path.isEmpty()) {
throw new IllegalArgumentException(
- "Polymorphic configuration type is not correct: " +
e.getMessage()
+ format("'%s' configuration root doesn't exist", key), e
+ );
+ } else {
+ throw new IllegalArgumentException(
+ format("'%s' configuration doesn't have the '%s'
sub-configuration", join(path), key), e
);
}
+ } catch (ConfigurationWrongPolymorphicTypeIdException e) {
+ throw new IllegalArgumentException(
+ "Polymorphic configuration type is not correct: " +
e.getMessage()
+ );
}
}
- /** {@inheritDoc} */
@Override
public @Nullable String polymorphicTypeId(String fieldName) {
ConfigValue typeId = hoconCfgObject.get(fieldName);
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
index 830b2567e7..4c50b42b27 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
@@ -58,7 +58,6 @@ class HoconPrimitiveConfigurationSource implements
ConfigurationSource {
this.hoconCfgValue = hoconCfgValue;
}
- /** {@inheritDoc} */
@Override
public <T> T unwrap(Class<T> clazz) {
if (clazz.isArray()) {
@@ -68,12 +67,17 @@ class HoconPrimitiveConfigurationSource implements
ConfigurationSource {
return unwrapPrimitive(hoconCfgValue, clazz, path, -1);
}
- /** {@inheritDoc} */
@Override
public void descend(ConstructableTreeNode node) {
- throw new IllegalArgumentException(
- format("'%s' is expected to be a composite configuration node,
not a single value", join(path))
- );
+ String fieldName = node.injectedValueFieldName();
+
+ if (fieldName == null) {
+ throw new IllegalArgumentException(
+ format("'%s' is expected to be a composite configuration
node, not a single value", join(path))
+ );
+ }
+
+ node.construct(fieldName, this, false);
}
/**
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
index a82d45fa46..edd2c18343 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
@@ -18,6 +18,8 @@
package org.apache.ignite.internal.configuration.tree;
import java.util.NoSuchElementException;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.jetbrains.annotations.Nullable;
/**
* Interface for filling the configuration node.
@@ -48,4 +50,11 @@ public interface ConstructableTreeNode {
* @return {@code true} if node became immutable, {@code false} if it has
already been immutable before.
*/
boolean makeImmutable();
+
+ /**
+ * Returns the name of the field annotated with {@link InjectedValue} or
{@code null} if no such field exists.
+ */
+ default @Nullable String injectedValueFieldName() {
+ return null;
+ }
}
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index 76a396e6f7..4e90427b4f 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.configuration.tree;
+import static org.apache.ignite.internal.util.IgniteUtils.newHashMap;
+
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -82,7 +84,7 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
if (val instanceof Character || val instanceof UUID) {
valObj = val.toString();
} else if (val != null && val.getClass().isArray()) {
- valObj = toListOfObjects(field, val);
+ valObj = toListOfObjects(val);
} else if (val instanceof String) {
valObj = maskIfNeeded(field, (String) val);
}
@@ -92,7 +94,6 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
return valObj;
}
- /** {@inheritDoc} */
@Override
public Object visitInnerNode(Field field, String key, InnerNode node) {
if (skipEmptyValues && node == null) {
@@ -107,33 +108,74 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
deque.pop();
- addToParent(key, innerMap);
+ String injectedValueFieldName = node.injectedValueFieldName();
+
+ if (injectedValueFieldName != null) {
+ // If configuration contains an injected value, the rendered named
list will be a map, where every injected value is represented
+ // as a separate key-value pair.
+ Object injectedValue = innerMap.get(injectedValueFieldName);
+
+ addToParent(key, injectedValue);
+
+ return injectedValue;
+ } else {
+ // Otherwise, the rendered named list will be a list of maps.
+ addToParent(key, innerMap);
- return innerMap;
+ return innerMap;
+ }
}
- /** {@inheritDoc} */
@Override
public Object visitNamedListNode(Field field, String key, NamedListNode<?>
node) {
- if (skipEmptyValues && node.size() == 0) {
+ if (skipEmptyValues && node.isEmpty()) {
return null;
}
- List<Object> list = new ArrayList<>(node.size());
+ Object renderedList;
+
+ boolean hasInjectedValues = !node.isEmpty() &&
getFirstNode(node).injectedValueFieldName() != null;
+
+ // See the comment inside "visitInnerNode" why named lists are
rendered differently for injected values.
+ if (hasInjectedValues) {
+ Map<String, Object> map = newHashMap(node.size());
+
+ deque.push(map);
- deque.push(list);
+ for (String subkey : node.namedListKeys()) {
+ InnerNode innerNode = node.getInnerNode(subkey);
- for (String subkey : node.namedListKeys()) {
- node.getInnerNode(subkey).accept(field, subkey, this);
+ innerNode.accept(field, subkey, this);
+ }
+
+ renderedList = map;
+ } else {
+ List<Object> list = new ArrayList<>(node.size());
+
+ deque.push(list);
+
+ for (String subkey : node.namedListKeys()) {
+ InnerNode innerNode = node.getInnerNode(subkey);
+
+ innerNode.accept(field, subkey, this);
- ((Map<String, Object>) list.get(list.size() -
1)).put(node.syntheticKeyName(), subkey);
+ ((Map<String, Object>) list.get(list.size() -
1)).put(node.syntheticKeyName(), subkey);
+ }
+
+ renderedList = list;
}
deque.pop();
- addToParent(key, list);
+ addToParent(key, renderedList);
+
+ return renderedList;
+ }
+
+ private static InnerNode getFirstNode(NamedListNode<?> namedListNode) {
+ String firstKey = namedListNode.namedListKeys().get(0);
- return list;
+ return namedListNode.getInnerNode(firstKey);
}
/**
@@ -173,11 +215,10 @@ public class ConverterToMapVisitor implements
ConfigurationVisitor<Object> {
/**
* Converts array into a list of objects. Boxes array elements if they are
primitive values.
*
- * @param field Field of the array
* @param val Array of primitives or array of {@link String}s
* @return List of objects corresponding to the passed array.
*/
- private List<?> toListOfObjects(Field field, Serializable val) {
+ private static List<?> toListOfObjects(Serializable val) {
Stream<?> stream = IntStream.range(0, Array.getLength(val)).mapToObj(i
-> Array.get(val, i));
if (val.getClass().getComponentType() == char.class ||
val.getClass().getComponentType() == UUID.class) {
diff --git
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
index 7ff2b2eae5..a0e1a28b96 100644
---
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
+++
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
@@ -56,6 +56,7 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationExtension;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
import org.apache.ignite.configuration.annotation.InternalId;
import org.apache.ignite.configuration.annotation.Name;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
@@ -443,7 +444,7 @@ public class ConfigurationUtil {
* @return {@code true} if field represents primitive configuration.
*/
public static boolean isValue(Field schemaField) {
- return schemaField.isAnnotationPresent(Value.class);
+ return schemaField.isAnnotationPresent(Value.class) ||
schemaField.isAnnotationPresent(InjectedValue.class);
}
/**
@@ -498,6 +499,12 @@ public class ConfigurationUtil {
public static boolean hasDefault(Field field) {
assert isValue(field) : field;
+ InjectedValue injectedValue = field.getAnnotation(InjectedValue.class);
+
+ if (injectedValue != null) {
+ return injectedValue.hasDefault();
+ }
+
return field.getAnnotation(Value.class).hasDefault();
}
@@ -1129,6 +1136,20 @@ public class ConfigurationUtil {
return schemaField.isAnnotationPresent(InjectedName.class);
}
+ /**
+ * Returns {@code true} if the given schema field contains the {@link
InjectedValue} annotation.
+ */
+ public static boolean isInjectedValue(Field schemaField) {
+ return schemaField.isAnnotationPresent(InjectedValue.class);
+ }
+
+ /**
+ * Returns {@code true} if the given schema field is read-only.
+ */
+ public static boolean isReadOnly(Field schemaField) {
+ return isPolymorphicId(schemaField) || isInjectedName(schemaField) ||
isInternalId(schemaField);
+ }
+
/**
* Checks whether configuration schema field contains {@link Name}.
*
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
new file mode 100644
index 0000000000..3d929c3055
--- /dev/null
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.internal.configuration;
+
+import static
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static
org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValue;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.internal.configuration.hocon.HoconConverter;
+import
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import
org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
+import org.apache.ignite.internal.manager.ComponentContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for schemas with {@link InjectedValue}s.
+ */
+public class InjectedValueConfigurationTest {
+ /** Root schema. */
+ @ConfigurationRoot(rootName = "rootInjectedValue", type = LOCAL)
+ public static class HoconInjectedValueRootConfigurationSchema {
+ @NamedConfigValue
+ public HoconInjectedValueConfigurationSchema nestedNamed;
+ }
+
+ /** Named list element schema. */
+ @Config
+ public static class HoconInjectedValueConfigurationSchema {
+ @InjectedName
+ public String someName;
+
+ @InjectedValue(hasDefault = true)
+ public String someValue = "default";
+ }
+
+ private ConfigurationRegistry registry;
+
+ @BeforeEach
+ void setUp() {
+ List<RootKey<?, ?>> roots = List.of(
+ HoconInjectedValueRootConfiguration.KEY
+ );
+
+ registry = new ConfigurationRegistry(
+ roots,
+ new TestConfigurationStorage(LOCAL),
+ new ConfigurationTreeGenerator(roots, List.of(), List.of()),
+ new TestConfigurationValidator()
+ );
+
+ assertThat(registry.startAsync(new ComponentContext()),
willCompleteSuccessfully());
+ }
+
+ @AfterEach
+ void tearDown() {
+ assertThat(registry.stopAsync(new ComponentContext()),
willCompleteSuccessfully());
+ }
+
+ @Nested
+ class HoconConverterTest {
+ @Test
+ void testEmpty() {
+ assertEquals("nestedNamed=[]",
asHoconStr(List.of("rootInjectedValue")));
+ }
+
+ @Test
+ void testAssignValuesUsingObjectNotation() {
+ change("rootInjectedValue.nestedNamed = {foo: bar}");
+
+ assertEquals("nestedNamed{foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ change("rootInjectedValue.nestedNamed = {foo: bar, baz: quux}");
+
+ assertEquals("nestedNamed{baz=quux,foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ change("rootInjectedValue.nestedNamed = {baz: anotherQuux}");
+
+ assertEquals("nestedNamed{baz=anotherQuux,foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ change("rootInjectedValue.nestedNamed = {baz: null}");
+
+ assertEquals("nestedNamed{foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+ }
+
+ @Test
+ void testAssignValuesUsingListNotation() {
+ change("rootInjectedValue.nestedNamed = [{foo=bar}]");
+
+ assertEquals("nestedNamed{foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ change("rootInjectedValue.nestedNamed = [{foo=bar},{baz=quux}]");
+
+ assertEquals("nestedNamed{baz=quux,foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ change("rootInjectedValue.nestedNamed = [{baz=anotherQuux}]");
+
+ assertEquals("nestedNamed{baz=anotherQuux,foo=bar}",
asHoconStr(List.of("rootInjectedValue")));
+
+ // Removing a value does not work in this notation.
+ }
+ }
+
+ @Nested
+ class JavaApiTest {
+ @Test
+ void testEmpty() {
+ HoconInjectedValueRootConfiguration cfg =
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+ assertThat(cfg.nestedNamed().value().size(), is(0));
+ }
+
+ @Test
+ void testDefaults() {
+ HoconInjectedValueRootConfiguration cfg =
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+ CompletableFuture<Void> changeFuture = cfg.change(rootChange ->
rootChange
+ .changeNestedNamed(nestedChange -> nestedChange
+ .create("foo", valueChange -> {})));
+
+ assertThat(changeFuture, willCompleteSuccessfully());
+
+ assertThat(cfg.value().nestedNamed().get("foo").someValue(),
is("default"));
+ assertThat(cfg.nestedNamed().value().get("foo").someValue(),
is("default"));
+ assertThat(cfg.nestedNamed().get("foo").value().someValue(),
is("default"));
+ assertThat(cfg.nestedNamed().get("foo").someValue().value(),
is("default"));
+ }
+
+ @Test
+ void testAssignValues() {
+ HoconInjectedValueRootConfiguration cfg =
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+ CompletableFuture<Void> changeFuture = cfg.change(rootChange ->
rootChange
+ .changeNestedNamed(nestedChange -> nestedChange
+ .create("foo", valueChange ->
valueChange.changeSomeValue("bar"))));
+
+ assertThat(changeFuture, willCompleteSuccessfully());
+
+ assertThat(cfg.value().nestedNamed().get("foo").someValue(),
is("bar"));
+ assertThat(cfg.nestedNamed().value().get("foo").someValue(),
is("bar"));
+ assertThat(cfg.nestedNamed().get("foo").value().someValue(),
is("bar"));
+ assertThat(cfg.nestedNamed().get("foo").someValue().value(),
is("bar"));
+ }
+ }
+
+ private void change(String hocon) {
+ assertThat(
+
registry.change(hoconSource(ConfigFactory.parseString(hocon).root())),
+ willCompleteSuccessfully()
+ );
+ }
+
+ private String asHoconStr(List<String> basePath, String... path) {
+ List<String> fullPath = Stream.concat(basePath.stream(),
Arrays.stream(path)).collect(Collectors.toList());
+
+ ConfigValue hoconCfg = HoconConverter.represent(registry.superRoot(),
fullPath);
+
+ return hoconCfg.render(ConfigRenderOptions.concise().setJson(false));
+ }
+}
diff --git
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
index f403cb9312..7909d9cd43 100644
---
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
+++
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -731,7 +731,7 @@ public class HoconConverterTest {
// Let's check that the NamedConfigValue#syntheticKeyName key will not
work.
assertThrowsIllegalArgException(
() -> change("rootInjectedName.nestedNamed = [{superName =
foo}]"),
- "'rootInjectedName.nestedNamed[0].someName' configuration
value is mandatory and must be a String"
+ "'rootInjectedName.nestedNamed' configuration doesn't have the
'superName' sub-configuration"
);
}
diff --git
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
index 8bfd18470f..14786209b3 100644
---
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
+++
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
@@ -63,7 +63,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
private static final String TABLE_NAME = "table1";
@Language("HOCON")
- private static final String NODE_ATTRIBUTES = "{region.attribute = US,
storage.attribute = SSD}";
+ private static final String NODE_ATTRIBUTES = "{region = US, storage =
SSD}";
private static final String STORAGE_PROFILES = String.format("'%s, %s'",
DEFAULT_ROCKSDB_PROFILE_NAME, DEFAULT_AIPERSIST_PROFILE_NAME);
@@ -115,7 +115,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
String filter = "$[?(@.region == \"US\" && @.storage == \"SSD\")]";
// This node do not pass the filter
- @Language("HOCON") String firstNodeAttributes =
"{region:{attribute:\"EU\"},storage:{attribute:\"SSD\"}}";
+ @Language("HOCON") String firstNodeAttributes = "{region: EU, storage:
SSD}";
Ignite node = startNode(1, createStartConfig(firstNodeAttributes,
STORAGE_PROFILES_CONFIGS));
@@ -123,10 +123,10 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
node.sql().execute(null, createTableSql());
- MetaStorageManager metaStorageManager = (MetaStorageManager)
IgniteTestUtils
+ MetaStorageManager metaStorageManager = IgniteTestUtils
.getFieldValue(node, IgniteImpl.class, "metaStorageMgr");
- TableManager tableManager = (TableManager)
IgniteTestUtils.getFieldValue(node, IgniteImpl.class, "distributedTblMgr");
+ TableManager tableManager = IgniteTestUtils.getFieldValue(node,
IgniteImpl.class, "distributedTblMgr");
TableViewInternal table = (TableViewInternal)
tableManager.table(TABLE_NAME);
@@ -140,7 +140,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
TIMEOUT_MILLIS
);
- @Language("HOCON") String secondNodeAttributes =
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+ @Language("HOCON") String secondNodeAttributes = "{region: US,
storage: SSD}";
// This node pass the filter but storage profiles of a node do not
match zone's storage profiles.
// TODO: https://issues.apache.org/jira/browse/IGNITE-21387 recovery
of this node is failing,
@@ -210,7 +210,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
TIMEOUT_MILLIS
);
- @Language("HOCON") String firstNodeAttributes =
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+ @Language("HOCON") String firstNodeAttributes = "{region: US, storage:
SSD}";
// This node pass the filter
startNode(1, createStartConfig(firstNodeAttributes,
STORAGE_PROFILES_CONFIGS));
@@ -265,7 +265,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
TIMEOUT_MILLIS
);
- @Language("HOCON") String firstNodeAttributes =
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+ @Language("HOCON") String firstNodeAttributes = "{region: US, storage:
SSD}";
// This node pass the filter
startNode(1, createStartConfig(firstNodeAttributes,
STORAGE_PROFILES_CONFIGS));
@@ -306,7 +306,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
Ignite node0 = unwrapIgniteImpl(node(0));
// This node passes the filter
- @Language("HOCON") String firstNodeAttributes =
"{region:{attribute:\"EU\"},storage:{attribute:\"HDD\"}}";
+ @Language("HOCON") String firstNodeAttributes = "{region: EU, storage:
HDD}";
Ignite node1 = startNode(1, createStartConfig(firstNodeAttributes,
STORAGE_PROFILES_CONFIGS));
@@ -324,7 +324,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
node1.sql().execute(null, createTableSql());
- TableManager tableManager = (TableManager)
IgniteTestUtils.getFieldValue(node0, IgniteImpl.class, "distributedTblMgr");
+ TableManager tableManager = IgniteTestUtils.getFieldValue(node0,
IgniteImpl.class, "distributedTblMgr");
TableViewInternal table = (TableViewInternal)
tableManager.table(TABLE_NAME);
@@ -350,7 +350,7 @@ public class ItDistributionZonesFiltersTest extends
ClusterPerTestIntegrationTes
IgniteImpl node0 = unwrapIgniteImpl(node(0));
// This node passes the filter
- @Language("HOCON") String firstNodeAttributes =
"{region:{attribute:\"EU\"},storage:{attribute:\"HDD\"}}";
+ @Language("HOCON") String firstNodeAttributes = "{region: EU, storage:
HDD}";
startNode(1, createStartConfig(firstNodeAttributes,
STORAGE_PROFILES_CONFIGS));
diff --git
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
index ac954febcd..6572931658 100644
---
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
+++
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
@@ -62,7 +62,7 @@ public class ItRebalanceTriggersRecoveryTest extends
ClusterPerTestIntegrationTe
+ " },\n"
+ " clientConnector: { port:{} },\n"
+ " nodeAttributes: {\n"
- + " nodeAttributes: {region: {attribute: \"US\"}, zone:
{attribute: \"global\"}}\n"
+ + " nodeAttributes: {region: US, zone: global}\n"
+ " },\n"
+ " rest.port: {},\n"
+ " failureHandler.dumpThreadsOnFailure: false\n"
@@ -77,7 +77,7 @@ public class ItRebalanceTriggersRecoveryTest extends
ClusterPerTestIntegrationTe
+ " },\n"
+ " clientConnector: { port:{} },\n"
+ " nodeAttributes: {\n"
- + " nodeAttributes: {zone: {attribute: \"global\"}}\n"
+ + " nodeAttributes: {zone: global}\n"
+ " },\n"
+ " rest.port: {},\n"
+ " failureHandler.dumpThreadsOnFailure: false\n"
diff --git
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
index 251e3bbd60..26909c6b2a 100644
---
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
+++
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
@@ -89,9 +89,7 @@ public abstract class BaseDistributionZoneManagerTest extends
BaseIgniteAbstract
private final List<IgniteComponent> components = new ArrayList<>();
- @InjectConfiguration("mock.properties = {"
- + PARTITION_DISTRIBUTION_RESET_TIMEOUT + ".propertyValue = \"" +
IMMEDIATE_TIMER_VALUE + "\", "
- + "}")
+ @InjectConfiguration("mock.properties." +
PARTITION_DISTRIBUTION_RESET_TIMEOUT + " = \"" + IMMEDIATE_TIMER_VALUE + "\"")
SystemDistributedConfiguration systemDistributedConfiguration;
@BeforeEach
diff --git a/modules/distribution-zones/tech-notes/filters.md
b/modules/distribution-zones/tech-notes/filters.md
index 0cad552679..22269fdf69 100644
--- a/modules/distribution-zones/tech-notes/filters.md
+++ b/modules/distribution-zones/tech-notes/filters.md
@@ -16,8 +16,8 @@ In the `ignite-config.conf` it looks like this:
```
nodeAttributes.nodeAttributes {
- region.attribute = "US"
- storage.attribute = "SSD"
+ region = "US"
+ storage = "SSD"
}
```
@@ -27,12 +27,8 @@ or like this in a `ignite-config.json`:
{
"nodeAttributes":{
"nodeAttributes":{
- "region":{
- "attribute":"US"
- },
- "storage":{
- "attribute":"SSD"
- }
+ "region": "US",
+ "storage": "SSD"
}
}
}
@@ -65,6 +61,3 @@ To check all capabilities of JSONPath, see
https://github.com/json-path/JsonPath
Note that as a default value for filter '$..*' filter is used, meaning that
all nodes match the filter.
Also it is important, that if there are no specified attributes for a node, it
means that a node match only the default filter.
-
-
-
diff --git
a/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
b/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
index 67deee8707..40a4482d30 100644
---
a/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
+++
b/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
@@ -81,8 +81,8 @@ public class TestMetasStorageUtils {
public static String createClusterConfigWithCompactionProperties(long
interval, long dataAvailabilityTime) {
return String.format(
"ignite.system.properties: {"
- + "%s.propertyValue= \"%s\", "
- + "%s.propertyValue= \"%s\""
+ + "%s = \"%s\", "
+ + "%s = \"%s\""
+ "}",
INTERVAL_SYSTEM_PROPERTY_NAME, interval,
DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME, dataAvailabilityTime
);
diff --git
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
index 296fed8b93..17364d6ee4 100644
---
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
+++
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
@@ -52,8 +52,8 @@ public class MetaStorageCompactionTriggerConfigurationTest
extends BaseIgniteAbs
@Test
void testValidSystemPropertiesOnStart(
@InjectConfiguration("mock.properties = {"
- + INTERVAL_SYSTEM_PROPERTY_NAME + ".propertyValue =
\"100\", "
- + DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME +
".propertyValue = \"500\""
+ + INTERVAL_SYSTEM_PROPERTY_NAME + " = \"100\", "
+ + DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME + " =
\"500\""
+ "}")
SystemDistributedConfiguration systemConfig
) {
diff --git
a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
index 749194de65..6280a5e0f6 100644
---
a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
+++
b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
@@ -256,13 +256,13 @@ public class ItReplicaLifecycleTest extends
BaseIgniteAbstractTest {
@InjectConfiguration
private static SystemLocalConfiguration systemConfiguration;
- @InjectConfiguration("mock.nodeAttributes: {region.attribute = US,
storage.attribute = SSD}")
+ @InjectConfiguration("mock.nodeAttributes: {region = US, storage = SSD}")
private static NodeAttributesConfiguration nodeAttributes1;
- @InjectConfiguration("mock.nodeAttributes: {region.attribute = EU,
storage.attribute = SSD}")
+ @InjectConfiguration("mock.nodeAttributes: {region = EU, storage = SSD}")
private static NodeAttributesConfiguration nodeAttributes2;
- @InjectConfiguration("mock.nodeAttributes: {region.attribute = UK,
storage.attribute = SSD}")
+ @InjectConfiguration("mock.nodeAttributes: {region = UK, storage = SSD}")
private static NodeAttributesConfiguration nodeAttributes3;
@InjectConfiguration
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index e9efcc2d8c..3e86b870d6 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -1028,8 +1028,8 @@ public class ItIgniteNodeRestartTest extends
BaseIgniteRestartTest {
stopNode(0);
String newAttributesCfg = "{\n"
- + " region.attribute = \"US\"\n"
- + " storage.attribute = \"SSD\"\n"
+ + " region = US\n"
+ + " storage = SSD\n"
+ "}";
Map<String, String> newAttributesMap = Map.of("region", "US",
"storage", "SSD");
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
index 6f2cd40ea4..615a03b0bc 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
@@ -52,9 +52,9 @@ import org.junit.jupiter.api.Test;
*/
public class ItReplicaStateManagerTest extends BaseIgniteRestartTest {
private static final String[] ATTRIBUTES = {
- "{region:{attribute:\"REG0\"}}",
- "{region:{attribute:\"REG1\"}}",
- "{region:{attribute:\"REG2\"}}"
+ "{ region: REG0 }",
+ "{ region: REG1 }",
+ "{ region: REG2 }"
};
private static final String ZONE_NAME = "TEST_ZONE";
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
index 6c703266b7..3c716423b9 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
@@ -37,7 +37,7 @@ public class ItUnstableTopologyTest extends
BaseSqlIntegrationTest {
+ " },\n"
+ " clientConnector: { port:{} },\n"
+ " nodeAttributes: {\n"
- + " nodeAttributes: {role: {attribute: \"data\"}}\n"
+ + " nodeAttributes: { role: data }\n"
+ " },\n"
+ " rest.port: {},\n"
+ " failureHandler.dumpThreadsOnFailure: false\n"
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
index 08dc440b6a..7456704f5f 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
@@ -24,11 +24,11 @@ import org.junit.jupiter.api.Test;
/** Test suite for the cases with a recovery of the group replication factor
after reset by zone filter update. */
public class ItHighAvailablePartitionsRecoveryByFilterUpdateTest extends
AbstractHighAvailablePartitionsRecoveryTest {
- private static final String GLOBAL_EU_NODES_CONFIG =
nodeConfig("{region.attribute = EU, zone.attribute = global}");
+ private static final String GLOBAL_EU_NODES_CONFIG = nodeConfig("{region =
EU, zone = global}");
- private static final String EU_ONLY_NODES_CONFIG =
nodeConfig("{region.attribute = EU}");
+ private static final String EU_ONLY_NODES_CONFIG = nodeConfig("{region =
EU}");
- private static final String GLOBAL_NODES_CONFIG =
nodeConfig("{zone.attribute = global}");
+ private static final String GLOBAL_NODES_CONFIG = nodeConfig("{zone =
global}");
@Override
protected int initialNodes() {