use of org.apache.ignite.internal.configuration.tree.InnerNode in project ignite-3 by apache.
the class ConfigurationUtilTest method findSuccessfully.
/**
* Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} finds proper node when provided with correct path.
*/
@Test
public void findSuccessfully() {
InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
ParentChange parentChange = (ParentChange) parentNode;
parentChange.changeElements(elements -> elements.createOrUpdate("name", element -> element.changeChild(child -> child.changeStr("value"))));
assertSame(parentNode, ConfigurationUtil.find(List.of(), parentNode, true));
assertSame(parentChange.elements(), ConfigurationUtil.find(List.of("elements"), parentNode, true));
assertSame(parentChange.elements().get("name"), ConfigurationUtil.find(List.of("elements", "name"), parentNode, true));
assertSame(parentChange.elements().get("name").child(), ConfigurationUtil.find(List.of("elements", "name", "child"), parentNode, true));
assertSame(parentChange.elements().get("name").child().str(), ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parentNode, true));
}
use of org.apache.ignite.internal.configuration.tree.InnerNode in project ignite-3 by apache.
the class ConfigurationExtension method cfgValue.
/**
* Instantiates a configuration instance for injection.
*
* @param type Type of the field or parameter. Class name must end with {@code Configuration}.
* @param annotation Annotation present on the field or parameter.
* @param cgen Runtime code generator associated with the extension instance.
* @param pool Single-threaded executor service to perform configuration changes.
* @param revisionListenerHolder Configuration storage revision change listener holder.
* @return Mock configuration instance.
* @throws ClassNotFoundException If corresponding configuration schema class is not found.
*/
private static Object cfgValue(Class<?> type, InjectConfiguration annotation, ConfigurationAsmGenerator cgen, ExecutorService pool, StorageRevisionListenerHolderImpl revisionListenerHolder) throws ClassNotFoundException {
// Trying to find a schema class using configuration naming convention. This code won't work for inner Java
// classes, extension is designed to mock actual configurations from public API to configure Ignite components.
Class<?> schemaClass = Class.forName(type.getCanonicalName() + "Schema");
cgen.compileRootSchema(schemaClass, internalSchemaExtensions(List.of(annotation.internalExtensions())), polymorphicSchemaExtensions(List.of(annotation.polymorphicExtensions())));
// RootKey must be mocked, there's no way to instantiate it using a public constructor.
RootKey rootKey = mock(RootKey.class);
when(rootKey.key()).thenReturn("mock");
when(rootKey.type()).thenReturn(LOCAL);
when(rootKey.schemaClass()).thenReturn(schemaClass);
when(rootKey.internal()).thenReturn(false);
SuperRoot superRoot = new SuperRoot(s -> new RootInnerNode(rootKey, cgen.instantiateNode(schemaClass)));
ConfigObject hoconCfg = ConfigFactory.parseString(annotation.value()).root();
HoconConverter.hoconSource(hoconCfg).descend(superRoot);
ConfigurationUtil.addDefaults(superRoot);
// Reference to the super root is required to make DynamicConfigurationChanger#change method atomic.
var superRootRef = new AtomicReference<>(superRoot);
// Reference that's required for notificator.
var cfgRef = new AtomicReference<DynamicConfiguration<?, ?>>();
cfgRef.set(cgen.instantiateCfg(rootKey, new DynamicConfigurationChanger() {
/**
* {@inheritDoc}
*/
@Override
public CompletableFuture<Void> change(ConfigurationSource change) {
return CompletableFuture.supplyAsync(() -> {
SuperRoot sr = superRootRef.get();
SuperRoot copy = sr.copy();
change.descend(copy);
ConfigurationUtil.dropNulls(copy);
if (superRootRef.compareAndSet(sr, copy)) {
long storageRevision = revisionListenerHolder.storageRev.incrementAndGet();
long notificationNumber = revisionListenerHolder.notificationListenerCnt.incrementAndGet();
List<CompletableFuture<?>> futures = new ArrayList<>();
futures.addAll(notifyListeners(sr.getRoot(rootKey), copy.getRoot(rootKey), (DynamicConfiguration<InnerNode, ?>) cfgRef.get(), storageRevision, notificationNumber));
futures.addAll(revisionListenerHolder.notifyStorageRevisionListeners(storageRevision, notificationNumber));
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));
}
return change(change);
}, pool).thenCompose(Function.identity());
}
/**
* {@inheritDoc}
*/
@Override
public InnerNode getRootNode(RootKey<?, ?> rk) {
return superRootRef.get().getRoot(rk);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T getLatest(List<KeyPathNode> path) {
return findEx(path, superRootRef.get());
}
/**
* {@inheritDoc}
*/
@Override
public long notificationCount() {
return revisionListenerHolder.notificationListenerCnt.get();
}
}));
touch(cfgRef.get());
return cfgRef.get();
}
use of org.apache.ignite.internal.configuration.tree.InnerNode in project ignite-3 by apache.
the class ConfigurationAsmGenerator method createNodeClass.
/**
* Construct a {@link InnerNode} definition for a configuration schema.
*
* @param schemaClass Configuration schema class.
* @param internalExtensions Internal extensions of the configuration schema.
* @param polymorphicExtensions Polymorphic extensions of the configuration schema.
* @param schemaFields Fields of the schema class.
* @param internalFields Fields of internal extensions of the configuration schema.
* @param polymorphicFields Fields of polymorphic extensions of the configuration schema.
* @param internalIdField Internal id field or {@code null} if it's not present.
* @return Constructed {@link InnerNode} definition for the configuration schema.
*/
private ClassDefinition createNodeClass(Class<?> schemaClass, Set<Class<?>> internalExtensions, Set<Class<?>> polymorphicExtensions, List<Field> schemaFields, Collection<Field> internalFields, Collection<Field> polymorphicFields, @Nullable Field internalIdField) {
SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
// Node class definition.
ClassDefinition classDef = new ClassDefinition(of(PUBLIC, FINAL), internalName(schemaClassInfo.nodeClassName), type(InnerNode.class), nodeClassInterfaces(schemaClass, internalExtensions));
// Spec fields.
Map<Class<?>, FieldDefinition> specFields = new HashMap<>();
int i = 0;
for (Class<?> clazz : concat(List.of(schemaClass), internalExtensions, polymorphicExtensions)) {
specFields.put(clazz, classDef.declareField(of(PRIVATE, FINAL), "_spec" + i++, clazz));
}
// Define the rest of the fields.
Map<String, FieldDefinition> fieldDefs = new HashMap<>();
// To store the id of the polymorphic configuration instance.
FieldDefinition polymorphicTypeIdFieldDef = null;
// Field with @InjectedName.
FieldDefinition injectedNameFieldDef = null;
for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
String fieldName = fieldName(schemaField);
FieldDefinition fieldDef = addNodeField(classDef, schemaField, fieldName);
fieldDefs.put(fieldName, fieldDef);
if (isPolymorphicId(schemaField)) {
polymorphicTypeIdFieldDef = fieldDef;
} else if (isInjectedName(schemaField)) {
injectedNameFieldDef = fieldDef;
}
}
// org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
addNodeSchemaTypeMethod(classDef, schemaClass, polymorphicExtensions, polymorphicTypeIdFieldDef);
// Constructor.
addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, internalFields, polymorphicFields);
// Add view method for internal id.
if (internalIdField != null) {
addNodeInternalIdMethod(classDef, internalIdField);
}
// VIEW and CHANGE methods.
for (Field schemaField : concat(schemaFields, internalFields)) {
String fieldName = schemaField.getName();
FieldDefinition fieldDef = fieldDefs.get(fieldName);
addNodeViewMethod(classDef, schemaField, viewMtd -> getThisFieldCode(viewMtd, fieldDef), null);
// Read only.
if (isPolymorphicId(schemaField) || isInjectedName(schemaField)) {
continue;
}
// Add change methods.
MethodDefinition changeMtd0 = addNodeChangeMethod(classDef, schemaField, changeMtd -> getThisFieldCode(changeMtd, fieldDef), (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, fieldDef), null);
addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd0);
}
Map<Class<?>, List<Field>> polymorphicFieldsByExtension = Map.of();
MethodDefinition changePolymorphicTypeIdMtd = null;
if (!polymorphicExtensions.isEmpty()) {
assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
addNodeSpecificNodeMethod(classDef, polymorphicExtensions, polymorphicTypeIdFieldDef);
changePolymorphicTypeIdMtd = addNodeChangePolymorphicTypeIdMethod(classDef, fieldDefs, polymorphicExtensions, polymorphicFields, polymorphicTypeIdFieldDef);
addNodeConvertMethod(classDef, schemaClass, polymorphicExtensions, changePolymorphicTypeIdMtd);
polymorphicFieldsByExtension = new LinkedHashMap<>();
for (Class<?> polymorphicExtension : polymorphicExtensions) {
polymorphicFieldsByExtension.put(polymorphicExtension, polymorphicFields.stream().filter(f -> polymorphicExtension.equals(f.getDeclaringClass())).collect(toList()));
}
}
// traverseChildren
addNodeTraverseChildrenMethod(classDef, schemaClass, fieldDefs, schemaFields, internalFields, polymorphicFieldsByExtension, polymorphicTypeIdFieldDef);
// traverseChild
addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, internalFields, polymorphicFieldsByExtension, polymorphicTypeIdFieldDef);
// construct
addNodeConstructMethod(classDef, fieldDefs, schemaFields, internalFields, polymorphicFieldsByExtension, polymorphicTypeIdFieldDef, changePolymorphicTypeIdMtd);
// constructDefault
addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, schemaFields, internalFields, polymorphicFieldsByExtension, polymorphicTypeIdFieldDef);
if (injectedNameFieldDef != null) {
addInjectedNameFieldMethods(classDef, injectedNameFieldDef);
}
return classDef;
}
use of org.apache.ignite.internal.configuration.tree.InnerNode in project ignite-3 by apache.
the class ConfigurationChanger method getLatest.
/**
* {@inheritDoc}
*/
@Override
public <T> T getLatest(List<KeyPathNode> path) {
assert !path.isEmpty();
assert path instanceof RandomAccess : path.getClass();
assert !path.get(0).unresolvedName : path;
// This map will be merged into the data from the storage. It's required for the conversion into tree to work.
// Namely, named list order indexes and names are mandatory for conversion.
Map<String, Map<String, Serializable>> extras = new HashMap<>();
// Joiner for the prefix that will be used to fetch data from the storage.
StringJoiner prefixJoiner = new StringJoiner(KEY_SEPARATOR);
int pathSize = path.size();
KeyPathNode lastPathNode = path.get(pathSize - 1);
// This loop is required to accumulate prefix and resolve all unresolved named list elements' ids.
for (int idx = 0; idx < pathSize; idx++) {
KeyPathNode keyPathNode = path.get(idx);
// Regular keys and resolved ids go straight to the prefix.
if (!keyPathNode.unresolvedName) {
// Fake name and 0 index go to extras in case of resolved named list elements.
if (keyPathNode.namedListEntry) {
prefixJoiner.add(escape(keyPathNode.key));
String prefix = prefixJoiner + KEY_SEPARATOR;
extras.put(prefix, Map.of(prefix + NamedListNode.NAME, "<name_placeholder>", prefix + NamedListNode.ORDER_IDX, 0));
} else {
prefixJoiner.add(keyPathNode.key);
}
continue;
}
assert keyPathNode.namedListEntry : path;
// Here we have unresolved named list element. Name must be translated into the internal id.
// There's a special path for this purpose in the storage.
String unresolvedNameKey = prefixJoiner + KEY_SEPARATOR + NamedListNode.IDS + KEY_SEPARATOR + escape(keyPathNode.key);
// Data from the storage.
Serializable resolvedName = storage.readLatest(unresolvedNameKey);
if (resolvedName == null) {
throw new NoSuchElementException(prefixJoiner + KEY_SEPARATOR + escape(keyPathNode.key));
}
assert resolvedName instanceof String : resolvedName;
// Resolved internal id from the map.
String internalId = (String) resolvedName;
// `*.get("resourceName").internalId()` then the result can be returned straight away.
if (idx == pathSize - 2 && INTERNAL_ID.equals(lastPathNode.key)) {
assert !lastPathNode.unresolvedName : path;
// Despite the fact that this cast looks very stupid, it is correct. Internal ids are always UUIDs.
return (T) UUID.fromString(internalId);
}
prefixJoiner.add(internalId);
String prefix = prefixJoiner + KEY_SEPARATOR;
// Real name and 0 index go to extras in case of unresolved named list elements.
extras.put(prefix, Map.of(prefix + NamedListNode.NAME, keyPathNode.key, prefix + NamedListNode.ORDER_IDX, 0));
}
// That id must be resolved, otherwise method would already be completed in the loop above.
if (lastPathNode.key.equals(INTERNAL_ID) && !lastPathNode.unresolvedName && path.get(pathSize - 2).namedListEntry) {
assert !path.get(pathSize - 2).unresolvedName : path;
// Not very elegant, I know. <internal_id> is replaced with the <name> in the prefix.
// <name> always exists in named list element, and it's an easy way to check element's existence.
String nameStorageKey = prefixJoiner.toString().replaceAll(quote(INTERNAL_ID) + "$", NamedListNode.NAME);
// Data from the storage.
Serializable name = storage.readLatest(nameStorageKey);
if (name != null) {
// Id is already known.
return (T) UUID.fromString(path.get(pathSize - 2).key);
} else {
throw new NoSuchElementException(prefixJoiner.toString());
}
}
String prefix = prefixJoiner.toString();
if (lastPathNode.key.equals(INTERNAL_ID) && !path.get(pathSize - 2).namedListEntry) {
// This is not particularly efficient, but there's no way someone will actually use this case for real outside of tests.
prefix = prefix.replaceAll(quote(KEY_SEPARATOR + INTERNAL_ID) + "$", "");
} else if (lastPathNode.key.contains(INJECTED_NAME)) {
prefix = prefix.replaceAll(quote(KEY_SEPARATOR + INJECTED_NAME), "");
}
// Data from the storage.
Map<String, ? extends Serializable> storageData = storage.readAllLatest(prefix);
// Data to be converted into the tree.
Map<String, Serializable> mergedData = new HashMap<>();
if (!storageData.isEmpty()) {
mergedData.putAll(storageData);
for (Entry<String, Map<String, Serializable>> extrasEntry : extras.entrySet()) {
for (String storageKey : storageData.keySet()) {
String extrasPrefix = extrasEntry.getKey();
if (storageKey.startsWith(extrasPrefix)) {
// Add extra order indexes and names before converting it to the tree.
for (Entry<String, Serializable> extrasEntryMap : extrasEntry.getValue().entrySet()) {
mergedData.putIfAbsent(extrasEntryMap.getKey(), extrasEntryMap.getValue());
}
break;
}
}
}
if (lastPathNode.namedListEntry) {
// Change element's order index to zero. Conversion won't work if indexes range is not continuous.
mergedData.put(prefix + KEY_SEPARATOR + NamedListNode.ORDER_IDX, 0);
}
}
// Super root that'll be filled from the storage data.
InnerNode rootNode = new SuperRoot(rootCreator());
fillFromPrefixMap(rootNode, toPrefixMap(mergedData));
// "addDefaults" won't work if regular root is missing.
if (storageData.isEmpty()) {
rootNode.construct(path.get(0).key, ConfigurationUtil.EMPTY_CFG_SRC, true);
}
addDefaults(rootNode);
return findEx(path, rootNode);
}
use of org.apache.ignite.internal.configuration.tree.InnerNode in project ignite-3 by apache.
the class ConfigurationNode method keyPath.
/**
* Converts {@link #keys} into a list of {@link KeyPathNode}. Result is used in implementations of {@link DirectPropertyProxy}.
*/
protected final List<KeyPathNode> keyPath() {
if (listenOnly) {
throw listenOnlyException();
}
ConfigurationVisitor<List<KeyPathNode>> visitor = new ConfigurationVisitor<>() {
/**
* List with the result.
*/
private List<KeyPathNode> res = new ArrayList<>(keys.size());
/**
* Current index.
*/
private int idx = 1;
/**
* {@inheritDoc}
*/
@Nullable
@Override
public List<KeyPathNode> visitLeafNode(String key, Serializable val) {
res.add(new KeyPathNode(key));
return res;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public List<KeyPathNode> visitInnerNode(String key, InnerNode node) {
res.add(new KeyPathNode(key));
if (keys.size() == idx) {
return res;
}
node.traverseChild(keys.get(idx++), this, true);
return res;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public List<KeyPathNode> visitNamedListNode(String key, NamedListNode node) {
res.add(new KeyPathNode(key));
if (keys.size() == idx) {
return res;
}
InnerNode innerNode = node.getInnerNode(keys.get(idx++));
if (innerNode == null) {
throw noSuchElementException();
}
// This is important, node is added as a resolved named list entry here.
res.add(new KeyPathNode(innerNode.internalId().toString(), false));
if (keys.size() == idx) {
return res;
}
innerNode.traverseChild(keys.get(idx++), this, true);
return res;
}
};
return changer.getRootNode(rootKey).accept(keys.get(0), visitor);
}
Aggregations