blob: 0f61d6519ae83a05be1f28a2ebd5cbe2a8b016ce [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.configuration.util;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.RootInnerNode;
import org.apache.ignite.internal.configuration.SuperRoot;
import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.tree.NamedListNode.NAME;
import static org.apache.ignite.internal.configuration.tree.NamedListNode.ORDER_IDX;
import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.extensionsFields;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.find;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** */
public class ConfigurationUtilTest {
private static ConfigurationAsmGenerator cgen;
@BeforeAll
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
}
@AfterAll
public static void afterAll() {
cgen = null;
}
public static <P extends InnerNode & ParentChange> P newParentInstance() {
return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
}
/** */
@Test
public void escape() {
assertEquals("foo", ConfigurationUtil.escape("foo"));
assertEquals("foo\\.bar", ConfigurationUtil.escape("foo.bar"));
assertEquals("foo\\\\bar", ConfigurationUtil.escape("foo\\bar"));
assertEquals("\\\\a\\.b\\\\c\\.", ConfigurationUtil.escape("\\a.b\\c."));
}
/** */
@Test
public void unescape() {
assertEquals("foo", ConfigurationUtil.unescape("foo"));
assertEquals("foo.bar", ConfigurationUtil.unescape("foo\\.bar"));
assertEquals("foo\\bar", ConfigurationUtil.unescape("foo\\\\bar"));
assertEquals("\\a.b\\c.", ConfigurationUtil.unescape("\\\\a\\.b\\\\c\\."));
}
/** */
@Test
public void split() {
assertEquals(List.of("a", "b.b", "c\\c", ""), ConfigurationUtil.split("a.b\\.b.c\\\\c."));
}
/** */
@Test
public void join() {
assertEquals("a.b\\.b.c\\\\c", ConfigurationUtil.join(List.of("a", "b.b", "c\\c")));
}
/** */
@ConfigurationRoot(rootName = "root", type = LOCAL)
public static class ParentConfigurationSchema {
/** */
@NamedConfigValue
public NamedElementConfigurationSchema elements;
}
/** */
@Config
public static class NamedElementConfigurationSchema {
/** */
@ConfigValue
public ChildConfigurationSchema child;
}
/** */
@Config
public static class ChildConfigurationSchema {
/** */
@Value
public String str;
}
/**
* Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} finds proper node when provided
* with correct path.
*/
@Test
public void findSuccessfully() {
var parent = newParentInstance();
parent.changeElements(elements ->
elements.createOrUpdate("name", element ->
element.changeChild(child ->
child.changeStr("value")
)
)
);
assertSame(
parent,
ConfigurationUtil.find(List.of(), parent, true)
);
assertSame(
parent.elements(),
ConfigurationUtil.find(List.of("elements"), parent, true)
);
assertSame(
parent.elements().get("name"),
ConfigurationUtil.find(List.of("elements", "name"), parent, true)
);
assertSame(
parent.elements().get("name").child(),
ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
);
assertSame(
parent.elements().get("name").child().str(),
ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
);
}
/**
* Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} returns null when path points to
* nonexistent named list element.
*/
@Test
public void findNulls() {
var parent = newParentInstance();
assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent, true));
parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
assertNull(ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true));
((NamedElementChange)parent.elements().get("name")).changeChild(child -> {});
assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true));
}
/**
* Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} throws
* {@link KeyNotFoundException} when provided with a wrong path.
*/
@Test
public void findUnsuccessfully() {
var parent = newParentInstance();
assertThrows(
KeyNotFoundException.class,
() -> ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
);
parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
assertThrows(
KeyNotFoundException.class,
() -> ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
);
((NamedElementChange)parent.elements().get("name")).changeChild(child -> child.changeStr("value"));
assertThrows(
KeyNotFoundException.class,
() -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent, true)
);
}
/**
* Tests convertion of flat map to a prefix map.
*/
@Test
public void toPrefixMap() {
assertEquals(
Map.of("foo", 42),
ConfigurationUtil.toPrefixMap(Map.of("foo", 42))
);
assertEquals(
Map.of("foo.bar", 42),
ConfigurationUtil.toPrefixMap(Map.of("foo\\.bar", 42))
);
assertEquals(
Map.of("foo", Map.of("bar1", 10, "bar2", 20)),
ConfigurationUtil.toPrefixMap(Map.of("foo.bar1", 10, "foo.bar2", 20))
);
assertEquals(
Map.of("root1", Map.of("leaf1", 10), "root2", Map.of("leaf2", 20)),
ConfigurationUtil.toPrefixMap(Map.of("root1.leaf1", 10, "root2.leaf2", 20))
);
}
/**
* Tests that patching of configuration node with a prefix map works fine when prefix map is valid.
*/
@Test
public void fillFromPrefixMapSuccessfully() {
var parentNode = newParentInstance();
ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
"elements", Map.of(
"0123456789abcde0123456789abcde", Map.of(
"child", Map.of("str", "value2"),
ORDER_IDX, 1,
NAME, "name2"
),
"12345689abcdef0123456789abcdef0", Map.of(
"child", Map.of("str", "value1"),
ORDER_IDX, 0,
NAME, "name1"
)
)
));
assertEquals("value1", parentNode.elements().get("name1").child().str());
assertEquals("value2", parentNode.elements().get("name2").child().str());
}
/**
* Tests that patching of configuration node with a prefix map works fine when prefix map is valid.
*/
@Test
public void fillFromPrefixMapSuccessfullyWithRemove() {
var parentNode = newParentInstance();
parentNode.changeElements(elements ->
elements.createOrUpdate("name", element ->
element.changeChild(child -> {})
)
);
ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
"elements", singletonMap("name", null)
));
assertNull(parentNode.elements().get("node"));
}
/**
* Tests that conversion from "changer" lambda to a flat map of updates for the storage works properly.
*/
@Test
public void flattenedUpdatesMap() {
var superRoot = new SuperRoot(key -> null, Map.of(ParentConfiguration.KEY, newParentInstance()));
assertThat(flattenedMap(superRoot, parent -> {}), is(anEmptyMap()));
assertThat(
flattenedMap(superRoot, parent -> parent
.changeElements(elements -> elements
.create("name", element -> element
.changeChild(child -> child.changeStr("foo"))
)
)
),
is(allOf(
aMapWithSize(3),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]child[.]str"), hasToString("foo")),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<order>"), is(0)),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<name>"), hasToString("name"))
))
);
assertThat(
flattenedMap(superRoot, parent -> parent
.changeElements(elements1 -> elements1.delete("void"))
),
is(anEmptyMap())
);
assertThat(
flattenedMap(superRoot, parent -> parent
.changeElements(elements -> elements.delete("name"))
),
is(allOf(
aMapWithSize(3),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]child[.]str"), nullValue()),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<order>"), nullValue()),
hasEntry(matchesPattern("root[.]elements[.]\\w{32}[.]<name>"), nullValue())
))
);
}
/** */
@Test
void testCheckConfigurationTypeMixedTypes() {
List<RootKey<?, ?>> rootKeys = List.of(LocalFirstConfiguration.KEY, DistributedFirstConfiguration.KEY);
assertThrows(
IllegalArgumentException.class,
() -> checkConfigurationType(rootKeys, new TestConfigurationStorage(LOCAL))
);
assertThrows(
IllegalArgumentException.class,
() -> checkConfigurationType(rootKeys, new TestConfigurationStorage(DISTRIBUTED))
);
}
/** */
@Test
void testCheckConfigurationTypeOppositeTypes() {
assertThrows(
IllegalArgumentException.class,
() -> checkConfigurationType(
List.of(DistributedFirstConfiguration.KEY, DistributedSecondConfiguration.KEY),
new TestConfigurationStorage(LOCAL)
)
);
assertThrows(
IllegalArgumentException.class,
() -> checkConfigurationType(
List.of(LocalFirstConfiguration.KEY, LocalSecondConfiguration.KEY),
new TestConfigurationStorage(DISTRIBUTED)
)
);
}
/** */
@Test
void testCheckConfigurationTypeNoError() {
checkConfigurationType(
List.of(LocalFirstConfiguration.KEY, LocalSecondConfiguration.KEY),
new TestConfigurationStorage(LOCAL)
);
checkConfigurationType(
List.of(DistributedFirstConfiguration.KEY, DistributedSecondConfiguration.KEY),
new TestConfigurationStorage(DISTRIBUTED)
);
}
/** */
@Test
void testInternalSchemaExtensions() {
assertTrue(internalSchemaExtensions(List.of()).isEmpty());
assertThrows(IllegalArgumentException.class, () -> internalSchemaExtensions(List.of(Object.class)));
assertThrows(
IllegalArgumentException.class,
() -> internalSchemaExtensions(List.of(SimpleRootConfigurationSchema.class))
);
assertThrows(
IllegalArgumentException.class,
() -> internalSchemaExtensions(List.of(SimpleConfigurationSchema.class))
);
Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
InternalFirstSimpleRootConfigurationSchema.class,
InternalSecondSimpleRootConfigurationSchema.class,
InternalFirstSimpleConfigurationSchema.class,
InternalSecondSimpleConfigurationSchema.class
));
assertEquals(2, extensions.size());
assertEquals(
Set.of(
InternalFirstSimpleRootConfigurationSchema.class,
InternalSecondSimpleRootConfigurationSchema.class
),
extensions.get(SimpleRootConfigurationSchema.class)
);
assertEquals(
Set.of(
InternalFirstSimpleConfigurationSchema.class,
InternalSecondSimpleConfigurationSchema.class
),
extensions.get(SimpleConfigurationSchema.class)
);
}
/** */
@Test
void testSchemaFields() {
assertThrows(
IllegalArgumentException.class,
() -> extensionsFields(
List.of(
InternalExtendedSimpleRootConfigurationSchema.class,
ErrorInternalExtendedSimpleRootConfigurationSchema.class
)
)
);
assertTrue(extensionsFields(List.of()).isEmpty());
List<Class<?>> extensions = List.of(
InternalFirstSimpleRootConfigurationSchema.class,
InternalSecondSimpleRootConfigurationSchema.class
);
Set<Field> exp = extensions.stream()
.flatMap(cls -> Stream.of(cls.getDeclaredFields()))
.collect(toSet());
assertEquals(exp, extensionsFields(extensions));
}
/** */
@Test
void testFindInternalConfigs() {
Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
InternalFirstSimpleRootConfigurationSchema.class,
InternalSecondSimpleRootConfigurationSchema.class,
InternalFirstSimpleConfigurationSchema.class,
InternalSecondSimpleConfigurationSchema.class
));
ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
addDefaults(innerNode);
// Check that no internal configuration will be found.
assertThrows(KeyNotFoundException.class, () -> find(List.of("str2"), innerNode, false));
assertThrows(KeyNotFoundException.class, () -> find(List.of("str3"), innerNode, false));
assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg1"), innerNode, false));
assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg", "str01"), innerNode, false));
assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg", "str02"), innerNode, false));
// Check that internal configuration will be found.
assertNull(find(List.of("str2"), innerNode, true));
assertEquals("foo", find(List.of("str3"), innerNode, true));
assertNotNull(find(List.of("subCfg1"), innerNode, true));
assertEquals("foo", find(List.of("subCfg", "str01"), innerNode, true));
assertEquals("foo", find(List.of("subCfg", "str02"), innerNode, true));
}
/** */
@Test
void testGetInternalConfigs() {
Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
InternalFirstSimpleRootConfigurationSchema.class,
InternalSecondSimpleRootConfigurationSchema.class,
InternalFirstSimpleConfigurationSchema.class,
InternalSecondSimpleConfigurationSchema.class
));
ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
addDefaults(innerNode);
Map<String, Object> config = (Map<String, Object>)innerNode.accept(null, new ConverterToMapVisitor(false));
// Check that no internal configuration will be received.
assertEquals(4, config.size());
assertNull(config.get("str0"));
assertEquals("foo", config.get("str1"));
assertNotNull(config.get("subCfg"));
assertNotNull(config.get("namedCfg"));
Map<String, Object> subConfig = (Map<String, Object>)config.get("subCfg");
assertEquals(1, subConfig.size());
assertEquals("foo", subConfig.get("str00"));
// Check that no internal configuration will be received.
config = (Map<String, Object>)innerNode.accept(null, new ConverterToMapVisitor(true));
assertEquals(7, config.size());
assertNull(config.get("str0"));
assertNull(config.get("str2"));
assertEquals("foo", config.get("str1"));
assertEquals("foo", config.get("str3"));
assertNotNull(config.get("subCfg"));
assertNotNull(config.get("subCfg1"));
assertNotNull(config.get("namedCfg"));
subConfig = (Map<String, Object>)config.get("subCfg");
assertEquals(3, subConfig.size());
assertEquals("foo", subConfig.get("str00"));
assertEquals("foo", subConfig.get("str01"));
assertEquals("foo", subConfig.get("str02"));
subConfig = (Map<String, Object>)config.get("subCfg1");
assertEquals(3, subConfig.size());
assertEquals("foo", subConfig.get("str00"));
assertEquals("foo", subConfig.get("str01"));
assertEquals("foo", subConfig.get("str02"));
}
/** */
@Test
void testSuperRootWithInternalConfig() {
ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
Class<?> schemaClass = InternalWithoutSuperclassConfigurationSchema.class;
RootKey<?, ?> schemaKey = InternalWithoutSuperclassConfiguration.KEY;
generator.compileRootSchema(schemaClass, Map.of());
SuperRoot superRoot = new SuperRoot(
s -> new RootInnerNode(schemaKey, generator.instantiateNode(schemaClass))
);
assertThrows(NoSuchElementException.class, () -> superRoot.construct(schemaKey.key(), null, false));
superRoot.construct(schemaKey.key(), null, true);
superRoot.addRoot(schemaKey, generator.instantiateNode(schemaClass));
assertThrows(KeyNotFoundException.class, () -> find(List.of(schemaKey.key()), superRoot, false));
assertNotNull(find(List.of(schemaKey.key()), superRoot, true));
Map<String, Object> config =
(Map<String, Object>)superRoot.accept(schemaKey.key(), new ConverterToMapVisitor(false));
assertTrue(config.isEmpty());
config = (Map<String, Object>)superRoot.accept(schemaKey.key(), new ConverterToMapVisitor(true));
assertEquals(1, config.size());
assertNotNull(config.get(schemaKey.key()));
}
/** */
@Test
void testCollectSchemas() {
assertTrue(collectSchemas(List.of()).isEmpty());
assertThrows(IllegalArgumentException.class, () -> collectSchemas(List.of(Object.class)));
assertEquals(
Set.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class),
collectSchemas(List.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class))
);
assertEquals(
Set.of(SimpleRootConfigurationSchema.class, SimpleConfigurationSchema.class),
collectSchemas(List.of(SimpleRootConfigurationSchema.class))
);
}
/**
* Patches super root and returns flat representation of the changes. Passed {@code superRoot} object will contain
* patched tree when method execution is completed.
*
* @param superRoot Super root to patch.
* @param patch Closure to cnahge parent node.
* @return Flat map with all changes from the patch.
*/
@NotNull private Map<String, Serializable> flattenedMap(SuperRoot superRoot, Consumer<ParentChange> patch) {
// Preserve a copy of the super root to use it as a golden source of data.
SuperRoot originalSuperRoot = superRoot.copy();
// Make a copy of the root insode of the superRoot. This copy will be used for further patching.
superRoot.construct(ParentConfiguration.KEY.key(), new ConfigurationSource() {}, true);
// Patch root node.
patch.accept((ParentChange)superRoot.getRoot(ParentConfiguration.KEY));
// Create flat diff between two super trees.
return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
}
/**
* First local configuration.
*/
@ConfigurationRoot(rootName = "localFirst", type = LOCAL)
public static class LocalFirstConfigurationSchema {
/** String field. */
@Value(hasDefault = true)
public String str = "str";
}
/**
* Second local configuration.
*/
@ConfigurationRoot(rootName = "localSecond", type = LOCAL)
public static class LocalSecondConfigurationSchema {
/** String field. */
@Value(hasDefault = true)
public String str = "str";
}
/**
* First distributed configuration.
*/
@ConfigurationRoot(rootName = "distributedFirst", type = DISTRIBUTED)
public static class DistributedFirstConfigurationSchema {
/** String field. */
@Value(hasDefault = true)
public String str = "str";
}
/**
* Second distributed configuration.
*/
@ConfigurationRoot(rootName = "distributedSecond", type = DISTRIBUTED)
public static class DistributedSecondConfigurationSchema {
/** String field. */
@Value(hasDefault = true)
public String str = "str";
}
/**
* Simple root configuration schema.
*/
@ConfigurationRoot(rootName = "testRootSimple")
public static class SimpleRootConfigurationSchema {
/** String value without default. */
@Value
public String str0;
/** String value with default. */
@Value(hasDefault = true)
public String str1 = "foo";
/** Sub configuration schema. */
@ConfigValue
public SimpleConfigurationSchema subCfg;
/** Named configuration schema. */
@NamedConfigValue
public SimpleConfigurationSchema namedCfg;
}
/**
* Simple configuration schema.
*/
@Config
public static class SimpleConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str00 = "foo";
}
/**
* Internal schema extension without superclass.
*/
@InternalConfiguration
@ConfigurationRoot(rootName = "testRootInternal")
public static class InternalWithoutSuperclassConfigurationSchema {
}
/**
* First simple internal schema extension.
*/
@InternalConfiguration
public static class InternalFirstSimpleConfigurationSchema extends SimpleConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str01 = "foo";
}
/**
* Second simple internal schema extension.
*/
@InternalConfiguration
public static class InternalSecondSimpleConfigurationSchema extends SimpleConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str02 = "foo";
}
/**
* First root simple internal schema extension.
*/
@InternalConfiguration
public static class InternalFirstSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
/** Second string value without default. */
@Value
public String str2;
/** Second string value with default. */
@Value(hasDefault = true)
public String str3 = "foo";
}
/**
* Second root simple internal schema extension.
*/
@InternalConfiguration
public static class InternalSecondSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
/** Second sub configuration schema. */
@ConfigValue
public SimpleConfigurationSchema subCfg1;
}
/**
* Internal extended simple root configuration schema.
*/
@InternalConfiguration
public static class InternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
/** String value without default. */
@Value
public String str00;
/** String value with default. */
@Value(hasDefault = true)
public String str01 = "foo";
/** Sub configuration schema. */
@ConfigValue
public SimpleConfigurationSchema subCfg0;
/** Named configuration schema. */
@NamedConfigValue
public SimpleConfigurationSchema namedCfg0;
}
/**
* Error: Duplicate field.
*/
@InternalConfiguration
public static class ErrorInternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
/** String value without default. */
@Value
public String str00;
}
}