2.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

metadatastore.h 23.4 KB
Newer Older
Alessio Netti's avatar
Alessio Netti committed
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
//================================================================================
// Name        : metadatastore.h
// Author      : Alessio Netti
// Contact     : info@dcdb.it
// Copyright   : Leibniz Supercomputing Centre
// Description : A meta-data store for sensors
//================================================================================

//================================================================================
// This file is part of DCDB (DataCenter DataBase)
// Copyright (C) 2011-2019 Leibniz Supercomputing Centre
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//================================================================================

#ifndef PROJECT_METADATASTORE_H
#define PROJECT_METADATASTORE_H

31
#include <set>
32
#include <unordered_map>
33
#include <atomic>
Alessio Netti's avatar
Alessio Netti committed
34
35
36
37
38
39
40
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <boost/foreach.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string.hpp>
41
#include <sstream>
Alessio Netti's avatar
Alessio Netti committed
42
43
44
45
#include "globalconfiguration.h"

using namespace std;

46
//TODO: evaluate if necessary to change TTL type to int64 with default -1 value
47
48
class SensorMetadata {
    
Alessio Netti's avatar
Alessio Netti committed
49
public:
50
51
52
53
54
55
56
57
58
59
60
61

    typedef enum { 
        IS_OPERATION_SET = 1,
        IS_VIRTUAL_SET = 2, 
        INTEGRABLE_SET = 4,
        MONOTONIC_SET = 8,
        PUBLICNAME_SET = 16,
        PATTERN_SET = 32,
        UNIT_SET = 64,
        SCALE_SET = 128,
        TTL_SET = 256,
        INTERVAL_SET = 512,
62
63
        OPERATIONS_SET = 1024,
	DELTA_SET = 2048
64
    } MetadataMask;
65
    
66
    SensorMetadata() :
67
68
69
70
71
72
73
74
75
76
77
        isOperation(false),
        isVirtual(false),
        integrable(false),
        monotonic(false),
        publicName(""),
        pattern(""),
        unit(""),
        scale(1),
        ttl(0),
        interval(0),
        operations(),
78
        delta(false),
79
        setMask(0) {}
80
81

    SensorMetadata(const SensorMetadata& other) {
82
        SensorMetadata();
83
84
85
86
87
88
89
90
91
92
93
94
        setMask = other.setMask;
        isOperation = other.isOperation;
        isVirtual = other.isVirtual;
        integrable = other.integrable;
        monotonic = other.monotonic;
        publicName = other.publicName;
        pattern = other.pattern;
        unit = other.unit;
        scale = other.scale;
        ttl = other.ttl;
        interval = other.interval;
        operations = other.operations;
95
	delta = other.delta;
96
    }
97

98
    SensorMetadata& operator=(const SensorMetadata& other) {
99
100
101
102
103
104
105
106
107
108
109
110
        setMask = other.setMask;
        isOperation = other.isOperation;
        isVirtual = other.isVirtual;
        integrable = other.integrable;
        monotonic = other.monotonic;
        publicName = other.publicName;
        pattern = other.pattern;
        unit = other.unit;
        scale = other.scale;
        ttl = other.ttl;
        interval = other.interval;
        operations = other.operations;
111
	delta = other.delta;
112
113
114
        return *this;
    }
    
115
116
117
118
119
120
121
    /**
     * @brief               Parses a JSON string and stores the content in this object.
     * 
     *                      If parsing fails, a InvalidArgument exception is thrown.
     * 
     * @param payload       JSON-encoded string containing metadata information
     */
122
123
124
125
126
127
    void parseJSON(const string& payload) {
        boost::property_tree::iptree config;
        std::istringstream str(payload);
        boost::property_tree::read_json(str, config);
        parsePTREE(config);
    }
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
    /**
     * @brief               Parses a CSV string and stores the content in this object.
     * 
     *                      If parsing fails, a InvalidArgument exception is thrown.
     * 
     * @param payload       CSV-encoded string containing metadata information
     */
    void parseCSV(const string& payload) {
        uint64_t fieldCtr = 0, oldPos = 0, newPos = 0;
        std::string buf = "";
        while(oldPos < payload.length() && (newPos = payload.find(",", oldPos)) != std::string::npos) {
            if((newPos - oldPos) > 0) {
                buf = payload.substr(oldPos, newPos - oldPos);
                switch (fieldCtr) {
                    case 0 :
                        setScale(stod(buf));
                        break;
                    case 1 :
                        setIsOperation(to_bool(buf));
                        break;
                    case 2 :
                        setIsVirtual(to_bool(buf));
                        break;
                    case 3 :
                        setMonotonic(to_bool(buf));
                        break;
                    case 4 :
                        setIntegrable(to_bool(buf));
                        break;
                    case 5 :
                        setUnit(buf);
                        break;
                    case 6 :
                        setPublicName(buf);
                        break;
                    case 7 :
                        setPattern(buf);
                        break;
                    case 8 :
                        setInterval(stoull(buf) * 1000000);
                        break;
                    case 9 :
                        setTTL(stoull(buf) * 1000000);
                        break;
                    case 10 :
                        setOperations(_parseOperations(buf, ','));
                        oldPos = payload.length();
                        break;
177
178
179
		    case 11 :
			setDelta(to_bool(buf));
			break;
180
181
182
183
184
185
186
187
188
189
                }
            }
            fieldCtr++;
            oldPos = newPos + 1;
        }
        if(fieldCtr < 11) {
            throw std::invalid_argument("Wrong number of fields in CSV entry!");
        }
    }

190
191
192
193
194
195
196
    /**
     * @brief               Parses a PTREE INFO block and stores the content in this object.
     *
     *                      If parsing fails, a InvalidArgument exception is thrown.
     *                      
     * @param config        PTREE block containing metadata
     */
197
198
199
    void parsePTREE(boost::property_tree::iptree& config) {
        BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
            if (boost::iequals(val.first, "monotonic")) {
200
                setMonotonic(to_bool(val.second.data()));
201
            } else if (boost::iequals(val.first, "isVirtual")) {
202
                setIsVirtual(to_bool(val.second.data()));
203
            } else if (boost::iequals(val.first, "isOperation")) {
204
                setIsOperation(to_bool(val.second.data()));
205
            } else if (boost::iequals(val.first, "integrable")) {
206
                setIntegrable(to_bool(val.second.data()));
207
            } else if (boost::iequals(val.first, "unit")) {
208
                setUnit(val.second.data());
209
            } else if (boost::iequals(val.first, "publicName")) {
210
                setPublicName(val.second.data());
211
            } else if (boost::iequals(val.first, "pattern")) {
212
                setPattern(val.second.data());
213
            } else if (boost::iequals(val.first, "scale")) {
214
                setScale(stod(val.second.data()));
215
            } else if (boost::iequals(val.first, "interval")) {
216
                setInterval(stoull(val.second.data()) * 1000000);
Alessio Netti's avatar
Alessio Netti committed
217
            } else if (boost::iequals(val.first, "ttl")) {
218
                setTTL(stoull(val.second.data()) * 1000000);
219
            } else if (boost::iequals(val.first, "operations")) {
220
                setOperations(val.second.data());
221
222
	    } else if (boost::iequals(val.first, "delta")) {
		setDelta(to_bool(val.second.data()));
223
224
225
            }
        }
    }
226
227
228
229
230
231
    
    /**
     * @brief               Converts the content of this object into JSON format.      
     * 
     * @return              String containing the JSON representation of this object
     */
232
233
234
235
236
237
238
    string getJSON() const {
        boost::property_tree::ptree config;
        std::ostringstream output;
        _dumpPTREE(config);
        boost::property_tree::write_json(output, config, true);
        return output.str();
    }
239

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
    /**
     * @brief               Converts the content of this object into CSV format.      
     * 
     * @return              String containing the CSV representation of this object
     */
    string getCSV() const {
        std::string buf = "";
        if(setMask & SCALE_SET) {
            std::ostringstream scaleStream;
            scaleStream << scale;
            buf += scaleStream.str() + ",";
        } else {
            buf += ",";
        }
        buf += (setMask & IS_OPERATION_SET) ? bool_to_str(isOperation) + "," : ",";
        buf += (setMask & IS_VIRTUAL_SET) ? bool_to_str(isVirtual) + "," : ",";
        buf += (setMask & MONOTONIC_SET) ? bool_to_str(monotonic) + "," : ",";
        buf += (setMask & INTEGRABLE_SET) ? bool_to_str(integrable) + "," : ",";
        buf += (setMask & UNIT_SET) ? unit + "," : ",";
        buf += (setMask & PUBLICNAME_SET) ? publicName + "," : ",";
        buf += (setMask & PATTERN_SET) ? pattern + "," : ",";
        buf += (setMask & INTERVAL_SET) ? to_string(interval / 1000000) + "," : ",";
        buf += (setMask & TTL_SET) ? to_string(ttl / 1000000) + "," : ",";
        buf += (setMask & OPERATIONS_SET) ? _dumpOperations(',') + "," : ",";
264
	buf += (setMask & DELTA_SET) ? bool_to_str(delta) + "," : ",";
265
266
267
        return buf;
    }

268
269
270
271
272
    /**
     * @brief               Returns a sensorMetadata_t object from the internal map, converted into PTREE format.     
     * 
     * @return              A PTREE object representing this SensorMetadata object
     */
273
274
275
276
277
    boost::property_tree::ptree getPTREE() const {
        boost::property_tree::ptree config;
        _dumpPTREE(config);
        return config;
    }
Alessio Netti's avatar
Alessio Netti committed
278
    
279
    const bool isValid()          const               { return setMask & (PUBLICNAME_SET | PATTERN_SET); }            
280
281
    
    //Getters and setters
282
283
284
285
286
287
288
289
290
291
292
293
    const bool* getIsOperation()  const               { return (setMask & IS_OPERATION_SET) ? &isOperation : nullptr; }
    const bool* getIsVirtual()    const               { return (setMask & IS_VIRTUAL_SET) ? &isVirtual : nullptr; }
    const bool* getIntegrable()   const               { return (setMask & INTEGRABLE_SET) ? &integrable : nullptr; }
    const bool* getMonotonic()    const               { return (setMask & MONOTONIC_SET) ? &monotonic : nullptr; }
    const string* getPublicName() const               { return (setMask & PUBLICNAME_SET) ? &publicName : nullptr; }
    const string* getPattern()    const               { return (setMask & PATTERN_SET) ? &pattern : nullptr; }
    const string* getUnit()       const               { return (setMask & UNIT_SET) ? &unit : nullptr; }
    const double* getScale()      const               { return (setMask & SCALE_SET) ? &scale : nullptr; }
    const uint64_t* getTTL()      const               { return (setMask & TTL_SET) ? &ttl : nullptr; }
    const uint64_t* getInterval() const               { return (setMask & INTERVAL_SET) ? &interval : nullptr; }
    const set<string>* getOperations() const          { return (setMask & OPERATIONS_SET) ? &operations : nullptr; }
    const string getOperationsString() const          { return (setMask & OPERATIONS_SET) ? _dumpOperations() : ""; }
294
295
    const bool* getDelta()        const               { return (setMask & DELTA_SET) ? &delta : nullptr; }

296
297
298
299
300
301
302
303
304
305
    void setIsOperation(bool o)                 { isOperation = o; setMask = setMask | IS_OPERATION_SET; }
    void setIsVirtual(bool v)                   { isVirtual = v; setMask = setMask | IS_VIRTUAL_SET; }
    void setIntegrable(bool i)                  { integrable = i; setMask = setMask | INTEGRABLE_SET; }
    void setMonotonic(bool m)                   { monotonic = m; setMask = setMask | MONOTONIC_SET; }
    void setPublicName(string p)                { publicName = p; setMask = setMask | PUBLICNAME_SET; }
    void setPattern(string p)                   { pattern = p; setMask = setMask | PATTERN_SET; }
    void setUnit(string u)                      { unit = u; setMask = setMask | UNIT_SET; }
    void setScale(double s)                     { scale = s; setMask = setMask | SCALE_SET; }
    void setTTL(uint64_t t)                     { ttl = t; setMask = setMask | TTL_SET; }
    void setInterval(uint64_t i)                { interval = i; setMask = setMask | INTERVAL_SET; }
306
    void setOperations(const string& o)         { setOperations(_parseOperations(o)); }
307
    void clearOperations()                      { operations.clear(); setMask = setMask & ~OPERATIONS_SET; }
308
309
    void setDelta(bool d)                       { delta = d; setMask = setMask | DELTA_SET; }

310
311
    // Merges a set of operations with the local one
    void setOperations(const set<string>& o) { 
312
313
        if(setMask & OPERATIONS_SET)
            operations.insert(o.begin(), o.end());
314
        else
315
316
            operations = set<string>(o);
        setMask = setMask | OPERATIONS_SET;
317
    }
318
319

    // Adds a single operation. Requires the publicName field to be set.
320
    // Here the operation is assumed to be the full sensor name, from which the actual operation name is extracted.
321
    bool addOperation(const string& opName) {
322
323
324
        if ((setMask & PUBLICNAME_SET) && publicName.length()>0 && opName.length()>publicName.length() 
        && !opName.compare(0, publicName.length(), publicName)) {
            setOperations(opName.substr(publicName.length()));
325
326
327
328
329
            return true;
        }
        else
            return false;
    }
330
331
332
    
protected:

333
    // Parses a operations string and sanitizes it from excess whitespace
334
335
    set<string> _parseOperations(const string& str, const char sep=',') {
        set<string> v;
336
337
        
        // We split the string into the comma-separated tokens
338
339
340
341
342
        std::stringstream ss(str);
        std::string token;
        while (std::getline(ss, token, sep)) {
            if(!token.empty()) {
                boost::algorithm::trim(token);
343
                v.insert(token);
344
345
            }
        }
346
347
348
349
350
        return v;
    }
    
    string _dumpOperations(const char sep=',') const {
        string out="";
351
352
        // We re-write the vector into a string, this time properly formatted
        string sepStr = string(1,sep);
353
354
        if(setMask & OPERATIONS_SET) {
            for (const auto &el : operations)
355
356
357
358
                out += el + sepStr;
            if (!out.empty() && out.back() == sep)
                out.erase(out.size() - 1, 1);
        }
359
        return out;
360
361
    }
    
362
    // Dumps the contents of "s" in "config"
363
364
    void _dumpPTREE(boost::property_tree::ptree& config) const {
        config.clear();
365
        if(setMask & SCALE_SET) {
366
            std::ostringstream scaleStream;
367
            scaleStream << scale;
368
369
            config.push_back(boost::property_tree::ptree::value_type("scale", boost::property_tree::ptree(scaleStream.str())));
        }
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
        if(setMask & IS_OPERATION_SET)
            config.push_back(boost::property_tree::ptree::value_type("isOperation", boost::property_tree::ptree(bool_to_str(isOperation))));
        if(setMask & IS_VIRTUAL_SET)
            config.push_back(boost::property_tree::ptree::value_type("isVirtual", boost::property_tree::ptree(bool_to_str(isVirtual))));
        if(setMask & MONOTONIC_SET)
            config.push_back(boost::property_tree::ptree::value_type("monotonic", boost::property_tree::ptree(bool_to_str(monotonic))));
        if(setMask & INTEGRABLE_SET)
            config.push_back(boost::property_tree::ptree::value_type("integrable", boost::property_tree::ptree(bool_to_str(integrable))));
        if(setMask & UNIT_SET)
            config.push_back(boost::property_tree::ptree::value_type("unit", boost::property_tree::ptree(unit)));
        if(setMask & PUBLICNAME_SET)
            config.push_back(boost::property_tree::ptree::value_type("publicName", boost::property_tree::ptree(publicName)));
        if(setMask & PATTERN_SET)
            config.push_back(boost::property_tree::ptree::value_type("pattern", boost::property_tree::ptree(pattern)));
        if(setMask & INTERVAL_SET)
            config.push_back(boost::property_tree::ptree::value_type("interval", boost::property_tree::ptree(to_string(interval / 1000000))));
        if(setMask & TTL_SET)
            config.push_back(boost::property_tree::ptree::value_type("ttl", boost::property_tree::ptree(to_string(ttl / 1000000))));
        if(setMask & OPERATIONS_SET)
389
            config.push_back(boost::property_tree::ptree::value_type("operations", boost::property_tree::ptree(_dumpOperations())));
390
391
392
	if(setMask & DELTA_SET)
	    config.push_back(boost::property_tree::ptree::value_type("delta", boost::property_tree::ptree(bool_to_str(delta))));

393
    }
394
395

    // Protected class members
396
397
398
399
400
401
402
403
404
405
406
    bool                isOperation;
    bool                isVirtual;
    bool                integrable;
    bool                monotonic;
    string              publicName;
    string              pattern;
    string              unit;
    double              scale;
    uint64_t            ttl;
    uint64_t            interval;
    set<string>         operations;
407
    bool                delta;
408
    uint64_t            setMask;
Alessio Netti's avatar
Alessio Netti committed
409
410
};

411
412
413
414
// ---------------------------------------------------------------------------
// --------------------------- METADATASTORE CLASS --------------------------- 
// ---------------------------------------------------------------------------

Alessio Netti's avatar
Alessio Netti committed
415
416
417
418
419
420
421
class MetadataStore {
    
public:
    
    /**
     * @brief               Public class constructor.
     */
422
423
    MetadataStore() {
        _updating.store(false);
424
        _access.store(0);
425
    }
Alessio Netti's avatar
Alessio Netti committed
426
427
428
429
430
431
432
433
434
    
    /**
     * @brief               Class destructor.
     */
    ~MetadataStore() {}
    
    /**
     * @brief               Clears the internal metadata map.
     */
435
436
437
    void clear() { 
        _metadata.clear(); 
    }
Alessio Netti's avatar
Alessio Netti committed
438
439
440
441
442
443
    
    /**
     * @brief               Returns the internal metadata map.
     * 
     * @return              A string to Metadata_t map
     */
444
    const unordered_map<string, SensorMetadata>& getMap() { 
445
446
        return _metadata; 
    }
Alessio Netti's avatar
Alessio Netti committed
447
    
448
449
450
451
452
    /**
     * @brief               Waits for internal updates to finish.
     */
    const void wait() {
        while(_updating.load()) {}
453
454
455
456
457
458
459
460
        ++_access;
    }

    /**
     * @brief               Reduces the internal reading counter.
     */
    const void release() {
        --_access;
461
462
    }
    
Alessio Netti's avatar
Alessio Netti committed
463
464
465
466
467
468
469
470
471
    /**
     * @brief               Stores a sensorMetadata_t object in the internal map.
     * 
     *                      If the input key already exists in the map, the entry is overwritten.
     * 
     * @param key           Sensor key under which metadata must be stored
     * @param s             Object containing sensor metadata
     * @return              True if "key" is unique, False if there was a collision
     */
472
    bool store(const string& key, const SensorMetadata& s) {
473
474
        // Spinlock to update the metadata store
        while(_updating.exchange(true)) {}
475
        while(_access.load()>0) {}
476
477
        bool overwritten = !_metadata.count(key);
        _metadata[key] = s;
478
        _updating.store(false);
479
480
        return overwritten;
    }
Alessio Netti's avatar
Alessio Netti committed
481
482
483
484
485
486
487
488
489
490
491
    
    /**
     * @brief               Stores a sensorMetadata_t object in the internal map, after parsing it from a JSON string.
     * 
     *                      If the input key already exists in the map, the entry is overwritten. If parsing fails,
     *                      a InvalidArgument exception is thrown.
     * 
     * @param key           Sensor key under which metadata must be stored
     * @param payload       JSON-encoded string containing metadata information
     * @return              True if "key" is unique, False if there was a collision
     */
492
493
494
495
496
    bool storeFromJSON(const string& key, const string& payload) {
        SensorMetadata metadata;
        metadata.parseJSON(payload);
        return store(key, metadata);
    }
Alessio Netti's avatar
Alessio Netti committed
497
498
499
500
501
502
503
504
505
506
507
    
    /**
     * @brief               Stores a sensorMetadata_t object in the internal map, after parsing it from a INFO block.
     *
     *                      If the input key already exists in the map, the entry is overwritten. If parsing fails,
     *                      a InvalidArgument exception is thrown.
     *                      
     * @param key           Sensor key under which metadata must be stored
     * @param config        PTREE block containing metadata
     * @return              True if "key" is unique, False if there was a collision
     */
508
509
510
511
512
    bool storeFromPTREE(const string& key, boost::property_tree::iptree& config) {
        SensorMetadata metadata;
        metadata.parsePTREE(config);
        return store(key, metadata);
    }
Alessio Netti's avatar
Alessio Netti committed
513
514
515
516
517
518
519
520
521
    
    /**
     * @brief               Returns a sensorMetadata_t object from the internal map. 
     * 
     *                      If the input key does not exist in the map, a InvalidArgument exception is thrown.
     * 
     * @param key           Sensor key to be queried
     * @return              A reference to a sensorMetadata_t object
     */
522
    SensorMetadata get(const string& key) {
523
524
525
526
        wait();
        auto it = _metadata.find(key);
        if(it==_metadata.end()) {
            release();
527
            throw invalid_argument("MetadataStore: key " + key + " does not exist!");
528
        } else {
529
            SensorMetadata sm = it->second;
530
531
532
            release();
            return sm;
        }
533
    }
534
535
536
537
538
539
540
541
542
543
544
545

    /**
     * @brief               Returns the TTL of a sensorMetadata_t object from the internal map. 
     * 
     *                      If the input key does not exist in the map, the value -1 is returned. This method exists
     *                      to boost (slightly) look-up performance in the CollectAgent, which requires TTL values
     *                      when performing database inserts.
     * 
     * @param key           Sensor key to be queried
     * @return              TTL value in seconds
     */
    int64_t getTTL(const string& key) {
546
        int64_t ttl = 0;
547
548
549
550
        wait();
        auto it = _metadata.find(key);
        ttl = it==_metadata.end() || !it->second.getTTL() ? -1 : *it->second.getTTL()/1000000000;
        release();
551
        return ttl;
552
    }
Alessio Netti's avatar
Alessio Netti committed
553
554
555
556
557
558
559
560
561
    
    /**
     * @brief               Returns a sensorMetadata_t object from the internal map, converted into JSON format.      
     * 
     *                      If the input key does not exist in the map, a InvalidArgument exception is thrown.
     * 
     * @param key           Sensor key to be queried
     * @return              String containing the JSON representation of the sensorMetadata_t object
     */
562
563
564
    string getJSON(const string& key) {
        return this->get(key).getJSON();
    }
Alessio Netti's avatar
Alessio Netti committed
565
566
567
568
569
570
571
572
573
    
    /**
     * @brief               Returns a sensorMetadata_t object from the internal map, converted into PTREE format.     
     * 
     *                      If the input key does not exist in the map, a InvalidArgument exception is thrown.
     * 
     * @param key           Sensor key to be queried
     * @return              A PTREE object representing the sensorMetadata_t object
     */
574
575
576
     boost::property_tree::ptree getPTREE(const string& key) {
        return this->get(key).getPTREE();
    }
Alessio Netti's avatar
Alessio Netti committed
577
578
579
    
protected:
    
580
    unordered_map<string, SensorMetadata> _metadata;
581
    atomic<bool> _updating;
582
    atomic<int>  _access;
Alessio Netti's avatar
Alessio Netti committed
583
584
585
586
587
    
};


#endif //PROJECT_METADATASTORE_H