All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.config.model.ConfigModelRepo 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;

import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.ConfigModelContext.ApplicationType;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
import com.yahoo.config.model.builder.xml.ConfigModelId;
import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.graph.ModelGraphBuilder;
import com.yahoo.config.model.graph.ModelNode;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.config.model.provision.HostsXmlProvisioner;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.builder.VespaModelBuilder;
import com.yahoo.vespa.model.content.Content;
import com.yahoo.vespa.model.routing.Routing;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A collection of config model instances owned by a system model
 *
 * @author gjoranv
 */
public class ConfigModelRepo implements ConfigModelRepoAdder, Iterable {

    private static final Logger log = Logger.getLogger(ConfigModelRepo.class.getPackage().toString());

    private final Map configModelMap = new TreeMap<>();
    private final List configModels = new ArrayList<>();

    /**
     * Returns a config model for a given id
     *
     * @param id the id of the model to return
     * @return the model, or none if a model with this id is not present in this
     */
    public ConfigModel get(String id) {
        return configModelMap.get(id);
    }

    /** Adds a new config model instance in this */
    @Override
    public void add(ConfigModel model) {
        configModelMap.put(model.getId(), model);
        configModels.add(model);
    }

    /** Returns the models in this as an iterator */
    public Iterator iterator() {
        return configModels.iterator();
    }

    /** Returns a read-only view of the config model instances of this */
    public Map asMap() { return Collections.unmodifiableMap(configModelMap); }

    /** Initialize part 1.: Reads the config models used in the application package. */
    public void readConfigModels(DeployState deployState,
                                 VespaModel vespaModel,
                                 VespaModelBuilder builder,
                                 ApplicationConfigProducerRoot root,
                                 ConfigModelRegistry configModelRegistry) throws IOException {
        Element userServicesElement = getServicesFromApp(deployState.getApplicationPackage());
        readConfigModels(root, userServicesElement, deployState, vespaModel, configModelRegistry);
        builder.postProc(deployState, root, this);
    }

    private Element getServicesFromApp(ApplicationPackage applicationPackage) throws IOException {
        try (Reader servicesFile = applicationPackage.getServices()) {
            return getServicesFromReader(servicesFile);
        }
    }

    /**
     * If the top level is <services>, it contains a list of services elements,
     * otherwise, the top level tag is a single service.
     */
    private List getServiceElements(Element servicesRoot) {
        if (servicesRoot.getTagName().equals("services"))
            return XML.getChildren(servicesRoot);
        List singleServiceList = new ArrayList<>(1);
        singleServiceList.add(servicesRoot);
        return singleServiceList;
    }

    /**
     * Creates all the config models specified in the given XML element and
     * passes their respective XML node as parameter.
     *
     * @param root The Root to set as parent for all plugins
     * @param servicesRoot XML root node of the services file
     */
    private void readConfigModels(ApplicationConfigProducerRoot root,
                                  Element servicesRoot,
                                  DeployState deployState,
                                  VespaModel vespaModel,
                                  ConfigModelRegistry configModelRegistry) {
        final Map> model2Element = new LinkedHashMap<>();
        ModelGraphBuilder graphBuilder = new ModelGraphBuilder();

        final List children = getServiceElements(servicesRoot);

        if (XML.getChild(servicesRoot, "admin") == null)
            children.add(getImplicitAdmin(deployState));

        for (Element servicesElement : children) {
            String tagName = servicesElement.getTagName();
            if (tagName.equals("legacy")) {
                // for enabling legacy features from old vespa versions
                continue;
            }
            if (tagName.equals("config")) {
                // Top level config, mainly to be used by the Vespa team.
                continue;
            }

            String tagVersion = servicesElement.getAttribute("version");
            ConfigModelId xmlId = ConfigModelId.fromNameAndVersion(tagName, tagVersion);

            Collection builders = configModelRegistry.resolve(xmlId);

            if (builders.isEmpty())
                throw new IllegalArgumentException("Could not resolve tag <" + tagName + " version=\"" + tagVersion + "\"> to a config model component");

            for (ConfigModelBuilder builder : builders) {
                if ( ! model2Element.containsKey(builder)) {
                    model2Element.put(builder, new ArrayList<>());
                    graphBuilder.addBuilder(builder);
                }
                model2Element.get(builder).add(servicesElement);
            }
        }

        for (ModelNode node : graphBuilder.build().topologicalSort())
            buildModels(node, getApplicationType(servicesRoot), deployState, vespaModel, root, model2Element.get(node.builder));
        for (ConfigModel model : configModels)
            model.initialize(ConfigModelRepo.this); // XXX deprecated
    }

    private ApplicationType getApplicationType(Element servicesRoot) {
        return XmlHelper.getOptionalAttribute(servicesRoot, "application-type")
                .map(ApplicationType::fromString)
                .orElse(ApplicationType.DEFAULT);
    }

    private Element getServicesFromReader(Reader reader) {
        Document doc = XmlHelper.getDocument(reader);
        return doc.getDocumentElement();
    }

    private void buildModels(ModelNode node,
                             ApplicationType applicationType,
                             DeployState deployState,
                             VespaModel vespaModel,
                             TreeConfigProducer parent,
                             List elements) {
        for (Element servicesElement : elements) {
            ConfigModel model = buildModel(node, applicationType, deployState, vespaModel, parent, servicesElement);
            if (model.isServing())
                add(model);
        }
    }

    private ConfigModel buildModel(ModelNode node,
                                   ApplicationType applicationType,
                                   DeployState deployState,
                                   VespaModel vespaModel,
                                   TreeConfigProducer parent,
                                   Element servicesElement) {
        ConfigModelBuilder builder = node.builder;
        ConfigModelContext context = ConfigModelContext.create(applicationType, deployState, vespaModel, this, parent, getIdString(servicesElement));
        return builder.build(node, servicesElement, context);
    }

    private static String getIdString(Element spec) {
        String idString = XmlHelper.getIdString(spec);
        if (idString == null || idString.isEmpty()) {
            idString = spec.getTagName();
        }
        return idString;
    }

    /**
     * Initialize part 2.:
     * Prepare all config models for starting. Must be called after plugins are loaded and frozen.
     */
    public void prepareConfigModels(DeployState deployState) {
        for (ConfigModel model : configModels) {
            model.prepare(this, deployState);
        }
    }

    @SuppressWarnings("unchecked")
    public  List getModels(Class modelClass) {
        List modelsOfModelClass = new ArrayList<>();

        for (ConfigModel model : configModels) {
            if (modelClass.isInstance(model))
                modelsOfModelClass.add((T)model);
        }
        return modelsOfModelClass;
    }

    public Routing getRouting() {
        for (ConfigModel m : configModels) {
            if (m instanceof Routing) {
                return (Routing)m;
            }
        }
        return null;
    }

    public Content getContent() {
        for (ConfigModel m : configModels) {
            if (m instanceof Content) {
                return (Content)m;
            }
        }
        return null;
    }

    // TODO: Doctoring on the XML is the wrong level for this. We should be able to mark a model as default instead   -Jon
    private static Element getImplicitAdmin(DeployState deployState) {
        String defaultAdminElement = deployState.isHosted() ? getImplicitAdminV4() : getImplicitAdminV2();
        log.log(Level.FINE, () -> "No  defined, using " + defaultAdminElement);
        return XmlHelper.getDocument(new StringReader(defaultAdminElement)).getDocumentElement();
    }

    private static String getImplicitAdminV2() {
        return "\n" +
               "  \n" +
               "\n";
    }

    private static String getImplicitAdminV4() {
        return """
                
                  
                
                """;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy