You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2020/12/17 12:51:30 UTC
[ignite-3] branch main updated: IGNITE-13562 Unified configuration:
basic features and code generation tools
This is an automated email from the ASF dual-hosted git repository.
sergeychugunov 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 ef06617 IGNITE-13562 Unified configuration: basic features and code generation tools
ef06617 is described below
commit ef0661718edbad2f85bc864ca4bdd6f5b0ad1c2a
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Thu Dec 17 15:48:59 2020 +0300
IGNITE-13562 Unified configuration: basic features and code generation tools
Signed-off-by: Sergey Chugunov <se...@gmail.com>
---
modules/configuration-annotation-processor/pom.xml | 113 +++
.../internal/ConfigurationDescription.java | 42 +
.../processor/internal/ConfigurationElement.java | 74 ++
.../processor/internal/ConfigurationNode.java | 53 ++
.../processor/internal/Processor.java | 910 +++++++++++++++++++++
.../processor/internal/ProcessorException.java | 32 +
.../configuration/processor/internal/Utils.java | 217 +++++
.../internal/pojo/ChangeClassGenerator.java | 87 ++
.../processor/internal/pojo/ClassGenerator.java | 127 +++
.../processor/internal/pojo/FieldMapping.java | 52 ++
.../internal/pojo/InitClassGenerator.java | 83 ++
.../internal/pojo/ViewClassGenerator.java | 79 ++
.../internal/validation/ValidationGenerator.java | 174 ++++
.../services/javax.annotation.processing.Processor | 1 +
.../processor/internal/AbstractProcessorTest.java | 164 ++++
.../processor/internal/ConfigSet.java | 123 +++
.../processor/internal/HasFieldMatcher.java | 129 +++
.../processor/internal/HasMethodMatcher.java | 126 +++
.../processor/internal/ParsedClass.java | 107 +++
.../processor/internal/ProcessorTest.java | 104 +++
.../configuration/processor/internal/Types.java | 32 +
.../sample/AutoAdjustConfigurationSchema.java | 42 +
.../sample/BaselineConfigurationSchema.java | 37 +
.../sample/CacheConfigurationSchema.java | 33 +
.../sample/ClusterWideConfigurationSchema.java | 36 +
.../sample/LocalConfigurationSchema.java | 32 +
.../sample/NodeConfigurationSchema.java | 45 +
.../ignite/configuration/sample/UsageTest.java | 95 +++
.../sample/validation/AutoAdjustValidator.java | 41 +
.../sample/validation/AutoAdjustValidator2.java | 41 +
.../sample/validation/NodeValidator.java | 40 +
.../internal/TestConfigurationSchema.java | 22 +
modules/configuration/pom.xml | 69 ++
.../configuration/ConfigurationProperty.java | 46 ++
.../ignite/configuration/ConfigurationTree.java | 30 +
.../ignite/configuration/ConfigurationValue.java | 25 +
.../apache/ignite/configuration/Configurator.java | 207 +++++
.../ignite/configuration/PropertyListener.java | 61 ++
.../ignite/configuration/PublicConfigurator.java | 42 +
.../ignite/configuration/annotation/Config.java | 69 ++
.../configuration/annotation/ConfigValue.java | 47 ++
.../configuration/annotation/NamedConfigValue.java | 51 ++
.../ignite/configuration/annotation/Validate.java | 75 ++
.../ignite/configuration/annotation/Value.java | 40 +
.../internal/DynamicConfiguration.java | 150 ++++
.../configuration/internal/DynamicProperty.java | 203 +++++
.../ignite/configuration/internal/Modifier.java | 48 ++
.../ignite/configuration/internal/NamedList.java | 44 +
.../internal/NamedListConfiguration.java | 126 +++
.../internal/selector/BaseSelectors.java | 157 ++++
.../configuration/internal/selector/Selector.java | 40 +
.../selector/SelectorNotFoundException.java | 33 +
.../internal/validation/MaxValidator.java | 44 +
.../internal/validation/MemberKey.java | 51 ++
.../internal/validation/MinValidator.java | 44 +
.../internal/validation/NotNullValidator.java | 41 +
.../storage/ConfigurationStorage.java | 56 ++
.../configuration/storage/StorageException.java | 36 +
.../ConfigurationValidationException.java | 30 +
.../configuration/validation/FieldValidator.java | 47 ++
pom.xml | 5 +
61 files changed, 5210 insertions(+)
diff --git a/modules/configuration-annotation-processor/pom.xml b/modules/configuration-annotation-processor/pom.xml
new file mode 100644
index 0000000..98758a8
--- /dev/null
+++ b/modules/configuration-annotation-processor/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+-->
+
+<!--
+ POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-configuration-annotation-processor</artifactId>
+ <version>3.0-SNAPSHOT</version>
+ <url>http://ignite.apache.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.squareup</groupId>
+ <artifactId>javapoet</artifactId>
+ <version>1.13.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-configuration</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.4.6</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.testing.compile</groupId>
+ <artifactId>compile-testing</artifactId>
+ <version>0.19</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>5.6.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>fr.inria.gforge.spoon</groupId>
+ <artifactId>spoon-core</artifactId>
+ <version>8.3.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <!-- disable processing because the definition in META-INF/services breaks javac -->
+<!-- <compilerArgument>-proc:none</compilerArgument>-->
+ <source>8</source>
+ <target>8</target>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-compile</id>
+ <configuration>
+ <compilerArgument>-proc:none</compilerArgument>
+ <includes>
+ <include>org/apache/ignite/configuration/processor/internal/*</include>
+ </includes>
+ </configuration>
+ </execution>
+ <execution>
+ <id>compile-project</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
new file mode 100644
index 0000000..2154aba
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Configuration and all it's inner fields.
+ */
+public class ConfigurationDescription extends ConfigurationElement {
+ /** Inner configuration fields. */
+ private List<ConfigurationElement> fields = new ArrayList<>();
+
+ /** Constructor. */
+ public ConfigurationDescription(TypeName type, String name, TypeName view, TypeName init, TypeName change) {
+ super(type, name, view, init, change);
+ }
+
+ /**
+ * Get configuration fields.
+ */
+ public List<ConfigurationElement> getFields() {
+ return fields;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
new file mode 100644
index 0000000..24bab53
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Element of configuration.
+ */
+public class ConfigurationElement {
+ /** Name of configuration element. */
+ private final String name;
+
+ /** Configuration type. */
+ private final TypeName type;
+
+ /** Configuration VIEW type. */
+ private final TypeName view;
+
+ /** Configuration INIT type. */
+ private final TypeName init;
+
+ /** Configuration CHANGE type. */
+ private final TypeName change;
+
+ /** Constructor. */
+ public ConfigurationElement(TypeName type, String name, TypeName view, TypeName init, TypeName change) {
+ this.type = type;
+ this.name = name;
+ this.view = view;
+ this.init = init;
+ this.change = change;
+ }
+
+ /** */
+ public String getName() {
+ return name;
+ }
+
+ /** */
+ public TypeName getType() {
+ return type;
+ }
+
+ /** */
+ public TypeName getView() {
+ return view;
+ }
+
+ /** */
+ public TypeName getInit() {
+ return init;
+ }
+
+ /** */
+ public TypeName getChange() {
+ return change;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java
new file mode 100644
index 0000000..2bbd637
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Configuration element with a reference to its parent and with an original name.
+ */
+public class ConfigurationNode extends ConfigurationElement {
+ /** Configuration parent. */
+ private final ConfigurationNode parent;
+
+ /** Original name of configuration element. */
+ private final String originalName;
+
+ /** Constructor. */
+ public ConfigurationNode(TypeName type, String name, String originalName, TypeName view, TypeName init, TypeName change, ConfigurationNode parent) {
+ super(type, name, view, init, change);
+ this.originalName = originalName;
+ this.parent = parent;
+ }
+
+ /**
+ * Get configuration parent.
+ */
+ public ConfigurationNode getParent() {
+ return parent;
+ }
+
+ /**
+ * Get original name of configuration element.
+ */
+ public String getOriginalName() {
+ return originalName;
+ }
+
+}
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
new file mode 100644
index 0000000..50d10c9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -0,0 +1,910 @@
+/*
+ * 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;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.WildcardTypeName;
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+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.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.Elements;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.ConfigurationValue;
+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.NamedConfigValue;
+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.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.internal.selector.Selector;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+import org.apache.ignite.configuration.processor.internal.pojo.ChangeClassGenerator;
+import org.apache.ignite.configuration.processor.internal.pojo.InitClassGenerator;
+import org.apache.ignite.configuration.processor.internal.pojo.ViewClassGenerator;
+import org.apache.ignite.configuration.processor.internal.validation.ValidationGenerator;
+
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+
+/**
+ * Annotation processor that produces configuration classes.
+ */
+public class Processor extends AbstractProcessor {
+ /** Wildcard (?) TypeName. */
+ private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
+
+ /** Type of Configurator (every DynamicConfiguration has a Configurator field). */
+ private static final ParameterizedTypeName CONFIGURATOR_TYPE = ParameterizedTypeName.get(
+ ClassName.get(Configurator.class),
+ WildcardTypeName.subtypeOf(
+ ParameterizedTypeName.get(ClassName.get(DynamicConfiguration.class), WILDCARD, WILDCARD, WILDCARD)
+ )
+ );
+
+ /** Generator of VIEW classes. */
+ private ViewClassGenerator viewClassGenerator;
+
+ /** Generator of INIT classes. */
+ private InitClassGenerator initClassGenerator;
+
+ /** Generator of CHANGE classes. */
+ private ChangeClassGenerator changeClassGenerator;
+
+ /** Class file writer. */
+ private Filer filer;
+
+ /**
+ * Constructor.
+ */
+ public Processor() {
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ filer = processingEnv.getFiler();
+ viewClassGenerator = new ViewClassGenerator(processingEnv);
+ initClassGenerator = new InitClassGenerator(processingEnv);
+ changeClassGenerator = new ChangeClassGenerator(processingEnv);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
+ final Elements elementUtils = processingEnv.getElementUtils();
+
+ Map<TypeName, ConfigurationDescription> props = new HashMap<>();
+
+ List<ConfigurationDescription> roots = new ArrayList<>();
+
+ // Package to use for Selectors and Keys classes
+ String packageForUtil = "";
+
+ // All classes annotated with @Config
+ final Set<TypeElement> annotatedConfigs = roundEnvironment.getElementsAnnotatedWith(Config.class).stream()
+ .filter(element -> element.getKind() == ElementKind.CLASS)
+ .map(TypeElement.class::cast)
+ .collect(Collectors.toSet());
+
+ if (annotatedConfigs.isEmpty())
+ return false;
+
+ for (TypeElement clazz : annotatedConfigs) {
+ // Get package name of the schema class
+ final PackageElement elementPackage = elementUtils.getPackageOf(clazz);
+ final String packageName = elementPackage.getQualifiedName().toString();
+
+ // Find all the fields of the schema
+ final List<VariableElement> fields = clazz.getEnclosedElements().stream()
+ .filter(el -> el.getKind() == ElementKind.FIELD)
+ .map(VariableElement.class::cast)
+ .collect(Collectors.toList());
+
+ final Config classConfigAnnotation = clazz.getAnnotation(Config.class);
+
+ // Configuration name
+ final String configName = classConfigAnnotation.value();
+ // Is root of the configuration
+ final boolean isRoot = classConfigAnnotation.root();
+ final ClassName schemaClassName = ClassName.get(packageName, clazz.getSimpleName().toString());
+
+ // Get name for generated configuration class and it's interface
+ final ClassName configClass = Utils.getConfigurationName(schemaClassName);
+ final ClassName configInterface = Utils.getConfigurationInterfaceName(schemaClassName);
+
+ ConfigurationDescription configDesc = new ConfigurationDescription(
+ configClass,
+ configName,
+ Utils.getViewName(schemaClassName),
+ Utils.getInitName(schemaClassName),
+ Utils.getChangeName(schemaClassName)
+ );
+
+ // If root, then use it's package as package for Selectors and Keys
+ if (isRoot) {
+ roots.add(configDesc);
+ packageForUtil = packageName;
+ }
+
+ TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
+ .addSuperinterface(configInterface)
+ .addModifiers(PUBLIC, FINAL);
+
+ TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
+ .addModifiers(PUBLIC);
+
+ CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
+ CodeBlock.Builder copyConstructorBodyBuilder = CodeBlock.builder();
+
+ for (VariableElement field : fields) {
+ // Get original field type (must be another configuration schema or "primitive" like String or long)
+ final TypeName baseType = TypeName.get(field.asType());
+
+ final String fieldName = field.getSimpleName().toString();
+
+ // Get configuration types (VIEW, INIT, CHANGE and so on)
+ final ConfigurationFieldTypes types = getTypes(field);
+
+ TypeName getMethodType = types.getGetMethodType();
+ TypeName viewClassType = types.getViewClassType();
+ TypeName initClassType = types.getInitClassType();
+ TypeName changeClassType = types.getChangeClassType();
+
+ final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
+ if (confAnnotation != null) {
+ // Create DynamicConfiguration (descendant) field
+ final FieldSpec nestedConfigField =
+ FieldSpec
+ .builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL)
+ .build();
+
+ configurationClassBuilder.addField(nestedConfigField);
+
+ // Constructor statement
+ constructorBodyBuilder.addStatement("add($L = new $T(qualifiedName, $S, false, configurator, this.root))", fieldName, getMethodType, fieldName);
+
+ // Copy constructor statement
+ copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+ }
+
+ final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+ if (namedConfigAnnotation != null) {
+ ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
+
+ // Create NamedListConfiguration<> field
+ final FieldSpec nestedConfigField = FieldSpec.builder(
+ getMethodType,
+ fieldName,
+ Modifier.PRIVATE,
+ FINAL
+ ).build();
+
+ configurationClassBuilder.addField(nestedConfigField);
+
+ // Constructor statement
+ constructorBodyBuilder.addStatement(
+ "add($L = new $T(qualifiedName, $S, configurator, this.root, (p, k) -> new $T(p, k, true, configurator, this.root)))",
+ fieldName,
+ getMethodType,
+ fieldName,
+ fieldType
+ );
+
+ // Copy constructor statement
+ copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+ }
+
+ final Value valueAnnotation = field.getAnnotation(Value.class);
+ if (valueAnnotation != null) {
+ // Create value (DynamicProperty<>) field
+ final FieldSpec generatedField = FieldSpec.builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL).build();
+
+ configurationClassBuilder.addField(generatedField);
+
+ final CodeBlock validatorsBlock = ValidationGenerator.generateValidators(field);
+
+ // Constructor statement
+ constructorBodyBuilder.addStatement(
+ "add($L = new $T(qualifiedName, $S, new $T($T.class, $S), this.configurator, this.root), $L)",
+ fieldName, getMethodType, fieldName, MemberKey.class, configClass, fieldName, validatorsBlock
+ );
+
+ // Copy constructor statement
+ copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+ }
+
+ configDesc.getFields().add(new ConfigurationElement(getMethodType, fieldName, viewClassType, initClassType, changeClassType));
+
+ createGettersAndSetter(configurationClassBuilder, configurationInterfaceBuilder, fieldName, types, valueAnnotation);
+ }
+
+ props.put(configClass, configDesc);
+
+ // Create VIEW, INIT and CHANGE classes
+ createPojoBindings(packageName, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
+
+ // Create constructors for configuration class
+ createConstructors(configClass, configName, configurationClassBuilder, CONFIGURATOR_TYPE, constructorBodyBuilder, copyConstructorBodyBuilder);
+
+ // Create copy method for configuration class
+ createCopyMethod(configClass, configurationClassBuilder);
+
+ // Write configuration interface
+ JavaFile interfaceFile = JavaFile.builder(packageName, configurationInterfaceBuilder.build()).build();
+
+ try {
+ interfaceFile.writeTo(filer);
+ } catch (IOException e) {
+ throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
+ }
+
+ // Write configuration
+ JavaFile classFile = JavaFile.builder(packageName, configurationClassBuilder.build()).build();
+
+ try {
+ classFile.writeTo(filer);
+ } catch (IOException e) {
+ throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
+ }
+ }
+
+ // Get all generated configuration nodes
+ final List<ConfigurationNode> flattenConfig = roots.stream()
+ .map((ConfigurationDescription cfg) -> buildConfigForest(cfg, props))
+ .flatMap(Set::stream)
+ .collect(Collectors.toList());
+
+ // Generate Keys class
+ createKeysClass(packageForUtil, flattenConfig);
+
+ // Generate Selectors class
+ createSelectorsClass(packageForUtil, flattenConfig);
+
+ return true;
+ }
+
+ /**
+ * Create getters and setters for configuration class.
+ *
+ * @param configurationClassBuilder
+ * @param configurationInterfaceBuilder
+ * @param fieldName
+ * @param types
+ * @param valueAnnotation
+ */
+ private void createGettersAndSetter(
+ TypeSpec.Builder configurationClassBuilder,
+ TypeSpec.Builder configurationInterfaceBuilder,
+ String fieldName,
+ ConfigurationFieldTypes types,
+ Value valueAnnotation
+ ) {
+ MethodSpec interfaceGetMethod = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC, ABSTRACT)
+ .returns(types.getInterfaceGetMethodType())
+ .build();
+ configurationInterfaceBuilder.addMethod(interfaceGetMethod);
+
+ MethodSpec getMethod = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC, FINAL)
+ .returns(types.getGetMethodType())
+ .addStatement("return $L", fieldName)
+ .build();
+ configurationClassBuilder.addMethod(getMethod);
+
+ if (valueAnnotation != null) {
+ MethodSpec setMethod = MethodSpec
+ .methodBuilder(fieldName)
+ .addModifiers(PUBLIC, FINAL)
+ .addParameter(types.getUnwrappedType(), fieldName)
+ .addStatement("this.$L.change($L)", fieldName, fieldName)
+ .build();
+ configurationClassBuilder.addMethod(setMethod);
+ }
+ }
+
+ /**
+ * Get types for configuration classes generation.
+ * @param field
+ * @return Bundle with all types for configuration
+ */
+ private ConfigurationFieldTypes getTypes(final VariableElement field) {
+ TypeName getMethodType = null;
+ TypeName interfaceGetMethodType = null;
+
+ final TypeName baseType = TypeName.get(field.asType());
+
+ TypeName unwrappedType = baseType;
+ TypeName viewClassType = baseType;
+ TypeName initClassType = baseType;
+ TypeName changeClassType = baseType;
+
+ final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
+ if (confAnnotation != null) {
+ getMethodType = Utils.getConfigurationName((ClassName) baseType);
+ interfaceGetMethodType = Utils.getConfigurationInterfaceName((ClassName) baseType);
+
+ unwrappedType = getMethodType;
+ viewClassType = Utils.getViewName((ClassName) baseType);
+ initClassType = Utils.getInitName((ClassName) baseType);
+ changeClassType = Utils.getChangeName((ClassName) baseType);
+ }
+
+ final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+ if (namedConfigAnnotation != null) {
+ ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
+
+ viewClassType = Utils.getViewName((ClassName) baseType);
+ initClassType = Utils.getInitName((ClassName) baseType);
+ changeClassType = Utils.getChangeName((ClassName) baseType);
+
+ getMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
+ interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
+ }
+
+ final Value valueAnnotation = field.getAnnotation(Value.class);
+ if (valueAnnotation != null) {
+ ClassName dynPropClass = ClassName.get(DynamicProperty.class);
+ ClassName confValueClass = ClassName.get(ConfigurationValue.class);
+
+ TypeName genericType = baseType;
+
+ if (genericType.isPrimitive()) {
+ genericType = genericType.box();
+ }
+
+ getMethodType = ParameterizedTypeName.get(dynPropClass, genericType);
+ interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, genericType);
+ }
+
+ return new ConfigurationFieldTypes(getMethodType, unwrappedType, viewClassType, initClassType, changeClassType, interfaceGetMethodType);
+ }
+
+ /**
+ * Wrapper for configuration schema types.
+ */
+ private static class ConfigurationFieldTypes {
+ /** Field get method type. */
+ private final TypeName getMethodType;
+
+ /** Configuration type (if marked with @ConfigValue or @NamedConfig), or original type (if marked with @Value) */
+ private final TypeName unwrappedType;
+
+ /** VIEW object type. */
+ private final TypeName viewClassType;
+
+ /** INIT object type. */
+ private final TypeName initClassType;
+
+ /** CHANGE object type. */
+ private final TypeName changeClassType;
+
+ /** Get method type for public interface. */
+ private final TypeName interfaceGetMethodType;
+
+ public ConfigurationFieldTypes(TypeName getMethodType, TypeName unwrappedType, TypeName viewClassType, TypeName initClassType, TypeName changeClassType, TypeName interfaceGetMethodType) {
+ this.getMethodType = getMethodType;
+ this.unwrappedType = unwrappedType;
+ this.viewClassType = viewClassType;
+ this.initClassType = initClassType;
+ this.changeClassType = changeClassType;
+ this.interfaceGetMethodType = interfaceGetMethodType;
+ }
+
+ /** */
+ public TypeName getInterfaceGetMethodType() {
+ return interfaceGetMethodType;
+ }
+
+ /** */
+ public TypeName getGetMethodType() {
+ return getMethodType;
+ }
+
+ /** */
+ public TypeName getUnwrappedType() {
+ return unwrappedType;
+ }
+
+ /** */
+ public TypeName getViewClassType() {
+ return viewClassType;
+ }
+
+ /** */
+ public TypeName getInitClassType() {
+ return initClassType;
+ }
+
+ /** */
+ public TypeName getChangeClassType() {
+ return changeClassType;
+ }
+ }
+
+ /**
+ * Create copy-method for configuration class.
+ *
+ * @param configClass Configuration class name.
+ * @param configurationClassBuilder Configuration class builder.
+ */
+ private void createCopyMethod(ClassName configClass, TypeSpec.Builder configurationClassBuilder) {
+ MethodSpec copyMethod = MethodSpec.methodBuilder("copy")
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(DynamicConfiguration.class, "root")
+ .returns(configClass)
+ .addStatement("return new $T(this, root)", configClass)
+ .build();
+
+ configurationClassBuilder.addMethod(copyMethod);
+ }
+
+ /**
+ * Create configuration class constructors.
+ *
+ * @param configClass Configuration class name.
+ * @param configName Configuration name.
+ * @param configurationClassBuilder Configuration class builder.
+ * @param configuratorClassName Configurator (configuration wrapper) class name.
+ * @param constructorBodyBuilder Constructor body.
+ * @param copyConstructorBodyBuilder Copy constructor body.
+ */
+ private void createConstructors(
+ ClassName configClass,
+ String configName,
+ TypeSpec.Builder configurationClassBuilder,
+ ParameterizedTypeName configuratorClassName,
+ CodeBlock.Builder constructorBodyBuilder,
+ CodeBlock.Builder copyConstructorBodyBuilder
+ ) {
+ final MethodSpec constructorWithName = MethodSpec.constructorBuilder()
+ .addModifiers(PUBLIC)
+ .addParameter(String.class, "prefix")
+ .addParameter(String.class, "key")
+ .addParameter(boolean.class, "isNamed")
+ .addParameter(configuratorClassName, "configurator")
+ .addParameter(DynamicConfiguration.class, "root")
+ .addStatement("super(prefix, key, isNamed, configurator, root)")
+ .addCode(constructorBodyBuilder.build())
+ .build();
+ configurationClassBuilder.addMethod(constructorWithName);
+
+ final MethodSpec copyConstructor = MethodSpec.constructorBuilder()
+ .addModifiers(PRIVATE)
+ .addParameter(configClass, "base")
+ .addParameter(DynamicConfiguration.class, "root")
+ .addStatement("super(base.prefix, base.key, base.isNamed, base.configurator, root)")
+ .addCode(copyConstructorBodyBuilder.build())
+ .build();
+ configurationClassBuilder.addMethod(copyConstructor);
+
+ final MethodSpec emptyConstructor = MethodSpec.constructorBuilder()
+ .addModifiers(PUBLIC)
+ .addParameter(configuratorClassName, "configurator")
+ .addStatement("this($S, $S, false, configurator, null)", "", configName)
+ .build();
+
+ configurationClassBuilder.addMethod(emptyConstructor);
+ }
+
+ /**
+ * Create selectors.
+ *
+ * @param packageForUtil Package to place selectors class to.
+ * @param flattenConfig List of configuration nodes.
+ */
+ private void createSelectorsClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
+ ClassName selectorsClassName = ClassName.get(packageForUtil, "Selectors");
+
+ final TypeSpec.Builder selectorsClass = TypeSpec.classBuilder(selectorsClassName)
+ .superclass(BaseSelectors.class)
+ .addModifiers(PUBLIC, FINAL);
+
+ final CodeBlock.Builder selectorsStaticBlockBuilder = CodeBlock.builder();
+ selectorsStaticBlockBuilder.addStatement("$T publicLookup = $T.publicLookup()", MethodHandles.Lookup.class, MethodHandles.class);
+
+ selectorsStaticBlockBuilder.beginControlFlow("try");
+
+ // For every configuration node create selector (based on a method call chain)
+ for (ConfigurationNode configNode : flattenConfig) {
+ String regex = "([a-z])([A-Z]+)";
+ String replacement = "$1_$2";
+
+ // Selector variable name (like LOCAL_BASELINE_AUTO_ADJUST_ENABLED)
+ final String varName = configNode.getName()
+ .replaceAll(regex, replacement)
+ .toUpperCase()
+ .replace(".", "_");
+
+ TypeName type = configNode.getType();
+
+ if (Utils.isNamedConfiguration(type))
+ type = Utils.unwrapNamedListConfigurationClass(type);
+
+ StringBuilder methodCall = new StringBuilder();
+
+ ConfigurationNode current = configNode;
+ ConfigurationNode root = null;
+ int namedCount = 0;
+
+ // Walk from node up to the root and create a method call chain
+ while (current != null) {
+ boolean isNamed = false;
+
+ if (Utils.isNamedConfiguration(current.getType())) {
+ namedCount++;
+ isNamed = true;
+ }
+
+ if (current.getParent() != null) {
+ String newMethodCall = "." + current.getOriginalName() + "()";
+
+ // if config is named, then create a call with name parameter
+ if (isNamed)
+ newMethodCall += ".get(name" + (namedCount - 1) + ")";
+
+ methodCall.insert(0, newMethodCall);
+ } else
+ root = current;
+
+ current = current.getParent();
+ }
+
+ TypeName selectorRec = Utils.getParameterized(ClassName.get(Selector.class), root.getType(), type, configNode.getView(), configNode.getInit(), configNode.getChange());
+
+ if (namedCount > 0) {
+ final MethodSpec.Builder builder = MethodSpec.methodBuilder(varName);
+
+ for (int i = 0; i < namedCount; i++) {
+ builder.addParameter(String.class, "name" + i);
+ }
+
+ selectorsClass.addMethod(
+ builder
+ .returns(selectorRec)
+ .addModifiers(PUBLIC, STATIC, FINAL)
+ .addStatement("return (root) -> root$L", methodCall.toString())
+ .build()
+ );
+
+
+ // Build a list of parameters for statement
+ List<Object> params = new ArrayList<>();
+ params.add(MethodHandle.class);
+ params.add(varName);
+ params.add(selectorsClassName);
+ params.add(varName);
+ params.add(MethodType.class);
+ params.add(Selector.class);
+
+ // For every named config in call chain -- add String (name) parameter
+ for (int i = 0; i < namedCount; i++) {
+ params.add(String.class);
+ }
+
+ // Create a string for name parameters
+ final String nameStringParameters = IntStream.range(0, namedCount).mapToObj(i -> "$T.class").collect(Collectors.joining(","));
+
+ selectorsStaticBlockBuilder.addStatement("$T $L = publicLookup.findStatic($T.class, $S, $T.methodType($T.class, " + nameStringParameters + "))", params.toArray());
+
+ selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
+ }
+ else {
+ selectorsClass.addField(
+ FieldSpec.builder(selectorRec, varName)
+ .addModifiers(PUBLIC, STATIC, FINAL)
+ .initializer("(root) -> root$L", methodCall.toString())
+ .build()
+ );
+ selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
+ }
+ }
+
+ selectorsStaticBlockBuilder
+ .nextControlFlow("catch ($T e)", Exception.class)
+ .endControlFlow();
+
+ selectorsClass.addStaticBlock(selectorsStaticBlockBuilder.build());
+
+ JavaFile selectorsClassFile = JavaFile.builder(selectorsClassName.packageName(), selectorsClass.build()).build();
+ try {
+ selectorsClassFile.writeTo(filer);
+ }
+ catch (IOException e) {
+ throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Create keys class.
+ *
+ * @param packageForUtil Package to place keys class to.
+ * @param flattenConfig List of configuration nodes.
+ */
+ private void createKeysClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
+ final TypeSpec.Builder keysClass = TypeSpec.classBuilder("Keys").addModifiers(PUBLIC, FINAL);
+
+ for (ConfigurationNode node : flattenConfig) {
+ final String varName = node.getName().toUpperCase().replace(".", "_");
+ keysClass.addField(
+ FieldSpec.builder(String.class, varName)
+ .addModifiers(PUBLIC, STATIC, FINAL)
+ .initializer("$S", node.getName())
+ .build()
+ );
+ }
+
+ JavaFile keysClassFile = JavaFile.builder(packageForUtil, keysClass.build()).build();
+ try {
+ keysClassFile.writeTo(filer);
+ } catch (IOException e) {
+ throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Create VIEW, INIT and CHANGE classes and methods.
+ *
+ * @param packageName Configuration package name.
+ * @param fields List of configuration fields.
+ * @param schemaClassName Class name of schema.
+ * @param configurationClassBuilder Configuration class builder.
+ */
+ private void createPojoBindings(
+ String packageName,
+ List<VariableElement> fields,
+ ClassName schemaClassName,
+ TypeSpec.Builder configurationClassBuilder,
+ TypeSpec.Builder configurationInterfaceBuilder
+ ) {
+ final ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
+ final ClassName initClassName = Utils.getInitName(schemaClassName);
+ final ClassName changeClassName = Utils.getChangeName(schemaClassName);
+
+ ClassName dynConfClass = ClassName.get(DynamicConfiguration.class);
+ TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, initClassName, changeClassName);
+
+ configurationClassBuilder.superclass(dynConfViewClassType);
+
+ ClassName confTreeInterface = ClassName.get(ConfigurationTree.class);
+ TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
+
+ configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
+
+ try {
+ viewClassGenerator.create(packageName, viewClassTypeName, fields);
+ final MethodSpec toViewMethod = createToViewMethod(viewClassTypeName, fields);
+ configurationClassBuilder.addMethod(toViewMethod);
+ }
+ catch (IOException e) {
+ throw new ProcessorException("Failed to write class " + viewClassTypeName.toString(), e);
+ }
+
+ try {
+ changeClassGenerator.create(packageName, changeClassName, fields);
+ final MethodSpec changeMethod = createChangeMethod(changeClassName, fields);
+ configurationClassBuilder.addMethod(changeMethod);
+ }
+ catch (IOException e) {
+ throw new ProcessorException("Failed to write class " + changeClassName.toString(), e);
+ }
+
+ try {
+ initClassGenerator.create(packageName, initClassName, fields);
+ final MethodSpec initMethod = createInitMethod(initClassName, fields);
+ configurationClassBuilder.addMethod(initMethod);
+ }
+ catch (IOException e) {
+ throw new ProcessorException("Failed to write class " + initClassName.toString(), e);
+ }
+ }
+
+ /**
+ * Build configuration forest base on root configuration description and all processed configurations.
+ *
+ * @param root Root configuration description.
+ * @param props All configurations.
+ * @return All possible config trees.
+ */
+ private Set<ConfigurationNode> buildConfigForest(ConfigurationDescription root, Map<TypeName, ConfigurationDescription> props) {
+ Set<ConfigurationNode> res = new HashSet<>();
+ Deque<ConfigurationNode> propsStack = new LinkedList<>();
+
+ ConfigurationNode rootNode = new ConfigurationNode(root.getType(), root.getName(), root.getName(), root.getView(), root.getInit(), root.getChange(), null);
+
+ propsStack.addFirst(rootNode);
+
+ // Walk through the all fields of every node and build a tree of configuration (more like chain)
+ while (!propsStack.isEmpty()) {
+ final ConfigurationNode current = propsStack.pollFirst();
+
+ // Get configuration type
+ TypeName type = current.getType();
+
+ if (Utils.isNamedConfiguration(type))
+ type = Utils.unwrapNamedListConfigurationClass(current.getType());
+
+ final ConfigurationDescription configDesc = props.get(type);
+
+ // Get fields of configuration
+ final List<ConfigurationElement> propertiesList = configDesc.getFields();
+
+ if (current.getName() != null && !current.getName().isEmpty())
+ // Add current node to result
+ res.add(current);
+
+ for (ConfigurationElement property : propertiesList) {
+ String qualifiedName = property.getName();
+
+ if (current.getName() != null && !current.getName().isEmpty())
+ qualifiedName = current.getName() + "." + qualifiedName;
+
+ final ConfigurationNode newChainElement = new ConfigurationNode(
+ property.getType(),
+ qualifiedName,
+ property.getName(),
+ property.getView(),
+ property.getInit(),
+ property.getChange(),
+ current
+ );
+
+ boolean isNamedConfig = false;
+ if (property.getType() instanceof ParameterizedTypeName) {
+ final ParameterizedTypeName parameterized = (ParameterizedTypeName) property.getType();
+
+ if (parameterized.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+ isNamedConfig = true;
+ }
+
+ if (props.containsKey(property.getType()) || isNamedConfig)
+ // If it's not a leaf, add to stack
+ propsStack.add(newChainElement);
+ else
+ // otherwise, add to result
+ res.add(newChainElement);
+
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Create {@link org.apache.ignite.configuration.ConfigurationProperty#value} method for configuration class.
+ *
+ * @param type VIEW method type.
+ * @param variables List of VIEW object's fields.
+ * @return toView() method.
+ */
+ public MethodSpec createToViewMethod(TypeName type, List<VariableElement> variables) {
+ String args = variables.stream()
+ .map(v -> v.getSimpleName().toString() + ".value()")
+ .collect(Collectors.joining(", "));
+
+ final CodeBlock returnBlock = CodeBlock.builder()
+ .add("return new $T($L)", type, args)
+ .build();
+
+ return MethodSpec.methodBuilder("value")
+ .addModifiers(PUBLIC)
+ .addAnnotation(Override.class)
+ .returns(type)
+ .addStatement(returnBlock)
+ .build();
+ }
+
+ /**
+ * Create {@link org.apache.ignite.configuration.internal.Modifier#init(Object)} method (accepts INIT object) for configuration class.
+ *
+ * @param type INIT method type.
+ * @param variables List of INIT object's fields.
+ * @return Init method.
+ */
+ public MethodSpec createInitMethod(TypeName type, List<VariableElement> variables) {
+ final CodeBlock.Builder builder = CodeBlock.builder();
+
+ for (VariableElement variable : variables) {
+ final String name = variable.getSimpleName().toString();
+ builder.beginControlFlow("if (initial.$L() != null)", name);
+ builder.addStatement("$L.init(initial.$L())", name, name);
+ builder.endControlFlow();
+ }
+
+ return MethodSpec.methodBuilder("init")
+ .addModifiers(PUBLIC)
+ .addAnnotation(Override.class)
+ .addParameter(type, "initial")
+ .addCode(builder.build())
+ .build();
+ }
+
+ /**
+ * Create {@link org.apache.ignite.configuration.internal.Modifier#change(Object)} method (accepts CHANGE object) for configuration class.
+ *
+ * @param type CHANGE method type.
+ * @param variables List of CHANGE object's fields.
+ * @return Change method.
+ */
+ public MethodSpec createChangeMethod(TypeName type, List<VariableElement> variables) {
+ final CodeBlock.Builder builder = CodeBlock.builder();
+
+ for (VariableElement variable : variables) {
+ final Value valueAnnotation = variable.getAnnotation(Value.class);
+ if (valueAnnotation != null && valueAnnotation.immutable())
+ continue;
+
+ final String name = variable.getSimpleName().toString();
+ builder.beginControlFlow("if (changes.$L() != null)", name);
+ builder.addStatement("$L.changeWithoutValidation(changes.$L())", name, name);
+ builder.endControlFlow();
+ }
+
+ return MethodSpec.methodBuilder("changeWithoutValidation")
+ .addModifiers(PUBLIC)
+ .addAnnotation(Override.class)
+ .addParameter(type, "changes")
+ .addCode(builder.build())
+ .build();
+ }
+
+ /** {@inheritDoc} */
+ @Override public Set<String> getSupportedAnnotationTypes() {
+ return Collections.singleton(Config.class.getCanonicalName());
+ }
+
+ /** {@inheritDoc} */
+ @Override public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.RELEASE_8;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java
new file mode 100644
index 0000000..90cd881
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Annotation processing exception.
+ */
+public class ProcessorException extends RuntimeException {
+ /** Constructor. */
+ public ProcessorException(String message) {
+ super(message);
+ }
+
+ /** Constructor. */
+ public ProcessorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
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
new file mode 100644
index 0000000..a1e3687
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+
+/**
+ * Annotation processing utilities.
+ */
+public class Utils {
+ /** Private constructor. */
+ private Utils() {
+ }
+
+ /**
+ * Create constructor for
+ *
+ * @param fieldSpecs List of fields.
+ * @return Constructor method.
+ */
+ public static MethodSpec createConstructor(List<FieldSpec> fieldSpecs) {
+ final MethodSpec.Builder builder = MethodSpec.constructorBuilder();
+
+ for (FieldSpec field : fieldSpecs) {
+ builder.addParameter(field.type, field.name);
+ builder.addStatement("this.$L = $L", field.name, field.name);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Create getters for fields.
+ *
+ * @param fieldSpecs List of fields.
+ * @return List of getter methods.
+ */
+ public static List<MethodSpec> createGetters(List<FieldSpec> fieldSpecs) {
+ return fieldSpecs.stream().map(field ->
+ MethodSpec.methodBuilder(field.name)
+ .returns(field.type)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addStatement("return $L", field.name)
+ .build()).collect(Collectors.toList()
+ );
+ }
+
+ /**
+ * Create builder-style setters.
+ *
+ * @param fieldSpecs List of fields.
+ * @return List of setter methods.
+ */
+ public static List<MethodSpec> createBuildSetters(List<FieldSpec> fieldSpecs) {
+ return fieldSpecs.stream().map(field -> {
+ return MethodSpec.methodBuilder("with" + field.name)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addStatement("this.$L = $L", field.name, field.name)
+ .build();
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * Create '{@code new SomeObject(arg1, arg2, ..., argN)}' code block.
+ *
+ * @param type Type of the new object.
+ * @param fieldSpecs List of arguments.
+ * @return New object code block.
+ */
+ public static CodeBlock newObject(TypeName type, List<VariableElement> fieldSpecs) {
+ String args = fieldSpecs.stream().map(f -> f.getSimpleName().toString()).collect(Collectors.joining(", "));
+ return CodeBlock.builder()
+ .add("new $T($L)", type, args)
+ .build();
+ }
+
+ /**
+ * Get class with parameters, boxing them if necessary.
+ *
+ * @param clz Generic class.
+ * @param types Generic parameters.
+ * @return Parameterized type.
+ */
+ public static ParameterizedTypeName getParameterized(ClassName clz, TypeName... types) {
+ types = Arrays.stream(types).map(t -> {
+ if (t.isPrimitive())
+ t = t.box();
+ return t;
+ }).toArray(TypeName[]::new);
+ return ParameterizedTypeName.get(clz, types);
+ }
+
+ /**
+ * Get {@link ClassName} for configuration class.
+ *
+ * @param schemaClassName Configuration schema ClassName.
+ * @return Configuration ClassName.
+ */
+ public static ClassName getConfigurationName(ClassName schemaClassName) {
+ return ClassName.get(
+ schemaClassName.packageName(),
+ schemaClassName.simpleName().replace("Schema", "Impl")
+ );
+ }
+
+ /**
+ * Get {@link ClassName} for configuration class' public interface.
+ *
+ * @param schemaClassName Configuration schema ClassName.
+ * @return Configuration's public interface ClassName.
+ */
+ public static ClassName getConfigurationInterfaceName(ClassName schemaClassName) {
+ return ClassName.get(
+ schemaClassName.packageName(),
+ schemaClassName.simpleName().replace("Schema", "")
+ );
+ }
+
+ /**
+ * Get {@link ClassName} for configuration VIEW object class.
+ *
+ * @param schemaClassName Configuration schema ClassName.
+ * @return Configuration VIEW object ClassName.
+ */
+ public static ClassName getViewName(ClassName schemaClassName) {
+ return ClassName.get(
+ schemaClassName.packageName(),
+ schemaClassName.simpleName().replace("ConfigurationSchema", "")
+ );
+ }
+
+ /**
+ * Get {@link ClassName} for configuration INIT object class.
+ *
+ * @param schemaClassName Configuration schema ClassName.
+ * @return Configuration INIT object ClassName.
+ */
+ public static ClassName getInitName(ClassName schemaClassName) {
+ return ClassName.get(
+ schemaClassName.packageName(),
+ "Init" + schemaClassName.simpleName().replace("ConfigurationSchema", "")
+ );
+ }
+
+ /**
+ * Get {@link ClassName} for configuration CHANGE object class.
+ *
+ * @param schemaClassName Configuration schema ClassName.
+ * @return Configuration CHANGE object ClassName.
+ */
+ public static ClassName getChangeName(ClassName schemaClassName) {
+ return ClassName.get(
+ schemaClassName.packageName(),
+ "Change" + schemaClassName.simpleName().replace("ConfigurationSchema", "")
+ );
+ }
+
+ /**
+ * Check whether type is {@link NamedListConfiguration}.
+ *
+ * @param type Type.
+ * @return {@code true} if type is {@link NamedListConfiguration}.
+ */
+ public static boolean isNamedConfiguration(TypeName type) {
+ if (type instanceof ParameterizedTypeName) {
+ ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
+
+ if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get {@code DynamicConfiguration} inside of the named configuration.
+ *
+ * @param type Type name.
+ * @return {@link DynamicConfiguration} class name.
+ */
+ public static TypeName unwrapNamedListConfigurationClass(TypeName type) {
+ if (type instanceof ParameterizedTypeName) {
+ ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
+
+ if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+ return parameterizedTypeName.typeArguments.get(1);
+ }
+
+ throw new ProcessorException(type + " is not a NamedListConfiguration class");
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java
new file mode 100644
index 0000000..491485c
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.pojo;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+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.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * CHANGE object class generator.
+ */
+public class ChangeClassGenerator extends ClassGenerator {
+ /** Constructor. */
+ public ChangeClassGenerator(ProcessingEnvironment env) {
+ super(env);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected FieldMapping mapField(VariableElement field) {
+ final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+ final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+ final Value valueAnnotation = field.getAnnotation(Value.class);
+
+ if (valueAnnotation != null && valueAnnotation.immutable())
+ return null;
+
+ final TypeMirror type = field.asType();
+ String name = field.getSimpleName().toString();
+
+ TypeName fieldType = TypeName.get(type);
+
+ if (fieldType.isPrimitive())
+ fieldType = fieldType.box();
+
+ if (namedConfigAnnotation != null || configAnnotation != null) {
+ ClassName confClass = (ClassName) fieldType;
+ fieldType = Utils.getChangeName(confClass);
+
+ if (namedConfigAnnotation != null)
+ fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+ }
+
+ final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE).build();
+
+ return new FieldMapping(field, fieldSpec);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+ final String name = field.name;
+ final String methodName = name.substring(0, 1).toUpperCase() + name.substring(1);
+ return MethodSpec.methodBuilder("with" + methodName)
+ .returns(clazz)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addParameter(field.type, name)
+ .addStatement("this.$L = $L", name, name)
+ .addStatement("return this")
+ .build();
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java
new file mode 100644
index 0000000..0ec13df
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java
@@ -0,0 +1,127 @@
+/*
+ * 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.pojo;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * Base POJO generator
+ */
+public abstract class ClassGenerator {
+ /** Processing environment. */
+ protected final ProcessingEnvironment env;
+
+ /** Annotation processing filer. */
+ private final Filer filer;
+
+ /** Constructor. */
+ public ClassGenerator(ProcessingEnvironment env) {
+ this.env = env;
+ this.filer = env.getFiler();
+ }
+
+ /**
+ * Create class.
+ *
+ * @param packageName Package name for class.
+ * @param className Class name.
+ * @param fields List of fields.
+ * @throws IOException If failed to write class file.
+ */
+ public final void create(String packageName, ClassName className, List<VariableElement> fields) throws IOException {
+ TypeSpec.Builder classBuilder = TypeSpec
+ .classBuilder(className)
+ .addSuperinterface(Serializable.class)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+ List<FieldMapping> fieldMappings = fields.stream().map(this::mapField).filter(Objects::nonNull).collect(Collectors.toList());
+
+ generate(classBuilder, packageName, className, fieldMappings);
+
+ final TypeSpec viewClass = classBuilder.build();
+ JavaFile classFile = JavaFile.builder(packageName, viewClass).build();
+ classFile.writeTo(filer);
+ }
+
+ /**
+ * Generate class fields, methods and constructor.
+ *
+ * @param classBuilder Class builder.
+ * @param packageName Package name.
+ * @param className Class name.
+ * @param fieldMappings Fields' mappings.
+ */
+ protected void generate(TypeSpec.Builder classBuilder, String packageName, ClassName className, List<FieldMapping> fieldMappings) {
+ List<FieldSpec> fieldSpecs = fieldMappings.stream().map(FieldMapping::getFieldSpec).collect(Collectors.toList());
+
+ List<MethodSpec> methodSpecs = fieldSpecs.stream().map(field -> mapMethod(className, field)).filter(Objects::nonNull).collect(Collectors.toList());
+
+ classBuilder.addFields(fieldSpecs);
+ classBuilder.addMethods(methodSpecs);
+
+ final MethodSpec constructor = createConstructor(fieldSpecs);
+ if (constructor != null)
+ classBuilder.addMethod(constructor);
+
+ final List<MethodSpec> getters = Utils.createGetters(fieldSpecs);
+
+ classBuilder.addMethods(getters);
+ }
+
+ /**
+ * Create {@link FieldSpec} from {@link VariableElement}.
+ *
+ * @param field Configuration class field.
+ * @return Mapping.
+ */
+ protected abstract FieldMapping mapField(VariableElement field);
+
+ /**
+ * Create access methods for field.
+ *
+ * @param clazz Method return type.
+ * @param field Configuration class field.
+ * @return Method specs.
+ */
+ protected abstract MethodSpec mapMethod(ClassName clazz, FieldSpec field);
+
+ /**
+ * Create constructor from fields.
+ *
+ * @param fields Configuration fields.
+ * @return If null, constructor won't be created.
+ */
+ protected MethodSpec createConstructor(List<FieldSpec> fields) {
+ return null;
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java
new file mode 100644
index 0000000..c0b216e
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java
@@ -0,0 +1,52 @@
+/*
+ * 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.pojo;
+
+import javax.lang.model.element.VariableElement;
+import com.squareup.javapoet.FieldSpec;
+
+/**
+ * Mapping from {@link VariableElement} to {@link FieldSpec}
+ */
+public class FieldMapping {
+ /** Variable element. */
+ private final VariableElement variableElement;
+
+ /** Field spec. */
+ private final FieldSpec fieldSpec;
+
+ /** Constructor. */
+ public FieldMapping(VariableElement variableElement, FieldSpec fieldSpec) {
+ this.variableElement = variableElement;
+ this.fieldSpec = fieldSpec;
+ }
+
+ /**
+ * Get varaible element.
+ */
+ public VariableElement getVariableElement() {
+ return variableElement;
+ }
+
+ /**
+ * Get field spec.
+ */
+ public FieldSpec getFieldSpec() {
+ return fieldSpec;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java
new file mode 100644
index 0000000..6f5e784
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java
@@ -0,0 +1,83 @@
+/*
+ * 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.pojo;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * INIT object class generator.
+ */
+public class InitClassGenerator extends ClassGenerator {
+ /** Constructor. */
+ public InitClassGenerator(ProcessingEnvironment env) {
+ super(env);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected FieldMapping mapField(VariableElement field) {
+ final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+ final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+
+ final TypeMirror type = field.asType();
+ String name = field.getSimpleName().toString();
+
+ TypeName fieldType = TypeName.get(type);
+
+ if (fieldType.isPrimitive())
+ fieldType = fieldType.box();
+
+ if (namedConfigAnnotation != null || configAnnotation != null) {
+ ClassName confClass = (ClassName) fieldType;
+ fieldType = Utils.getInitName(confClass);
+
+ if (namedConfigAnnotation != null)
+ fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+
+ }
+
+ final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE).build();
+
+ return new FieldMapping(field, fieldSpec);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+ final String name = field.name;
+ final String methodName = name.substring(0, 1).toUpperCase() + name.substring(1);
+ return MethodSpec.methodBuilder("with" + methodName)
+ .returns(clazz)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addParameter(field.type, name)
+ .addStatement("this.$L = $L", name, name)
+ .addStatement("return this")
+ .build();
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.java
new file mode 100644
index 0000000..a192a04
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.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.processor.internal.pojo;
+
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * VIEW object class generator.
+ */
+public class ViewClassGenerator extends ClassGenerator {
+ /** Constructor. */
+ public ViewClassGenerator(ProcessingEnvironment env) {
+ super(env);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected FieldMapping mapField(VariableElement field) {
+ final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+ final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+
+ final TypeMirror type = field.asType();
+ String name = field.getSimpleName().toString();
+
+ TypeName fieldType = TypeName.get(type);
+
+ if (fieldType.isPrimitive())
+ fieldType = fieldType.box();
+
+ if (namedConfigAnnotation != null || configAnnotation != null) {
+ ClassName confClass = (ClassName) fieldType;
+ fieldType = Utils.getViewName(confClass);
+
+ if (namedConfigAnnotation != null)
+ fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+ }
+
+ final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE, Modifier.FINAL).build();
+
+ return new FieldMapping(field, fieldSpec);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MethodSpec createConstructor(List<FieldSpec> fields) {
+ return Utils.createConstructor(fields);
+ }
+}
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
new file mode 100644
index 0000000..6b3cc44
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.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.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/main/resources/META-INF/services/javax.annotation.processing.Processor b/modules/configuration-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..cd4d259
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+org.apache.ignite.configuration.processor.internal.Processor
\ No newline at end of file
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
new file mode 100644
index 0000000..a089a50
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import com.google.common.base.Functions;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import com.squareup.javapoet.ClassName;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.tools.JavaFileObject;
+
+import static com.google.testing.compile.Compiler.javac;
+
+/**
+ * Base class for configuration annotation processor tests.
+ */
+public class AbstractProcessorTest {
+
+ /**
+ * Compile set of classes
+ * @param schemaClasses Configuration schema classes.
+ * @return Result of batch compilation.
+ */
+ protected static BatchCompilation batchCompile(ClassName... schemaClasses) {
+ List<String> fileNames = Arrays.stream(schemaClasses)
+ .map(name -> {
+ final String folderName = name.packageName().replaceAll("\\.", File.separator);
+ return String.format("%s%c%s.java", folderName, File.separatorChar, name.simpleName());
+ })
+ .collect(Collectors.toList());
+
+ final List<JavaFileObject> fileObjects = fileNames.stream().map(JavaFileObjects::forResource).collect(Collectors.toList());
+
+ final Compilation compilation = javac()
+ .withProcessors(new Processor())
+ .compile(fileObjects);
+
+ return new BatchCompilation(Arrays.asList(schemaClasses), compilation);
+ }
+
+ /**
+ * Get {@link ConfigSet} object from generated classes.
+ * @param clazz Configuration schema ClassName.
+ * @param generatedClasses Map with all generated classes.
+ * @return ConfigSet.
+ */
+ protected static ConfigSet getConfigSet(ClassName clazz, final Map<ClassName, JavaFileObject> generatedClasses) {
+ final ClassName configurationName = Utils.getConfigurationName(clazz);
+ final ClassName viewName = Utils.getViewName(clazz);
+ final ClassName initName = Utils.getInitName(clazz);
+ final ClassName changeName = Utils.getChangeName(clazz);
+
+ final JavaFileObject configurationFileObject = generatedClasses.get(configurationName);
+ final JavaFileObject viewClass = generatedClasses.get(viewName);
+ final JavaFileObject initClass = generatedClasses.get(initName);
+ final JavaFileObject changeClass = generatedClasses.get(changeName);
+
+ return new ConfigSet(configurationFileObject, viewClass, initClass, changeClass);
+ }
+
+ /**
+ * Get {@link ClassName} object from generated file path.
+ * @param fileName File path.
+ * @return ClassName.
+ */
+ protected static ClassName fromGeneratedFilePath(String fileName) {
+ return fromFilePath(fileName.replace("/SOURCE_OUTPUT/", ""));
+ }
+
+ /**
+ * Get {@link ClassName} object from file path.
+ * @param fileName File path.
+ * @return ClassName.
+ */
+ protected static ClassName fromFilePath(String fileName) {
+ int slashIdx = fileName.lastIndexOf("/");
+ int dotJavaIdx = fileName.lastIndexOf(".java");
+
+ String packageName = fileName.substring(0, slashIdx).replaceAll("/", ".");
+
+ final String className = fileName.substring(slashIdx + 1, dotJavaIdx);
+
+ return ClassName.get(packageName, className);
+ }
+
+ /**
+ * Result of multiple compiled schema classes.
+ */
+ protected static class BatchCompilation {
+ /** Generated source files. */
+ private final List<JavaFileObject> generatedSources;
+
+ /** Generated classes mapped by config schema class name. */
+ private final Map<ClassName, JavaFileObject> generatedClasses;
+
+ /** Config class sets by config schema class name. */
+ private final Map<ClassName, ConfigSet> classSets;
+
+ /** Compilation status. */
+ private final Compilation compilationStatus;
+
+ /**
+ * Constructor.
+ * @param schemaClasses List of schema class names.
+ * @param compilation Compilation status.
+ */
+ public BatchCompilation(List<ClassName> schemaClasses, Compilation compilation) {
+ this.compilationStatus = compilation;
+
+ generatedSources = compilation.generatedSourceFiles();
+
+ generatedClasses = generatedSources.stream()
+ .collect(Collectors.toMap(object -> fromGeneratedFilePath(object.getName()), Functions.identity()));
+
+ classSets = schemaClasses.stream().collect(
+ Collectors.toMap(name -> name, name -> getConfigSet(name, generatedClasses))
+ );
+ }
+
+ /**
+ * Get config class set by schema class name.
+ * @param schemaClass Schema class name.
+ * @return Config class set.
+ */
+ public ConfigSet getBySchema(ClassName schemaClass) {
+ return classSets.get(schemaClass);
+ }
+
+ /**
+ * Get compilation status.
+ * @return Compilation status.
+ */
+ public Compilation getCompilationStatus() {
+ return compilationStatus;
+ }
+
+ /**
+ * Get all generated source files.
+ * @return Generated source files.
+ */
+ public List<JavaFileObject> generated() {
+ return generatedSources;
+ }
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
new file mode 100644
index 0000000..c5966ab
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.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.processor.internal;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.tools.JavaFileObject;
+import org.apache.commons.io.IOUtils;
+import spoon.Launcher;
+
+/**
+ * Wrapper for generated classes of the configuration schema.
+ */
+public class ConfigSet {
+ /** Configuration class. */
+ private final JavaFileObject configurationClass;
+
+ /** VIEW class. */
+ private final JavaFileObject viewClass;
+
+ /** INIT class. */
+ private final JavaFileObject initClass;
+
+ /** CHANGE class. */
+ private final JavaFileObject changeClass;
+
+ /** Parsed configuration class. */
+ private final ParsedClass conf;
+
+ /** Parsed VIEW class. */
+ private final ParsedClass view;
+
+ /** Parsed INIT class. */
+ private final ParsedClass init;
+
+ /** Parsed CHANGE class. */
+ private final ParsedClass change;
+
+ /** Constructor. */
+ public ConfigSet(JavaFileObject configurationClass, JavaFileObject viewClass, JavaFileObject initClass, JavaFileObject changeClass) {
+ this.configurationClass = configurationClass;
+ this.viewClass = viewClass;
+ this.initClass = initClass;
+ this.changeClass = changeClass;
+
+ if (configurationClass != null)
+ this.conf = parse(configurationClass);
+ else
+ this.conf = null;
+
+ if (viewClass != null)
+ this.view = parse(viewClass);
+ else
+ this.view = null;
+
+ if (initClass != null)
+ this.init = parse(initClass);
+ else
+ this.init = null;
+
+ if (changeClass != null)
+ this.change = parse(changeClass);
+ else
+ this.change = null;
+ }
+
+ /**
+ * Parse source file.
+ * @param clz Class file object.
+ * @return Parsed class.
+ */
+ private ParsedClass parse(JavaFileObject clz) {
+ String classFileContent;
+ try {
+ classFileContent = IOUtils.toString(clz.openInputStream(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse class: " + e.getMessage(), e);
+ }
+
+ return new ParsedClass(Launcher.parseClass(classFileContent));
+ }
+
+ /**
+ * @return {@code true} if all required classes were generated.
+ */
+ public boolean allGenerated() {
+ return configurationClass != null && viewClass != null && initClass != null && changeClass != null;
+ }
+
+ /** */
+ public ParsedClass getConfigurationClass() {
+ return conf;
+ }
+
+ /** */
+ public ParsedClass getViewClass() {
+ return view;
+ }
+
+ /** */
+ public ParsedClass getInitClass() {
+ return init;
+ }
+
+ /** */
+ public ParsedClass getChangeClass() {
+ return change;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java
new file mode 100644
index 0000000..7b729fd
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import spoon.reflect.reference.CtFieldReference;
+
+/**
+ * Hamcrest matcher that tests class for field existence.
+ */
+public class HasFieldMatcher extends BaseMatcher<ParsedClass> {
+ /** Name of the expected field. */
+ private String fieldName;
+
+ /** Type of the expected field. */
+ private String fieldType;
+
+ /**
+ * Constructor.
+ * @param fieldName Name of the expected field.
+ * @param fieldType Type of the expected field.
+ */
+ private HasFieldMatcher(String fieldName, String fieldType) {
+ this.fieldName = fieldName;
+ this.fieldType = fieldType;
+ }
+
+ /**
+ * Create matcher for fields with types.
+ * @param arguments Array of field names and field types, paired.
+ * @return Matcher.
+ */
+ public static BaseMatcher<ParsedClass> hasFields(String... arguments) {
+ if (arguments.length % 2 != 0)
+ throw new RuntimeException("Number of field names should be equal to number of field types");
+
+ List<HasFieldMatcher> matcherList = new ArrayList<>();
+
+ for (int i = 0; i < arguments.length; i+=2)
+ matcherList.add(new HasFieldMatcher(arguments[i], arguments[i + 1]));
+
+ return new BaseMatcher<ParsedClass>() {
+ /** Currently used matcher. */
+ int currentMatcher = 0;
+
+ /** {@inheritDoc} */
+ @Override public void describeTo(Description description) {
+ matcherList.get(currentMatcher).describeTo(description);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeMismatch(Object item, Description description) {
+ matcherList.get(currentMatcher).describeMismatch(item, description);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean matches(Object o) {
+ for (int i = 0; i < matcherList.size(); i++) {
+ currentMatcher = i;
+ if (!matcherList.get(i).matches(o))
+ return false;
+ }
+
+ return true;
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean matches(Object o) {
+ if (!(o instanceof ParsedClass))
+ return false;
+
+ ParsedClass cls = (ParsedClass) o;
+
+ final Map<String, CtFieldReference<?>> fields = cls.getFields();
+ final CtFieldReference<?> field = fields.get(fieldName);
+
+ if (field == null)
+ return false;
+
+ return field.getType().getQualifiedName().equals(fieldType);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeTo(Description description) {
+ description.appendText(String.format("has field \"%s\" with type \"%s\"", fieldName, fieldType));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeMismatch(Object item, Description description) {
+ if (!(item instanceof ParsedClass)) {
+ description.appendText("is not ParsedClass instance");
+ return;
+ }
+
+ ParsedClass cls = (ParsedClass) item;
+
+ final Map<String, CtFieldReference<?>> fields = cls.getFields();
+ final CtFieldReference<?> field = fields.get(fieldName);
+
+ if (field == null) {
+ description.appendText("doesn't have field \"" + fieldName + "\"");
+ return;
+ }
+
+ final String actualFieldType = field.getType().getQualifiedName();
+ if (!actualFieldType.equals(fieldType))
+ description.appendText(String.format("\"%s\" has incorrect type \"%s\"", fieldName, actualFieldType));
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java
new file mode 100644
index 0000000..385a280
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import spoon.reflect.declaration.CtMethod;
+
+/**
+ * Hamcrest matcher that tests class for method existence.
+ */
+public class HasMethodMatcher extends BaseMatcher<ParsedClass> {
+ /** Name of the expected field. */
+ private String methodName;
+
+ /** Type of the expected field. */
+ private String methodReturnType;
+
+ /** Constructor. */
+ public HasMethodMatcher(String methodName, String methodReturnType) {
+ this.methodName = methodName;
+ this.methodReturnType = methodReturnType;
+ }
+
+ /**
+ * Create matcher for fields with types.
+ * @param arguments Array of field names and field types, paired.
+ * @return Matcher.
+ */
+ public static BaseMatcher<ParsedClass> hasMethods(String... arguments) {
+ if (arguments.length % 2 != 0)
+ throw new RuntimeException("Number of method names should be equal to number of method return types");
+
+ List<HasMethodMatcher> matcherList = new ArrayList<>();
+
+ for (int i = 0; i < arguments.length; i+=2)
+ matcherList.add(new HasMethodMatcher(arguments[i], arguments[i + 1]));
+
+ return new BaseMatcher<ParsedClass>() {
+ /** Currently used matcher. */
+ int currentMatcher = 0;
+
+ /** {@inheritDoc} */
+ @Override public void describeTo(Description description) {
+ matcherList.get(currentMatcher).describeTo(description);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeMismatch(Object item, Description description) {
+ matcherList.get(currentMatcher).describeMismatch(item, description);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean matches(Object o) {
+ for (int i = 0; i < matcherList.size(); i++) {
+ currentMatcher = i;
+ if (!matcherList.get(i).matches(o))
+ return false;
+ }
+
+ return true;
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean matches(Object o) {
+ if (!(o instanceof ParsedClass))
+ return false;
+
+ ParsedClass cls = (ParsedClass) o;
+
+ final Map<String, CtMethod<?>> methods = cls.getMethods();
+ final CtMethod<?> method = methods.get(methodName);
+
+ if (method == null)
+ return false;
+
+ return method.getType().getQualifiedName().equals(methodReturnType);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeTo(Description description) {
+ description.appendText(String.format("has method \"%s\" with return type \"%s\"", methodName, methodReturnType));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void describeMismatch(Object item, Description description) {
+ if (!(item instanceof ParsedClass)) {
+ description.appendText("is not ParsedClass instance");
+ return;
+ }
+
+ ParsedClass cls = (ParsedClass) item;
+
+ final Map<String, CtMethod<?>> methods = cls.getMethods();
+ final CtMethod<?> method = methods.get(methodName);
+
+ if (method == null) {
+ description.appendText("doesn't have method \"" + methodName + "\"");
+ return;
+ }
+
+ final String actualMethodReturnType = method.getType().getQualifiedName();
+
+ if (!actualMethodReturnType.equals(methodReturnType))
+ description.appendText(String.format("\"%s\" has incorrect return type \"%s\"", methodName, actualMethodReturnType));
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java
new file mode 100644
index 0000000..15502b7
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import com.google.common.base.Functions;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtConstructor;
+import spoon.reflect.declaration.CtExecutable;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.declaration.CtParameter;
+import spoon.reflect.reference.CtFieldReference;
+import spoon.reflect.reference.CtReference;
+
+/**
+ * Convenient wrapper for parsed source file.
+ * Method and constructor signatures are represented by string containing name and list of argument types.
+ * E.g.: "foo(java.lang.Integer, java.lang.String)" for method {@code public Object foo(Integer a, String b)}.
+ */
+public class ParsedClass {
+ /** Class info. */
+ private final CtClass<?> cls;
+
+ /** Class fields by name. */
+ private final Map<String, CtFieldReference<?>> fields;
+
+ /** Class methods by signature. */
+ private final Map<String, CtMethod<?>> methods;
+
+ /** Class constructors by signature. */
+ private final Map<String, CtConstructor<?>> constructors;
+
+ /**
+ * Constructor.
+ * @param cls Class info.
+ */
+ public ParsedClass(CtClass<?> cls) {
+ this.cls = cls;
+
+ this.fields = cls.getAllFields()
+ .stream()
+ .collect(Collectors.toMap(CtReference::getSimpleName, Functions.identity()));
+
+ this.methods = cls.getMethods()
+ .stream()
+ .collect(Collectors.toMap(this::getMethodName, Functions.identity()));
+
+ this.constructors = cls.getConstructors()
+ .stream()
+ .collect(Collectors.toMap(this::getMethodName, Functions.identity()));
+ }
+
+ /**
+ * Get method name from method meta info.
+ * @param method Method meta info.
+ * @return Method name.
+ */
+ private String getMethodName(CtExecutable<?> method) {
+ final List<CtParameter<?>> parameters = method.getParameters();
+
+ final String params = parameters.stream().map(parameter ->
+ parameter.getType().getQualifiedName()).collect(Collectors.joining(", ")
+ );
+
+ return method.getSimpleName() + "(" + params + ")";
+ }
+
+ /**
+ * Get fields.
+ * @return Fields.
+ */
+ public Map<String, CtFieldReference<?>> getFields() {
+ return fields;
+ }
+
+ /**
+ * Get methods.
+ * @return Methods.
+ */
+ public Map<String, CtMethod<?>> getMethods() {
+ return methods;
+ }
+
+ /**
+ * Get constructors.
+ * @return Constructors.
+ */
+ public Map<String, CtConstructor<?>> getConstructors() {
+ return constructors;
+ }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java
new file mode 100644
index 0000000..dc361dc
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import com.google.testing.compile.CompilationSubject;
+import com.squareup.javapoet.ClassName;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import static org.apache.ignite.configuration.processor.internal.HasFieldMatcher.hasFields;
+import static org.apache.ignite.configuration.processor.internal.HasMethodMatcher.hasMethods;
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test for basic code generation scenarios.
+ */
+public class ProcessorTest extends AbstractProcessorTest {
+ /**
+ * The simplest test for code generation.
+ */
+ @Test
+ public void test() {
+ final String packageName = "org.apache.ignite.configuration.processor.internal";
+
+ final ClassName testConfigurationSchema = ClassName.get(packageName, "TestConfigurationSchema");
+
+ final BatchCompilation batch = batchCompile(testConfigurationSchema);
+
+ CompilationSubject.assertThat(batch.getCompilationStatus()).succeeded();
+
+ assertEquals(7, batch.generated().size());
+
+ final ConfigSet classSet = batch.getBySchema(testConfigurationSchema);
+
+ assertTrue(classSet.allGenerated());
+
+ MatcherAssert.assertThat(
+ classSet.getViewClass(),
+ hasFields(
+ "value1", Types.STRING,
+ "primitiveLong", Types.LONG,
+ "boxedLong", Types.LONG,
+ "primitiveInt", Types.INT,
+ "boxedInt", Types.INT
+ )
+ );
+
+ MatcherAssert.assertThat(
+ classSet.getViewClass(),
+ hasMethods(
+ "value1()", Types.STRING,
+ "primitiveLong()", Types.LONG,
+ "boxedLong()", Types.LONG,
+ "primitiveInt()", Types.INT,
+ "boxedInt()", Types.INT
+ )
+ );
+
+ MatcherAssert.assertThat(
+ classSet.getInitClass(),
+ hasFields(
+ "value1", Types.STRING,
+ "primitiveLong", Types.LONG,
+ "boxedLong", Types.LONG,
+ "primitiveInt", Types.INT,
+ "boxedInt", Types.INT
+ )
+ );
+
+ String initTypeName = Types.typeName(packageName, "InitTest");
+
+ MatcherAssert.assertThat(
+ classSet.getInitClass(),
+ hasMethods(
+ "value1()", Types.STRING,
+ "primitiveLong()", Types.LONG,
+ "boxedLong()", Types.LONG,
+ "primitiveInt()", Types.INT,
+ "boxedInt()", Types.INT,
+ "withValue1(java.lang.String)", initTypeName,
+ "withPrimitiveLong(java.lang.Long)", initTypeName,
+ "withBoxedLong(java.lang.Long)", initTypeName,
+ "withPrimitiveInt(java.lang.Integer)", initTypeName,
+ "withBoxedInt(java.lang.Integer)", initTypeName
+ )
+ );
+ }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java
new file mode 100644
index 0000000..0d42050
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java
@@ -0,0 +1,32 @@
+package org.apache.ignite.configuration.processor.internal;
+
+/**
+ * Type names for testing.
+ */
+public class Types {
+ /** Java lang package name. */
+ private static final String PKG_JAVA_LANG = "java.lang.";
+
+ /** Integer. */
+ public static final String INT = PKG_JAVA_LANG + "Integer";
+
+ /** Long. */
+ public static final String LONG = PKG_JAVA_LANG + "Long";
+
+ /** String. */
+ public static final String STRING = PKG_JAVA_LANG + "String";
+
+ /** Double. */
+ public static final String DOUBLE = PKG_JAVA_LANG + "Double";
+
+ /**
+ * Get type name by package name and class name.
+ * @param packageName Package name.
+ * @param className Class name.
+ * @return Type name.
+ */
+ public static String typeName(String packageName, String className) {
+ return String.format("%s.%s", packageName, className);
+ }
+
+}
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
new file mode 100644
index 0000000..ef3c02b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+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.
+ */
+@Config
+public class AutoAdjustConfigurationSchema {
+ /** Timeout. */
+ @Value
+ @Min(value = 0, message = "Minimal is 0")
+ @Validate(value = AutoAdjustValidator.class, message = "a")
+ @Validate(value = AutoAdjustValidator2.class, message = "b")
+ private long timeout;
+
+ /** Enabled. */
+ @Value
+ private boolean enabled;
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java
new file mode 100644
index 0000000..bc6e314
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+
+/**
+ * Test baseline configuration schema.
+ */
+@Config
+public class BaselineConfigurationSchema {
+ /** Auto adjust */
+ @ConfigValue
+ private AutoAdjustConfigurationSchema autoAdjust;
+
+ /** Nodes. */
+ @NamedConfigValue
+ private NodeConfigurationSchema nodes;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
new file mode 100644
index 0000000..e6da55b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import javax.validation.constraints.Min;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+/**
+ * Test cache configuration schema.
+ */
+@Config
+public class CacheConfigurationSchema {
+ /** Size. */
+ @Value
+ @Min(value = 1, message = "Minimal cache size is 1")
+ private int size;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.java
new file mode 100644
index 0000000..2c51d25
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+/**
+ * Test cluster wide configuration schema.
+ */
+@Config(value = "cluster", root = true)
+public class ClusterWideConfigurationSchema {
+ /** Cache. */
+ @NamedConfigValue
+ CacheConfigurationSchema cacheConfig;
+
+ /** Baseline. */
+ @ConfigValue
+ private BaselineConfigurationSchema baseline;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
new file mode 100644
index 0000000..6f6cf7f
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+
+/**
+ * Test local configuration schema.
+ */
+@Config(value = "local", root = true)
+public class LocalConfigurationSchema {
+ /** Baseline. */
+ @ConfigValue
+ private BaselineConfigurationSchema baseline;
+
+}
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
new file mode 100644
index 0000000..74d2d48
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+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.
+ */
+@Config
+public class NodeConfigurationSchema {
+ /** Consistent id. */
+ @Value(immutable = true)
+ @NotNull(message = "Consistent id must not be null")
+ private String consistentId;
+
+ /** Port. */
+ @Value
+ private int port;
+
+ /** Auto adjust enabled. */
+ @Value
+ @Validate(NodeValidator.class)
+ private boolean autoAdjustEnabled;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
new file mode 100644
index 0000000..d200efc
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.function.Consumer;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.apache.ignite.configuration.PublicConfigurator;
+
+/**
+ * Simple usage test of generated configuration schema.
+ */
+public class UsageTest {
+
+ /**
+ * Test creation of configuration and calling configuration API methods.
+ */
+ @Test
+ public void test() {
+ final ConfigurationStorage storage = new ConfigurationStorage() {
+
+ @Override
+ public <T extends Serializable> void save(String propertyName, T object) {
+
+ }
+
+ @Override
+ public <T extends Serializable> T get(String propertyName) {
+ return null;
+ }
+
+ @Override
+ public <T extends Serializable> void listen(String key, Consumer<T> listener) {
+
+ }
+ };
+
+ InitLocal initLocal = new InitLocal().withBaseline(
+ new InitBaseline()
+ .withNodes(
+ new NamedList<>(
+ Collections.singletonMap("node1", new InitNode().withConsistentId("test").withPort(1000))
+ )
+ )
+ .withAutoAdjust(new InitAutoAdjust().withEnabled(true).withTimeout(100000L))
+ );
+
+ final Configurator<LocalConfigurationImpl> configurator = Configurator.create(
+ storage,
+ LocalConfigurationImpl::new,
+ initLocal
+ );
+
+ final LocalConfiguration root = configurator.getRoot();
+ root.baseline().autoAdjust().enabled().value();
+
+ try {
+ configurator.set(Selectors.LOCAL_BASELINE_AUTO_ADJUST_ENABLED, false);
+ Assertions.fail();
+ } catch (ConfigurationValidationException e) {}
+ configurator.set(Selectors.LOCAL_BASELINE_AUTO_ADJUST, new ChangeAutoAdjust().withEnabled(false).withTimeout(0L));
+ configurator.getRoot().baseline().nodes().get("node1").autoAdjustEnabled(false);
+ configurator.getRoot().baseline().autoAdjust().enabled(true);
+ configurator.getRoot().baseline().nodes().get("node1").autoAdjustEnabled(true);
+
+ try{
+ configurator.getRoot().baseline().autoAdjust().enabled(false);
+ Assertions.fail();
+ } catch (ConfigurationValidationException e) {}
+
+ PublicConfigurator<LocalConfiguration> con = new PublicConfigurator<>(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
new file mode 100644
index 0000000..fdd2d58
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java
@@ -0,0 +1,41 @@
+/*
+ * 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
new file mode 100644
index 0000000..5ab1bba
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java
@@ -0,0 +1,41 @@
+/*
+ * 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
new file mode 100644
index 0000000..5c99658
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.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.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-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
new file mode 100644
index 0000000..5875b7d
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
@@ -0,0 +1,22 @@
+package org.apache.ignite.configuration.processor.internal;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+
+@Config(value = "test", root = true)
+public class TestConfigurationSchema {
+ @Value
+ private String value1;
+
+ @Value
+ private long primitiveLong;
+
+ @Value
+ private Long boxedLong;
+
+ @Value
+ private int primitiveInt;
+
+ @Value
+ private Integer boxedInt;
+}
\ No newline at end of file
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
new file mode 100644
index 0000000..7eaa507
--- /dev/null
+++ b/modules/configuration/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+-->
+
+<!--
+ POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-configuration</artifactId>
+ <version>3.0-SNAPSHOT</version>
+ <url>http://ignite.apache.org</url>
+
+ <properties>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ <version>2.0.1.Final</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.4.6</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
new file mode 100644
index 0000000..4947b7e
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+
+/**
+ * Base interface for configuration.
+ * @param <VALUE> Type of the value.
+ * @param <CHANGE> Type of the object that changes the value of configuration.
+ */
+public interface ConfigurationProperty<VALUE, CHANGE> {
+ /**
+ * Get key of this node.
+ * @return Key.
+ */
+ String key();
+
+ /**
+ * Get value of this property.
+ * @return Value of this property.
+ */
+ VALUE value();
+
+ /**
+ * Change this configuration node value.
+ * @param change CHANGE object.
+ * @throws ConfigurationValidationException If validation failed.
+ */
+ void change(CHANGE change) throws ConfigurationValidationException;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
new file mode 100644
index 0000000..2df36b7
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.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.configuration;
+
+import java.util.Map;
+
+/**
+ * Configuration tree with configuration values and other configuration trees as child nodes.
+ * @param <VALUE> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public interface ConfigurationTree<VALUE, CHANGE> extends ConfigurationProperty<VALUE, CHANGE> {
+ /** Children of the tree. */
+ Map<String, ConfigurationProperty<?, ?>> members();
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
new file mode 100644
index 0000000..5e6229c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * Configuration value.
+ * @param <VALUE> Type of the value.
+ */
+public interface ConfigurationValue<VALUE> extends ConfigurationProperty<VALUE, VALUE> {
+}
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
new file mode 100644
index 0000000..ba04aca
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import java.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.Modifier;
+import org.apache.ignite.configuration.internal.selector.Selector;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+
+/**
+ * Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions
+ * on configuration such as initialized, change and view.
+ * @param <T> Type of configuration root.
+ */
+public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
+ /** Storage for the configuration tree. */
+ private final ConfigurationStorage storage;
+
+ /** 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 storage
+ * @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(
+ ConfigurationStorage storage,
+ Function<Configurator<CONF>, CONF> rootBuilder
+ ) {
+ return new Configurator<>(storage, rootBuilder, null);
+ }
+
+ /**
+ *
+ * @param storage
+ * @param rootBuilder
+ * @param init
+ * @param <VIEW>
+ * @param <INIT>
+ * @param <CHANGE>
+ * @param <CONF>
+ * @return
+ */
+ public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
+ ConfigurationStorage storage,
+ Function<Configurator<CONF>, CONF> rootBuilder,
+ INIT init
+ ) {
+ return new Configurator<>(storage, rootBuilder, init);
+ }
+
+ /**
+ * Constructor.
+ * @param storage Configuration storage.
+ * @param rootBuilder Function, that creates configuration root.
+ */
+ private <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator(
+ ConfigurationStorage storage,
+ Function<Configurator<CONF>, CONF> rootBuilder,
+ INIT init
+ ) {
+ this.storage = storage;
+
+ final CONF built = rootBuilder.apply((Configurator<CONF>) this);
+
+ if (init != null)
+ built.init(init);
+
+ root = (T) built;
+ }
+
+ /**
+ *
+ * @param selector
+ * @param <TARGET>
+ * @param <VIEW>
+ * @param <INIT>
+ * @param <CHANGE>
+ * @return
+ */
+ public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> VIEW getPublic(
+ Selector<T, TARGET, VIEW, INIT, CHANGE> selector
+ ) {
+ return selector.select(root).value();
+ }
+
+ /**
+ *
+ * @param selector
+ * @param newValue
+ * @param <TARGET>
+ * @param <VIEW>
+ * @param <INIT>
+ * @param <CHANGE>
+ * @throws ConfigurationValidationException
+ */
+ public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> void set(
+ Selector<T, TARGET, VIEW, INIT, CHANGE> selector,
+ CHANGE newValue
+ ) throws ConfigurationValidationException {
+ // TODO: atomic change start
+ final T copy = (T) root.copy();
+
+ final TARGET select = selector.select(copy);
+ select.changeWithoutValidation(newValue);
+ copy.validate(root);
+ selector.select(root).changeWithoutValidation(newValue);
+ // TODO: atomic change end
+ }
+
+ /**
+ *
+ * @param selector
+ * @param <TARGET>
+ * @param <VIEW>
+ * @param <INIT>
+ * @param <CHANGE>
+ * @return
+ */
+ public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> ConfigurationProperty<VIEW, CHANGE> getInternal(
+ Selector<T, TARGET, VIEW, INIT, CHANGE> selector
+ ) {
+ return selector.select(root);
+ }
+
+ /**
+ *
+ * @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) {
+ final String key = property.key();
+ property.addListener(new PropertyListener<PROP, PROP>() {
+ /** {@inheritDoc} */
+ public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
+ storage.save(key, newValue);
+ }
+ });
+ storage.listen(key, property::setSilently);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
new file mode 100644
index 0000000..52f3522
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import java.io.Serializable;
+
+/**
+ * Configuration property change listener.
+ *
+ * @param <VIEW> VIEW type of property.
+ * @param <CHANGE> CHANGE type of property.
+ */
+public interface PropertyListener<VIEW extends Serializable, CHANGE extends Serializable> {
+ /**
+ * Called before property value is updated.
+ *
+ * @param oldValue Previous value.
+ * @param newValue New value.
+ * @param modifier Property itself.
+ * @return {@code true} if changes are approved and {@code false} then property update must be aborted.
+ */
+ default boolean beforeUpdate(VIEW oldValue, VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+ return true;
+ }
+
+ /**
+ * Called on property value update.
+ *
+ * @param newValue New value of the property.
+ * @param modifier Property itself.
+ */
+ default void update(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+ /* No-op */
+ }
+
+ /**
+ * Called after property value update.
+ *
+ * @param newValue New value of the property.
+ * @param modifier Property itself.
+ */
+ default void afterUpdate(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+ /* No-op */
+ }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java
new file mode 100644
index 0000000..7b8f953
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+
+/**
+ * Public configurator.
+ * @param <T> Public type.
+ */
+public class PublicConfigurator<T extends ConfigurationTree<?, ?>> {
+ /** Configuration root. */
+ private T root;
+
+ public <VIEW, INIT, CHANGE> PublicConfigurator(Configurator<? extends DynamicConfiguration<VIEW, INIT, CHANGE>> configurator) {
+ final ConfigurationTree<VIEW, CHANGE> root = configurator.getRoot();
+ this.root = (T) root;
+ }
+
+ /**
+ * Get root of the configuration.
+ * @return Configuration root.
+ */
+ public T getRoot() {
+ return root;
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java
new file mode 100644
index 0000000..6328d34
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java
@@ -0,0 +1,69 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.Target;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation, if applied to a class, marks it as a configuration schema.
+ * Annotation processor generates several classes for each configuration schema:
+ * <ul>
+ * <li>Config - Represents configuration itself, provides API to init, change and view it. Extends {@link DynamicConfiguration}</li>
+ * <li>Init - initializes config tree</li>
+ * <li>Change - changes config tree</li>
+ * <li>View - immutable object to view config tree</li>
+ * </ul>
+ *
+ * <h1 class="header">Example</h1>
+ * Here is how to create a root configuration schema:
+ * <pre name="code" class="java">
+ * {@literal @}Config(value = "local", root = true)
+ * public class LocalConfigurationSchema {
+ *
+ * {@literal @}Value
+ * private String foo;
+ *
+ * {@literal @}Value
+ * private boolean bar;
+ *
+ * {@literal @}ConfigValue
+ * private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ */
+@Target({ TYPE })
+@Retention(SOURCE)
+@Documented
+public @interface Config {
+ /**
+ * @return The name of the configuration.
+ */
+ String value() default "";
+
+ /**
+ * @return {@code true } if marked class is the root of the configuration schema.
+ */
+ boolean root() default false;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
new file mode 100644
index 0000000..b593674
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation marks configuration schema field as a configuration tree node.
+ * <pre name="code" class="java">
+ * {@literal @}Config
+ * public class FooConfigurationSchema {
+ *
+ * {@literal @}ConfigValue
+ * private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface ConfigValue {
+ /**
+ * @return The name of the configuration.
+ */
+ String value() default "";
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
new file mode 100644
index 0000000..f3d3f77
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.Target;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+import org.apache.ignite.configuration.internal.NamedList;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation denotes configuration schema fields that are dynamically created and mapped by name.
+ * Example use-cases for this annotation are Ignite node configuration, cache configuration, because nodes and caches
+ * can be added dynamically.
+ * Every field annotated with this annotation will produce a {@link NamedListConfiguration} field in generated configuration class.
+ *
+ * <h1 class="header">Example</h1>
+ * <pre name="code" class="java">
+ * {@literal @}Config(value = "local", root = true)
+ * public class LocalConfigurationSchema {
+ *
+ * {@literal @}NamedConfigValue
+ * private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ * @see NamedList
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface NamedConfigValue {
+}
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
new file mode 100644
index 0000000..2c2c34d
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java
@@ -0,0 +1,75 @@
+/*
+ * 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/annotation/Value.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
new file mode 100644
index 0000000..eeda647
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import org.apache.ignite.configuration.internal.DynamicProperty;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation marks configuration schema field as a configuration tree leaf.
+ * Every field annotated with this annotation will produce a {@link DynamicProperty} field in generated configuration class.
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface Value {
+ /**
+ * @return {@code true} if this value can only be initialized and can't be changed afterwards.
+ */
+ boolean immutable() default false;
+}
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
new file mode 100644
index 0000000..a739f7c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
@@ -0,0 +1,150 @@
+/*
+ * 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.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * This class represents configuration root or node.
+ */
+public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> implements Modifier<VIEW, INIT, CHANGE>, ConfigurationTree<VIEW, CHANGE> {
+ /** Fully qualified name of the configuration. */
+ protected final String qualifiedName;
+
+ /** Configuration key. */
+ protected final String key;
+
+ /** Configuration prefix. */
+ protected final String prefix;
+
+ /** Configuration members (leaves and nodes). */
+ protected final Map<String, Modifier<?, ?, ?>> members = new HashMap<>();
+
+ /** Root configuration node. */
+ protected final DynamicConfiguration<?, ?, ?> root;
+
+ /** {@code true} if this is a member of {@link NamedListConfiguration}. */
+ protected final boolean isNamed;
+
+ /** Configurator that this configuration is attached to. */
+ protected final Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator;
+
+ /**
+ * Constructor.
+ * @param prefix Configuration prefix.
+ * @param key Configuration key.
+ * @param isNamed Is this a part of named configuration.
+ * @param configurator Configurator that this object is attached to.
+ * @param root Root configuration.
+ */
+ protected DynamicConfiguration(
+ String prefix,
+ String key,
+ boolean isNamed,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root
+ ) {
+ this.prefix = prefix;
+ this.isNamed = isNamed;
+ this.configurator = configurator;
+
+ this.key = key;
+ if (root == null)
+ this.qualifiedName = key;
+ else {
+ if (isNamed)
+ qualifiedName = String.format("%s[%s]", prefix, key);
+ else
+ qualifiedName = String.format("%s.%s", prefix, key);
+ }
+
+ this.root = root != null ? root : this;
+ }
+
+ /**
+ * Add new configuration member.
+ * @param member Configuration member (leaf or node).
+ * @param <M> Type of member.
+ */
+ protected <M extends Modifier<?, ?, ?>> void add(M member) {
+ members.put(member.key(), member);
+ }
+
+ /**
+ * Add new configuration member with validators.
+ * @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
+ ) {
+ members.put(member.key(), member);
+
+ configurator.addValidations((Class<? extends ConfigurationTree<?, ?>>) getClass(), member.key(), validators);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void change(CHANGE change) throws ConfigurationValidationException {
+ configurator.set(BaseSelectors.find(qualifiedName), change);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String key() {
+ return key;
+ }
+
+ /**
+ * Create a deep copy of this DynamicConfiguration, but attaching it to another configuration root.
+ * @param root New configuration root.
+ * @return Copy of this configuration.
+ */
+ public abstract DynamicConfiguration<VIEW, INIT, CHANGE> copy(DynamicConfiguration<?, ?, ?> root);
+
+ /**
+ * Create a deep copy of this DynamicConfiguration, making it root configuration (so this method must be called
+ * only on root configuration object).
+ * @return Copy of this configuration.
+ */
+ public final DynamicConfiguration<VIEW, INIT, CHANGE> copy() {
+ return copy(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException {
+ for (Modifier<?, ?, ?> member : members.values())
+ member.validate(oldRoot);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Map<String, ConfigurationProperty<?, ?>> members() {
+ return members.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+}
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
new file mode 100644
index 0000000..3ab17db
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
@@ -0,0 +1,203 @@
+/*
+ * 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.PropertyListener;
+import org.apache.ignite.configuration.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+
+/**
+ * Holder for property value. Expected to be used with numbers, strings and other immutable objects, e.g. IP addresses.
+ */
+public class DynamicProperty<T extends Serializable> implements Modifier<T, T, T>, ConfigurationValue<T> {
+ /** Name of property. */
+ private final String name;
+
+ /** Member key. */
+ private final MemberKey memberKey;
+
+ /** Full name with prefix. */
+ private final String qualifiedName;
+
+ /** Property value. */
+ protected volatile T val;
+
+ /** Listeners of property update. */
+ private final List<PropertyListener<T, T>> updateListeners = new ArrayList<>();
+
+ /** Configurator that this configuration is attached to. */
+ protected final Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator;
+
+ /** Configuration root. */
+ protected final DynamicConfiguration<?, ?, ?> root;
+
+ /**
+ * Constructor.
+ * @param prefix Property prefix.
+ * @param name Property name.
+ * @param memberKey Property member key.
+ * @param configurator Configurator to attach to.
+ * @param root Configuration root.
+ */
+ public DynamicProperty(
+ String prefix,
+ String name,
+ MemberKey memberKey,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root
+ ) {
+ this(prefix, name, memberKey, null, configurator, root);
+ }
+
+ /**
+ * Constructor.
+ * @param prefix Property prefix.
+ * @param name Property name.
+ * @param memberKey Property member key.
+ * @param defaultValue Default value for the property.
+ * @param configurator Configurator to attach to.
+ * @param root Configuration root.
+ */
+ public DynamicProperty(
+ String prefix,
+ String name,
+ MemberKey memberKey,
+ T defaultValue,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root
+ ) {
+ this(defaultValue, name, memberKey, String.format("%s.%s", prefix, name), configurator, root, false);
+ }
+
+ /**
+ * Copy constructor.
+ * @param base Property to copy from.
+ * @param root Configuration root.
+ */
+ private DynamicProperty(
+ DynamicProperty<T> base,
+ DynamicConfiguration<?, ?, ?> root
+ ) {
+ this(base.val, base.name, base.memberKey, base.qualifiedName, base.configurator, root, true);
+ }
+
+ /**
+ * Constructor.
+ * @param value Property value.
+ * @param name Property name.
+ * @param memberKey Member key.
+ * @param qualifiedName Fully qualified name of the property.
+ * @param configurator Configurator.
+ * @param root Configuration root.
+ */
+ private DynamicProperty(
+ T value,
+ String name,
+ MemberKey memberKey,
+ String qualifiedName,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root,
+ boolean isCopy
+ ) {
+ this.name = name;
+ this.memberKey = memberKey;
+ this.qualifiedName = qualifiedName;
+ this.val = value;
+ this.configurator = configurator;
+ this.root = root;
+
+ if (isCopy)
+ configurator.onAttached(this);
+ }
+
+ /**
+ * Add change listener to this property.
+ * @param listener Property change listener.
+ */
+ public void addListener(PropertyListener<T, T> listener) {
+ updateListeners.add(listener);
+ }
+
+ /** {@inheritDoc} */
+ public T value() {
+ return val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void change(T object) throws ConfigurationValidationException {
+ configurator.set(BaseSelectors.find(qualifiedName), object);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init(T object) throws ConfigurationValidationException {
+ this.val = object;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void changeWithoutValidation(T object) {
+ this.val = object;
+
+ for (PropertyListener<T, T> listener : updateListeners)
+ listener.update(object, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException {
+ final List<? extends FieldValidator<? extends Serializable, ? extends DynamicConfiguration<?, ?, ?>>> validators = configurator.validators(memberKey);
+
+ for (FieldValidator<? extends Serializable, ? extends DynamicConfiguration<?, ?, ?>> validator : validators)
+ ((FieldValidator<T, DynamicConfiguration<?, ?, ?>>) validator).validate(val, root, oldRoot);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String key() {
+ return name;
+ }
+
+ /**
+ * Get fully qualified name of this property.
+ * @return Fully qualified name.
+ */
+ public String qualifiedName() {
+ return qualifiedName;
+ }
+
+ public void setSilently(T serializable) {
+ val = serializable;
+
+ for (PropertyListener<T, T> listener : updateListeners)
+ listener.update(val, this);
+ }
+
+ /**
+ * Create a deep copy of this DynamicProperty, but attaching it to another configuration root.
+ * @param newRoot New configuration root.
+ * @return Copy of this property.
+ */
+ public DynamicProperty<T> copy(DynamicConfiguration<?, ?, ?> newRoot) {
+ return new DynamicProperty<>(this, newRoot);
+ }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java
new file mode 100644
index 0000000..eb123f4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+
+/**
+ * Interface for configuration nodes and leaves.
+ */
+public interface Modifier<VIEW, INIT, CHANGE> extends ConfigurationProperty<VIEW, CHANGE> {
+ /**
+ * Change this configuration node value, but without validation.
+ * FIXME: this is a necessary evil, but this should'n be accessed from outside of the configurator.
+ * @param change CHANGE object.
+ */
+ void changeWithoutValidation(CHANGE change);
+
+ /**
+ * Initialize this configuration node with value.
+ * @param init INIT object.
+ * @throws ConfigurationValidationException If validation failed.
+ */
+ void init(INIT init) throws ConfigurationValidationException;
+
+ /**
+ * Validate this configuration node against old configuration root thus comparing new configuration "snapshot"
+ * with a previous one.
+ * @param oldRoot Old configuration root.
+ * @throws ConfigurationValidationException If validation failed.
+ */
+ void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java
new file mode 100644
index 0000000..edce953
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * This class holds named configurations in VIEW object.
+ */
+public class NamedList<T> {
+ /** Named values. */
+ private final Map<String, T> values;
+
+ /**
+ * Constructor.
+ * @param values Named values.
+ */
+ public NamedList(Map<String, T> values) {
+ this.values = values;
+ }
+
+ /**
+ * Get named values.
+ * @return Named values.
+ */
+ public Map<String, T> getValues() {
+ return values;
+ }
+}
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
new file mode 100644
index 0000000..98e2619
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
@@ -0,0 +1,126 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.Configurator;
+
+/**
+ * Named configuration wrapper.
+ */
+public class NamedListConfiguration<VIEW, T extends Modifier<VIEW, INIT, CHANGE>, INIT, CHANGE>
+ extends DynamicConfiguration<NamedList<VIEW>, NamedList<INIT>, NamedList<CHANGE>> {
+ /** Creator of named configuration. */
+ private final BiFunction<String, String, T> creator;
+
+ /** Named configurations. */
+ private final Map<String, T> values = new HashMap<>();
+
+ /**
+ * Constructor.
+ * @param prefix Configuration prefix.
+ * @param key Configuration key.
+ * @param configurator Configurator that this object is attached to.
+ * @param root Root configuration.
+ * @param creator Underlying configuration creator function.
+ */
+ public NamedListConfiguration(
+ String prefix,
+ String key,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root,
+ BiFunction<String, String, T> creator
+ ) {
+ super(prefix, key, false, configurator, root);
+ this.creator = creator;
+ }
+
+ /**
+ * Copy constructor.
+ * @param base Base to copy from.
+ * @param configurator Configurator to attach to.
+ * @param root Root of the configuration.
+ */
+ private NamedListConfiguration(
+ NamedListConfiguration<VIEW, T, INIT, CHANGE> base,
+ Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+ DynamicConfiguration<?, ?, ?> root
+ ) {
+ super(base.prefix, base.key, false, configurator, root);
+
+ this.creator = base.creator;
+
+ for (Map.Entry<String, T> entry : base.values.entrySet()) {
+ String k = entry.getKey();
+ T value = entry.getValue();
+
+ final T copy = (T) ((DynamicConfiguration<VIEW, INIT, CHANGE>) value).copy(root);
+ add(copy);
+
+ this.values.put(k, copy);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init(NamedList<INIT> list) {
+ list.getValues().forEach((key, init) -> {
+ if (!values.containsKey(key)) {
+ final T created = creator.apply(qualifiedName, key);
+ add(created);
+ values.put(key, created);
+ }
+
+ values.get(key).init(init);
+ });
+ }
+
+ /**
+ * Get named configuration by name.
+ * @param name Name.
+ * @return Configuration.
+ */
+ public T get(String name) {
+ return values.get(name);
+ }
+
+ /** {@inheritDoc} */
+ @Override public NamedList<VIEW> value() {
+ return new NamedList<>(values.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, it -> it.getValue().value())));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void changeWithoutValidation(NamedList<CHANGE> list) {
+ list.getValues().forEach((key, change) -> {
+ if (!values.containsKey(key)) {
+ final T created = creator.apply(qualifiedName, key);
+ add(created);
+ values.put(key, created);
+ }
+
+ values.get(key).changeWithoutValidation(change);
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override public NamedListConfiguration<VIEW, T, INIT, CHANGE> copy(DynamicConfiguration<?, ?, ?> root) {
+ return new NamedListConfiguration<>(this, configurator, root);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java
new file mode 100644
index 0000000..f219b73
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java
@@ -0,0 +1,157 @@
+/*
+ * 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.selector;
+
+import java.lang.invoke.MethodHandle;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.Modifier;
+
+/**
+ * Base selector holder.
+ */
+public class BaseSelectors {
+ /** Map from string representation of selector to {@link SelectorHolder}. */
+ private static final Map<String, SelectorHolder> selectors = new HashMap<>();
+
+ /**
+ * Get selector from selectors map by key.
+ *
+ * Valid formats for selector key:
+ * <ul>
+ * <li>root.inner.option.field in case of static config field</li>
+ * <li>root.inner.named[name].field in case of dynamic (named) config field</li>
+ * </ul>
+ *
+ * @param name Selector name.
+ * @return Selector.
+ */
+ public static <ROOT extends DynamicConfiguration<?, ?, ?>, TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> Selector<ROOT, TARGET, VIEW, INIT, CHANGE> find(String name) {
+ String[] nameParts = name.split("\\.");
+
+ List<String> arguments = new ArrayList<>();
+ StringBuilder keyBuilder = new StringBuilder();
+
+ for (int i = 0; i < nameParts.length; i++) {
+ String part = nameParts[i];
+
+ int start = part.indexOf('[');
+
+ String methodArg = null;
+
+ if (start != -1) {
+ int end = part.indexOf(']');
+
+ if (end != -1) {
+ methodArg = part.substring(start + 1, end);
+ part = part.substring(0, start);
+ }
+ }
+
+ if (methodArg != null)
+ arguments.add(methodArg);
+
+ keyBuilder.append(part);
+
+ if (i != nameParts.length - 1)
+ keyBuilder.append('.');
+ }
+
+ final String key = keyBuilder.toString();
+
+ final SelectorHolder selector = selectors.get(key);
+
+ if (selector == null) {
+ final int lastDot = key.lastIndexOf('.');
+
+ if (lastDot != -1) {
+ String partialKey = key.substring(0, lastDot);
+
+ final SelectorHolder partialSelector = selectors.get(partialKey);
+
+ if (partialSelector != null) {
+ final String availableOptions = selectors.keySet().stream().filter(s -> s.startsWith(partialKey)).collect(Collectors.joining(", "));
+ throw new SelectorNotFoundException("Selector " + key + " was not found, available options are: " + availableOptions);
+ }
+ }
+ }
+
+ try {
+ return (Selector<ROOT, TARGET, VIEW, INIT, CHANGE>) selector.get(arguments);
+ } catch (Throwable throwable) {
+ throw new SelectorNotFoundException("Failed to get selector: " + throwable.getMessage(), throwable);
+ }
+ }
+
+ /**
+ * Put selector to selectors map by key.
+ * @param key Selector key.
+ * @param selector Selector.
+ */
+ public static void put(String key, Selector<?, ?, ?, ?, ?> selector) {
+ selectors.put(key, new SelectorHolder(selector));
+ }
+
+ /**
+ * Put method handle selector (for named configuration) to selectors map by key.
+ * @param key Selector key.
+ * @param handle Method handle.
+ */
+ public static void put(String key, MethodHandle handle) {
+ selectors.put(key, new SelectorHolder(handle));
+ }
+
+ /**
+ * Holder for selector (it's either selector object or method handle for named configurations).
+ */
+ private static final class SelectorHolder {
+ /** Selector object. */
+ Selector<?, ?, ?, ?, ?> selector;
+
+ /** Method handle for named configuration. */
+ MethodHandle selectorFn;
+
+ /** Constructor for selector. */
+ public SelectorHolder(Selector<?, ?, ?, ?, ?> selector) {
+ this.selector = selector;
+ }
+
+ /** Constructor for method handle. */
+ public SelectorHolder(MethodHandle selectorFn) {
+ this.selectorFn = selectorFn;
+ }
+
+ /**
+ * Get selector object.
+ * @param arguments Arguments (empty if static selector or contains names for named configuration).
+ * @return Selector.
+ * @throws Throwable If failed to invoke method handle.
+ */
+ Selector<?, ?, ?, ?, ?> get(List<String> arguments) throws Throwable {
+ if (selector != null)
+ return selector;
+
+ return (Selector<?, ?, ?, ?, ?>) selectorFn.invokeWithArguments(arguments);
+ }
+
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.java
new file mode 100644
index 0000000..177e2c2
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.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.internal.selector;
+
+import org.apache.ignite.configuration.internal.Modifier;
+
+/**
+ * Interface for objects helping select configuration elements.
+ *
+ * @param <ROOT> Root configuration class.
+ * @param <TARGET> Target configuration class.
+ * @param <VIEW> View class of target.
+ * @param <INIT> Init class of target.
+ * @param <CHANGE> Change class of target.
+ */
+public interface Selector<ROOT, TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> {
+ /**
+ * Select configuration element.
+ *
+ * @param root Configuration root object.
+ * @return Configuration element.
+ */
+ TARGET select(ROOT root);
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.java
new file mode 100644
index 0000000..39d9381
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.selector;
+
+/**
+ * Exception for absence of selector.
+ */
+public class SelectorNotFoundException extends RuntimeException {
+ /** Constructor. */
+ public SelectorNotFoundException(String message) {
+ super(message);
+ }
+
+ /** Constructor. */
+ public SelectorNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
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
new file mode 100644
index 0000000..60620fe
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 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 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;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
+ if (value.longValue() > maxValue)
+ throw new ConfigurationValidationException(message);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java
new file mode 100644
index 0000000..2462903
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Objects;
+
+/**
+ * Configuration member key.
+ */
+public class MemberKey {
+ /** Class of the field holder. */
+ private final Class<?> clazz;
+
+ /** Name of the field. */
+ private final String fieldName;
+
+ /** Constructor. */
+ public MemberKey(Class<?> clazz, String fieldName) {
+ this.clazz = clazz;
+ this.fieldName = fieldName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MemberKey key = (MemberKey) o;
+ return clazz.equals(key.clazz) &&
+ fieldName.equals(key.fieldName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(clazz, fieldName);
+ }
+}
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
new file mode 100644
index 0000000..b94e65f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 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 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;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
+ if (value.longValue() < minValue)
+ throw new ConfigurationValidationException(message);
+ }
+}
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
new file mode 100644
index 0000000..f060f53
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java
@@ -0,0 +1,41 @@
+/*
+ * 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/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
new file mode 100644
index 0000000..9cb37e4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.configuration.storage;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+/**
+ * Storage interface for configuration.
+ */
+public interface ConfigurationStorage {
+ /**
+ * Save configuration property.
+ *
+ * @param propertyName Fully qualified name of the property.
+ * @param object Object, that represents the value of the property.
+ * @param <T> Type of the property.
+ * @throws StorageException If failed to save object.
+ */
+ <T extends Serializable> void save(String propertyName, T object) throws StorageException;
+
+ /**
+ * Get property value from storage.
+ *
+ * @param propertyName Fully qualified name of the property.
+ * @param <T> Type of the property.
+ * @return Object, that represents the value of the property.
+ * @throws StorageException If failed to retrieve object frm configuration storage.
+ */
+ <T extends Serializable> T get(String propertyName) throws StorageException;
+
+ /**
+ * Listen for the property change in the storage.
+ *
+ * @param key Key to listen on.
+ * @param listener Listener function.
+ * @param <T> Type of the property.
+ * @throws StorageException If failed to attach listener to configuration storage.
+ */
+ <T extends Serializable> void listen(String key, Consumer<T> listener) throws StorageException;
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java
new file mode 100644
index 0000000..5c511db
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.storage;
+
+/**
+ * Exception thrown from configuration storage.
+ */
+public class StorageException extends RuntimeException {
+ /** Constructor. */
+ public StorageException() {
+ }
+
+ /** Constructor. */
+ public StorageException(String message) {
+ super(message);
+ }
+
+ /** Constructor. */
+ public StorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
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
new file mode 100644
index 0000000..a7860de
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.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.configuration.validation;
+
+/**
+ * Configuration validation exception.
+ */
+public class ConfigurationValidationException extends RuntimeException {
+
+ /** Constructor. */
+ public ConfigurationValidationException(String message) {
+ super(message);
+ }
+
+}
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
new file mode 100644
index 0000000..f32a357
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java
@@ -0,0 +1,47 @@
+/*
+ * 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/pom.xml b/pom.xml
index e4d80c4..a27e525 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,4 +36,9 @@
<scope>test</scope>
</dependency>
</dependencies>
+
+ <modules>
+ <module>modules/configuration</module>
+ <module>modules/configuration-annotation-processor</module>
+ </modules>
</project>