summaryrefslogtreecommitdiffstats
path: root/src/lib/config/config_data.cc
blob: 82537bc96a701429603279cbfd78604b4bbf9e7a (plain)
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config/config_data.h>

#include <boost/foreach.hpp>

#include <string>
#include <iostream>

using namespace isc::data;

namespace {

// Returns the '_spec' part of a list or map specification (recursively,
// i.e. if it is a list of lists or maps, will return the spec of the
// inner-most list or map).
//
// \param spec_part the list or map specification (part)
// \return the value of spec_part's "list_item_spec" or "map_item_spec",
//         or the original spec_part, if it is not a MapElement or does
//         not contain "list_item_spec" or "map_item_spec"
ConstElementPtr findListOrMapSubSpec(ConstElementPtr spec_part) {
    while (spec_part->getType() == Element::map &&
           (spec_part->contains("list_item_spec") ||
            spec_part->contains("map_item_spec"))) {
        if (spec_part->contains("list_item_spec")) {
            spec_part = spec_part->get("list_item_spec");
        } else {
            spec_part = spec_part->get("map_item_spec");
        }
    }
    return spec_part;
}

// Returns a specific Element in a given specification ListElement
//
// \exception DataNotFoundError if the given identifier does not
// point to an existing element. Since we are dealing with the
// specification here, and not the config data itself, this should
// not happen, and is a code bug.
//
// \param spec_part ListElement to find the element in
// \param id_part the name of the element to find (must match the value
//                "item_name" in the list item
// \param id_full the full identifier id_part is a part of, this is
//                used to better report any errors
ConstElementPtr findItemInSpecList(ConstElementPtr spec_part,
                                   const std::string& id_part,
                                   const std::string& id_full)
{
    bool found = false;
    BOOST_FOREACH(ConstElementPtr list_el, spec_part->listValue()) {
        if (list_el->getType() == Element::map &&
            list_el->contains("item_name") &&
            list_el->get("item_name")->stringValue() == id_part) {
            spec_part = list_el;
            found = true;
        }
    }
    if (!found) {
        isc_throw(isc::config::DataNotFoundError,
                  id_part + " in " + id_full + " not found");
    }
    return (spec_part);
}

} // anonymous namespace

namespace isc {
namespace config {

//
// Return a part of a specification, as identified by the
// '/'-separated identifier.
// If it cannot be found, a DataNotFound error is thrown.
//
// Recursively goes through the Element. If it is a List,
// we search it contents to have 'items' (i.e. contain item_name)
// If it is a map, we search through the list contained in its
// 'map_item_spec' value. This code assumes the data has been
// validated and conforms to the specification.
static ConstElementPtr
find_spec_part(ConstElementPtr spec, const std::string& identifier) {
    if (!spec) {
        isc_throw(DataNotFoundError, "Empty specification");
    }

    ConstElementPtr spec_part = spec;
    if (identifier == "") {
        isc_throw(DataNotFoundError, "Empty identifier");
    }
    std::string id = identifier;
    size_t sep = id.find('/');
    while(sep != std::string::npos) {
        std::string part = id.substr(0, sep);

        if (spec_part->getType() == Element::list) {
            spec_part = findItemInSpecList(spec_part, part, identifier);
        } else {
            isc_throw(DataNotFoundError,
                      "Not a list of spec items: " + spec_part->str());
        }
        id = id.substr(sep + 1);
        sep = id.find("/");

        // As long as we are not in the 'final' element as specified
        // by the identifier, we want to automatically traverse list
        // and map specifications
        if (id != "" && id != "/") {
            spec_part = findListOrMapSubSpec(spec_part);
        }
    }
    if (id != "" && id != "/") {
        if (spec_part->getType() == Element::list) {
            spec_part = findItemInSpecList(spec_part, id, identifier);
        } else if (spec_part->getType() == Element::map) {
            if (spec_part->contains("map_item_spec")) {
                spec_part = findItemInSpecList(
                                spec_part->get("map_item_spec"),
                                id, identifier);
            } else {
                // Either we already have the element we are looking
                // for, or we are trying to reach something that does
                // not exist (i.e. the code does not match the spec)
                if (!spec_part->contains("item_name") ||
                    spec_part->get("item_name")->stringValue() != id) {
                    isc_throw(DataNotFoundError, "Element above " + id +
                                                 " in " + identifier +
                                                 " is not a map: " + spec_part->str());
                }
            }
        }
    }
    return (spec_part);
}

//
// Adds the names of the items in the given specification part.
// If recurse is true, maps will also have their children added.
// Result must be a ListElement
//
static void
spec_name_list(ElementPtr result, ConstElementPtr spec_part,
               const std::string& prefix, bool recurse = false)
{
    if (spec_part->getType() == Element::list) {
        BOOST_FOREACH(ConstElementPtr list_el, spec_part->listValue()) {
            if (list_el->getType() == Element::map &&
                list_el->contains("item_name")) {
                std::string new_prefix = prefix;
                if (prefix != "") {
                    new_prefix += "/";
                }
                new_prefix += list_el->get("item_name")->stringValue();
                if (recurse && list_el->get("item_type")->stringValue() == "map") {
                    spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse);
                } else {
                    result->add(Element::create(new_prefix));
                }
            }
        }
    } else if (spec_part->getType() == Element::map &&
               spec_part->contains("map_item_spec")) {
        spec_name_list(result, spec_part->get("map_item_spec"), prefix,
                       recurse);
    }
}

ConstElementPtr
ConfigData::getValue(const std::string& identifier) const {
    // 'fake' is set, but dropped by this function and
    // serves no further purpose.
    bool fake;
    return (getValue(fake, identifier));
}

ConstElementPtr
ConfigData::getValue(bool& is_default, const std::string& identifier) const {
    ConstElementPtr value = _config->find(identifier);
    if (value) {
        is_default = false;
    } else {
        ConstElementPtr spec_part =
            find_spec_part(_module_spec.getConfigSpec(), identifier);
        if (spec_part->contains("item_default")) {
            value = spec_part->get("item_default");
            is_default = true;
        } else {
            is_default = false;
            value = ElementPtr();
        }
    }
    return (value);
}

ConstElementPtr
ConfigData::getDefaultValue(const std::string& identifier) const {
    ConstElementPtr spec_part =
        find_spec_part(_module_spec.getConfigSpec(), identifier);
    if (spec_part->contains("item_default")) {
        return spec_part->get("item_default");
    } else {
        isc_throw(DataNotFoundError, "No default for " + identifier);
    }
}

/// Returns an ElementPtr pointing to a ListElement containing
/// StringElements with the names of the options at the given
/// identifier. If recurse is true, maps will be expanded as well
ConstElementPtr
ConfigData::getItemList(const std::string& identifier, bool recurse) const {
    ElementPtr result = Element::createList();
    ConstElementPtr spec_part = getModuleSpec().getConfigSpec();
    if (identifier != "" && identifier != "/") {
        spec_part = find_spec_part(spec_part, identifier);
    }
    spec_name_list(result, spec_part, identifier, recurse);
    return (result);
}

/// Returns an ElementPtr containing a MapElement with identifier->value
/// pairs.
ConstElementPtr
ConfigData::getFullConfig() const {
    ElementPtr result = Element::createMap();
    ConstElementPtr items = getItemList("", false);
    BOOST_FOREACH(ConstElementPtr item, items->listValue()) {
        result->set(item->stringValue(), getValue(item->stringValue()));
    }
    return (result);
}

}
}