com.yahoo.config.model.producer.TreeConfigProducer Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.model.producer;
import com.yahoo.api.annotations.Beta;
import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.vespa.model.Service;
import com.yahoo.vespa.model.SimpleConfigProducer;
import com.yahoo.vespa.model.utils.FreezableMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Superclass for all producers with children.
* Config producers constructs and returns config instances on request.
*
* @author gjoranv
* @author arnej
*/
public abstract class TreeConfigProducer
extends AnyConfigProducer
{
private final List descendantServices = new ArrayList<>();
private final FreezableMap childrenBySubId = new FreezableMap<>(LinkedHashMap.class);
/**
* Creates a new TreeConfigProducer with the given parent and subId.
* This constructor will add the resulting producer to the children of parent.
*
* @param parent the parent of this ConfigProducer
* @param subId the fragment of the config id for the producer
*/
public TreeConfigProducer(TreeConfigProducer parent, String subId) {
super(parent, subId);
}
/**
* Create an config producer with a configId only. Used e.g. to create root nodes, and producers
* that are given children after construction using {@link #addChild(AnyConfigProducer)}.
*
* @param subId The sub configId. Note that this can be prefixed when calling addChild with this producer as arg.
*/
public TreeConfigProducer(String subId) {
super(subId);
}
/**
* Helper to provide an error message on collisions of sub ids (ignore SimpleConfigProducer, use the parent in that case)
*/
private String errorMsgClassName() {
if (getClass().equals(SimpleConfigProducer.class)) return getParent().getClass().getSimpleName();
return getClass().getSimpleName();
}
/**
* Adds a child to this config producer.
*
* @param child the child config producer to add
*/
protected void addChild(CHILD child) {
if (child == null) {
throw new IllegalArgumentException("Trying to add null child for: " + this);
}
if (child instanceof AbstractConfigProducerRoot) {
throw new IllegalArgumentException("Child cannot be a root node: " + child);
}
child.setParent(this);
if (childrenBySubId.get(child.getSubId()) != null) {
throw new IllegalArgumentException("Multiple services/instances of the id '" + child.getSubId() + "' under the service/instance " +
errorMsgClassName() + " '" + getSubId() + "'. (This is commonly caused by service/node index " +
"collisions in the config.)." +
"\nExisting instance: " + childrenBySubId.get(child.getSubId()) +
"\nAttempted to add: " + child +
"\nStack trace: " + Arrays.toString(Thread.currentThread().getStackTrace()));
}
childrenBySubId.put(child.getSubId(), child);
if (child instanceof Service) {
addDescendantService((Service)child);
}
}
public void removeChild(CHILD child) {
if (child.getParent() != this)
throw new IllegalArgumentException("Could not remove " + child + ": Expected its parent to be " +
this + ", but was " + child.getParent());
if (child instanceof Service)
descendantServices.remove(child);
childrenBySubId.remove(child.getSubId());
child.setParent(null);
}
/** Returns this ConfigProducer's children (only 1st level) */
public Map getChildren() { return Collections.unmodifiableMap(childrenBySubId); }
@Beta
public List getChildrenByTypeRecursive(Class type) {
List validChildren = new ArrayList<>();
if (this.getClass().equals(type)) {
validChildren.add(type.cast(this));
}
Map children = this.getChildren();
for (CHILD child : children.values()) {
validChildren.addAll(child.getChildrenByTypeRecursive(type));
}
return Collections.unmodifiableList(validChildren);
}
/** Returns a list of all the children of this who are instances of Service */
public List getDescendantServices() { return Collections.unmodifiableList(descendantServices); }
protected void addDescendantService(Service s) { descendantServices.add(s); }
void setupConfigId(String parentConfigId) {
super.setupConfigId(parentConfigId);
setupChildConfigIds(getConfigIdPrefix());
}
String getConfigIdPrefix() {
if (this instanceof AbstractConfigProducerRoot || this instanceof ApplicationConfigProducerRoot) {
return "";
}
if (currentConfigId() == null) {
return null;
}
return getConfigId() + "/";
}
@Override
protected ClassLoader getConfigClassLoader(String producerName) {
ClassLoader classLoader = findInheritedClassLoader(getClass(), producerName);
if (classLoader != null)
return classLoader;
// TODO: Make logic correct, so that the deepest child will be the one winning.
for (AnyConfigProducer child : childrenBySubId.values()) {
ClassLoader loader = child.getConfigClassLoader(producerName);
if (loader != null) {
return loader;
}
}
return null;
}
private void setupChildConfigIds(String currentConfigId) {
for (AnyConfigProducer child : childrenBySubId.values()) {
child.setupConfigId(currentConfigId);
}
}
@Override
void aggregateDescendantServices() {
for (CHILD child : childrenBySubId.values()) {
child.aggregateDescendantServices();
descendantServices.addAll(child.getDescendantServices());
}
}
@Override
void freeze() {
childrenBySubId.freeze();
for (CHILD child : childrenBySubId.values()) {
child.freeze();
}
}
@Override
public void validate() throws Exception {
assert (childrenBySubId.isFrozen());
for (CHILD child : childrenBySubId.values()) {
child.validate();
}
}
}