aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelBuilder.java
blob: 8c72b5c0237a773275c708da9b886e351fc7e8ec (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.model.builder.xml;

import com.yahoo.component.AbstractComponent;
import com.yahoo.config.model.ConfigModel;
import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.ConfigModelInstanceFactory;
import com.yahoo.config.model.ConfigModelRepo;
import com.yahoo.config.model.api.ConfigModelPlugin;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.vespa.model.VespaModel;
import org.w3c.dom.Element;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
 * Builds a config model using DOM parsers
 *
 * @author vegardh
 */
public abstract class ConfigModelBuilder<MODEL extends ConfigModel> extends AbstractComponent implements ConfigModelPlugin {

    private final Class<MODEL> configModelClass;

    public ConfigModelBuilder(Class<MODEL> configModelClass) {
        this.configModelClass = configModelClass;
    }

    /**
     * Method that must return the XML elements this builder handles. Subclasses must implement this in order to
     * get called when one of the elements have been encountered when parsing.
     *
     * @return a list of elements that this builder handles
     */
    public abstract List<ConfigModelId> handlesElements();

    /**
     * Convenience hook called from {@link #build}. Implement this method to build a config model.
     *
     * @param spec the XML element that this builder should handle
     * @param modelContext a model context that contains the application package and other data needed by the
     *                     config model constructor
     */
    public abstract void doBuild(MODEL model, Element spec, ConfigModelContext modelContext);

    /**
     * Builds an instance of this component model.
     * This calls instantiate(...), instance.setUp(...), doBuild(instance, ...).
     *
     * @param deployState a global deployment state used for this model.
     * @param parent the root config producer this should be added to
     * @param spec the XML element this is constructed from
     */
    public final MODEL build(DeployState deployState, VespaModel vespaModel, ConfigModelRepo configModelRepo,
                             TreeConfigProducer<AnyConfigProducer> parent, Element spec) {
        ConfigModelContext context = ConfigModelContext.create(deployState, vespaModel, configModelRepo, parent, getIdString(spec));
        return build(new DefaultModelInstanceFactory(), spec, context);
    }

    /**
     * Builds an instance of this component model.
     * This calls instantiate(...), instance.setUp(...), doBuild(instance, ...).
     *
     * @param factory A factory capable of creating models.
     * @param spec the XML element this is constructed from
     * @param context A context object containing various data used by builders.
     */
    public MODEL build(ConfigModelInstanceFactory<MODEL> factory, Element spec, ConfigModelContext context) {
        MODEL model = factory.createModel(context);
        doBuild(model, spec, context);
        return model;
    }

    public Class<MODEL> getModelClass() {
        return configModelClass;
    }

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

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof ConfigModelBuilder<?> otherBuilder)) {
            return false;
        }
        List<ConfigModelId> thisIds = this.handlesElements();
        List<ConfigModelId> otherIds = otherBuilder.handlesElements();
        if (thisIds.size() != otherIds.size()) {
            return false;
        }
        for (int i = 0; i < thisIds.size(); i++) {
            if (!thisIds.get(i).equals(otherIds.get(i))) {
                return false;
            }
        }
        return true;
    }


    private class DefaultModelInstanceFactory implements ConfigModelInstanceFactory<MODEL> {
        @Override
        public MODEL createModel(ConfigModelContext context) {
            try {
                Constructor<MODEL> constructor = configModelClass.getConstructor(ConfigModelContext.class);
                return constructor.newInstance(context);
            } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("Error constructing model '" + configModelClass.getName() + "'", e);
            }
        }
    }

}