bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
TheBESKeys.cc
1// TheBESKeys.cc
2
3// This file is part of bes, A C++ back-end server implementation framework
4// for the OPeNDAP Data Access Protocol.
5
6// Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7// Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8//
9// This library is free software; you can redistribute it and/or
10// modify it under the terms of the GNU Lesser General Public
11// License as published by the Free Software Foundation; either
12// version 2.1 of the License, or (at your option) any later version.
13//
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this library; if not, write to the Free Software
21// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22//
23// You can contact University Corporation for Atmospheric Research at
24// 3080 Center Green Drive, Boulder, CO 80301
25
26// (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27// Please read the full copyright statement in the file COPYRIGHT_UCAR.
28//
29// Authors:
30// pwest Patrick West <pwest@ucar.edu>
31// jgarcia Jose Garcia <jgarcia@ucar.edu>
32
33#include "config.h"
34
35#if HAVE_UNISTD_H
36#include <unistd.h>
37#endif
38
39#include <string>
40#include <vector>
41#include <map>
42#include <unordered_map>
43#include <sstream>
44#include <memory>
45
46#include "BESDebug.h"
47#include "TheBESKeys.h"
48#include "kvp_utils.h"
49#include "BESUtil.h"
50#include "BESInternalError.h"
51
52#define BES_INCLUDE_KEY "BES.Include"
53
54using namespace std;
55
56#define MODULE "bes"
57#define prolog std::string("TheBESKeys::").append(__func__).append("() - ")
58
65
66static string get_the_config_filename() {
67 string config_file = TheBESKeys::ConfigFile;
68 if (config_file.empty()) {
69 // d_instance is a nullptr and TheBESKeys::ConfigFile is ""
70 // so lets try some obvious places...
71 // Add to the vector to try more places.
72
73 vector<string> try_ini{"/usr/local/etc/bes/bes.conf", "/etc/bes/bes.conf", "/usr/etc/bes/bes.conf"};
74 for (const auto &ini : try_ini) {
75 if (access(ini.c_str(), R_OK) == 0) {
76 config_file = ini;
77 break;
78 }
79 }
80 }
81
82 return config_file;
83}
84
85TheBESKeys *TheBESKeys::TheKeys() {
86 static TheBESKeys instance(get_the_config_filename());
87 return &instance;
88}
89
106TheBESKeys::TheBESKeys(string keys_file_name) : d_keys_file_name(std::move(keys_file_name))
107{
108 if (!d_keys_file_name.empty()) {
109 kvp::load_keys(d_keys_file_name, d_ingested_key_files, d_the_keys);
110#if DYNAMIC_CONFIG_ENABLED
111 *d_the_original_keys = d_the_keys;
112#endif
113 }
114
115 BESDEBUG(MODULE, prolog << " d_keys_file_name: " << d_keys_file_name << endl);
116 BESDEBUG(MODULE, prolog << " d_the_keys.size(): " << d_the_keys.size() << endl);
117#if DYNAMIC_CONFIG_ENABLED
118 BESDEBUG(MODULE, prolog << "d_the_original_keys.size(): " << d_the_original_keys->size() << endl);
119#endif
120}
121
127void TheBESKeys::reload_keys(const std::string &keys_file_name)
128{
129 d_keys_file_name = keys_file_name;
130 reload_keys();
131}
132
138{
139 d_the_keys.clear();
140 d_ingested_key_files.clear();
141 kvp::load_keys(d_keys_file_name, d_ingested_key_files, d_the_keys);
142}
143
160void TheBESKeys::set_key(const string &key, const string &val, bool addto)
161{
162 auto i = d_the_keys.find(key);
163 if (i == d_the_keys.end()) {
164 vector<string> vals;
165 d_the_keys[key] = vals;
166 }
167 if (!addto) d_the_keys[key].clear();
168 if (!val.empty()) {
169 d_the_keys[key].push_back(val);
170 }
171}
172
190void TheBESKeys::set_keys(const string &key, const vector<string> &values, bool addto)
191{
192 auto i = d_the_keys.find(key);
193 if (i == d_the_keys.end()) {
194 vector<string> vals;
195 d_the_keys[key] = vals;
196 }
197 if (!addto) d_the_keys[key].clear();
198
199 for(const auto &value: values) {
200 if (!value.empty()) {
201 d_the_keys[key].push_back(value);
202 }
203 }
204}
205
224 const string &key,
225 const unordered_map<string, string> &values,
226 const bool case_insensitive_map_keys,
227 bool addto)
228{
229 auto i = d_the_keys.find(key);
230 if (i == d_the_keys.end()) {
231 vector<string> vals;
232 d_the_keys[key] = vals;
233 }
234 if (!addto) {
235 d_the_keys[key].clear();
236 }
237
238 for(auto &p : values){
239 string map_key = p.first;
240 if(map_key.empty() ){
241 BESDEBUG(MODULE, prolog << "The map_key is empty. SKIPPING." << endl);
242 }
243 else {
244 if(case_insensitive_map_keys){
245 map_key = BESUtil::lowercase(map_key);
246 }
247 string map_record=map_key+":"+p.second;
248 d_the_keys[key].push_back(map_record);
249 }
250
251 }
252}
253
265void TheBESKeys::set_key(const string &pair)
266{
267 string key;
268 string val;
269 bool addto = false;
270 kvp::break_pair(pair.c_str(), key, val, addto);
271 set_key(key, val, addto);
272}
273
288void TheBESKeys::get_value(const string &key, string &val, bool &found)
289{
290 found = false;
291 const auto &i = d_the_keys.find(key);
292 if (i != d_the_keys.end()) {
293 found = true;
294 if (i->second.size() > 1) {
295 string err = string("Multiple values for the key ") + key + " found, there should only be one.";
296 throw BESInternalError(err, __FILE__, __LINE__);
297 }
298 else if (i->second.size() == 1) {
299 val = i->second[0];
300 }
301 else {
302 val = "";
303 }
304 }
305}
306
318void TheBESKeys::get_values(const string& key, vector<string> &vals, bool &found)
319{
320 found = false;
321 const auto &i = d_the_keys.find(key);
322 if (i != d_the_keys.end()) {
323 found = true;
324 for (const auto &value: i->second) {
325 vals.push_back(value);
326 }
327 }
328}
329
342bool TheBESKeys::read_bool_key(const string &key, bool default_value)
343{
344 bool found = false;
345 string value;
346 TheBESKeys::TheKeys()->get_value(key, value, found);
347 // 'key' holds the string value at this point if key_found is true
348 if (found) {
349 value = BESUtil::lowercase(value);
350 return (value == "true" || value == "yes"|| value == "on");
351 }
352 else {
353 return default_value;
354 }
355 }
356
367string TheBESKeys::read_string_key(const string &key, const string &default_value)
368{
369 bool found = false;
370 string value;
371 TheBESKeys::TheKeys()->get_value(key, value, found);
372 // 'value' holds the string value at this point if found is true
373 if (found) {
374 // Wrote and used this in place of the more cumbersome if(...) since this
375 // same operation is performed in many places in our software. jhrg 1/26/22
377 return value;
378 }
379 else {
380 return default_value;
381 }
382}
383
394int TheBESKeys::read_int_key(const string &key, int default_value)
395{
396 bool found = false;
397 string value;
398 TheBESKeys::TheKeys()->get_value(key, value, found);
399 // 'key' holds the string value at this point if found is true
400 if (found) {
401 std::istringstream iss(value);
402 int int_val;
403 iss >> int_val;
404 if (!iss.eof() || iss.bad() || iss.fail())
405 return default_value;
406 else
407 return int_val;
408 }
409 else {
410 return default_value;
411 }
412}
413
424unsigned long TheBESKeys::read_ulong_key(const string &key, unsigned long default_value)
425{
426 bool found = false;
427 string value;
428 TheBESKeys::TheKeys()->get_value(key, value, found);
429 // 'key' holds the string value at this point if found is true
430 if (found) {
431 std::istringstream iss(value);
432 int int_val;
433 iss >> int_val;
434 if (!iss.eof() || iss.bad() || iss.fail())
435 return default_value;
436 else
437 return int_val;
438 }
439 else {
440 return default_value;
441 }
442}
443
454
455
456uint64_t TheBESKeys::read_uint64_key(const string &key, uint64_t default_value)
457{
458 bool found = false;
459 string value;
460 TheBESKeys::TheKeys()->get_value(key, value, found);
461 // 'key' holds the string value at this point if found is true
462 if (found) {
463 std::istringstream iss(value);
464 uint64_t uint64_val;
465 iss >> uint64_val;
466 if (!iss.eof() || iss.bad() || iss.fail())
467 return default_value;
468 else
469 return uint64_val;
470 }
471 else {
472 return default_value;
473 }
474}
475
489{
490 // We copy the keys into a std::map because they need to be sorted.
491 map<string, vector<std::string>, std::less<> > sorted_keys;
492 for(const auto &key_entry: d_the_keys){
493 sorted_keys.insert( pair<string, vector<string> >(key_entry.first,( vector<string>(key_entry.second) )));
494 }
495 stringstream ss;
496 ss << endl;
497 ss << "# TheBESKeys::get_as_config()" << endl;
498 if (!sorted_keys.empty()) {
499 for (const auto &key_entry : sorted_keys) {
500 string name = key_entry.first;
501 vector<string> values = key_entry.second;
502 bool first = true;
503 for(const string &value: values){
504 ss << name << (first?"=":"+=") << value << endl;
505 first = false;
506 }
507 }
508 }
509 else {
510 ss << "# TheBESKeys are empty()" << endl;
511 }
512 return ss.str();
513}
514
515constexpr auto MAP_SEPARATOR = ':';
516
517bool parse_map_record(const string &map_record, const bool &case_insensitive_map_keys, string &key, string &value) {
518 size_t primary_index = map_record.find(MAP_SEPARATOR);
519 if (primary_index > 0) {
520 key = map_record.substr(0, primary_index);
521 if (case_insensitive_map_keys)
522 key = BESUtil::lowercase(key);
523 value = map_record.substr(primary_index + 1);
524 BESDEBUG(MODULE, prolog << "key: '" << key << "' value: " << value << endl);
525 return true;
526 }
527 return false;
528}
529
538 const std::string &key,
539 std::unordered_map<std::string,std::string> &map_values,
540 bool case_insensitive_map_keys,
541 bool &found) {
542
543 vector<string> values;
544 get_values(key, values, found);
545 if (!found) {
546 return;
547 }
548
549 for (const auto &value: values) {
550 string map_key;
551 string map_value;
552 if (parse_map_record(value, case_insensitive_map_keys, map_key, map_value)) {
553 map_values.insert( std::pair<string,string>(map_key, map_value));
554 }
555 else {
556 BESDEBUG(MODULE, prolog << string("The configuration entry for the ") << key << " was not " <<
557 "formatted as a map record. The offending entry: " << value << " HAS BEEN SKIPPED." << endl);
558 }
559 }
560}
561
570 const std::string &key,
571 std::unordered_map< std::string, std::unordered_map<std::string,std::vector<std::string> > > &primary_map,
572 bool case_insensitive_map_keys,
573 bool &found){
574
575 BESDEBUG(MODULE, prolog << "BEGIN" << endl);
576 vector<string> values;
577 get_values(key, values, found);
578 if (!found) {
579 return;
580 }
581
582 for (const auto &map_record: values) {
583 string primary_map_key;
584 string primary_map_value;
585 if (parse_map_record(map_record, case_insensitive_map_keys, primary_map_key, primary_map_value)) {
586 string secondary_key;
587 string secondary_value;
588 if (parse_map_record(primary_map_value, case_insensitive_map_keys, secondary_key, secondary_value)) {
589 const auto &pit = primary_map.find(primary_map_key);
590 if (pit != primary_map.end()) {
591 const auto &sit = pit->second.find(secondary_key);
592 if (sit != pit->second.end()) {
593 sit->second.push_back(secondary_value);
594 }
595 else {
596 // How to make a vector<string>> and poke in to the secondary_map??
597 vector<string> secondary_map_entry_values;
598 secondary_map_entry_values.push_back(secondary_value);
599 pit->second.insert(pair<string, vector<string>>(secondary_key, secondary_map_entry_values));
600 }
601 }
602 else {
603 // How to make a map<string,vector<string>> and poke in to the primary_map??
604 unordered_map< string, vector<string> > secondary_map_entry;
605 vector< string > secondary_map_entry_values;
606 secondary_map_entry_values.push_back(secondary_value);
607 secondary_map_entry.insert(pair< string, vector<string> >(secondary_key, secondary_map_entry_values));
608 primary_map.insert(pair<string, unordered_map<string, vector<string> > >(primary_map_key, secondary_map_entry));
609
610 }
611 }
612 else {
613 // Map entry improperly formatted.
614 BESDEBUG(MODULE, prolog << string("The configuration entry for the ") << key << " was not " <<
615 "formatted as a map record. The offending entry: " << map_record <<
616 " HAS BEEN SKIPPED." << endl);
617 }
618 }
619 else {
620 BESDEBUG(MODULE, prolog << string("The configuration entry for the ") << key << " was not " <<
621 "formatted as a map record. The offending entry: " << map_record <<
622 " HAS BEEN SKIPPED." << endl);
623 }
624 }
625 BESDEBUG(MODULE, prolog << "END" << endl);
626
627}
628
633void TheBESKeys::load_dynamic_config(const string &name)
634{
635#if DYNAMIC_CONFIG_ENABLED
636 BESDEBUG(MODULE, prolog << "BEGIN" << endl);
637
638 // Clear the active keys and copy the original keys into
639 // the active keys (resets the keys to 'as read from config files')
640 if( d_dynamic_config_in_use ){
641 BESDEBUG(MODULE, prolog << "Unloading DynamicConfig." << endl);
642 d_the_keys.clear();
643 d_the_keys = *d_the_original_keys;
644 d_dynamic_config_in_use = false;
645 }
646
647 map<string, map<string, vector<string>>> dynamic_confg;
648 bool found;
649 get_values(DYNAMIC_CONFIG_KEY, dynamic_confg, true, found);
650 if(!found){
651 BESDEBUG(MODULE, prolog << "Unable to locate " << DYNAMIC_CONFIG_KEY
652 << " in the configuration keys." << endl);
653 return;
654 }
655 BESDEBUG(MODULE, prolog << "Found a " << DYNAMIC_CONFIG_KEY << " in TheBESKeys." << endl);
656
657 string best_matching_config_name;
658 long longest_match=0;
659 auto best_matching_config = dynamic_confg.end();
660
661 for(auto dcit = dynamic_confg.begin(); dcit != dynamic_confg.end(); dcit++){
662 BESDEBUG(MODULE, prolog << "Processing " << DYNAMIC_CONFIG_KEY << "["<<dcit->first<< "]" << endl);
663
664 auto rit = dcit->second.find(DC_REGEX_KEY);
665 if(rit==dcit->second.end()){
666 BESDEBUG(MODULE, prolog << "Could not find a " << DC_REGEX_KEY << " (regular expression) for the "
667 << DYNAMIC_CONFIG_KEY << " named: " << dcit->first << " SKIPPING!" << endl);
668 }
669 else {
670 BESDEBUG(MODULE, prolog << "Found " << DC_REGEX_KEY << " vector for "
671 << DYNAMIC_CONFIG_KEY << "["<< dcit->first << "]" << endl);
672 for(auto vit = rit->second.begin(); vit != rit->second.end(); vit ++){ // For all the regex expressions
673 BESDEBUG(MODULE, prolog << "Processing " << DC_REGEX_KEY << " value '" << *vit << "'" << endl);
674 BESRegex regex((*vit).c_str()); // make BESRegex
675 long match_length = regex.match(name.c_str(),name.size(),0); // Eval match
676
677 BESDEBUG(MODULE, prolog << "The name '"<< name << (match_length<0?"' does not match ":"' matches ")
678 << "the regular expression: '"<< *vit << "' (match_length: " << match_length << ")" << endl);
679 if(match_length>longest_match){ // Is is a better match?
680 BESDEBUG(MODULE, prolog << "match_length of " << match_length
681 << " is larger than the current longest_match of "<< longest_match << endl);
682
683 auto cit = dcit->second.find(DC_CONFIG_KEY);
684 if(cit==dcit->second.end() || cit->second.empty()){ // does it have a config?
685 BESDEBUG(MODULE, prolog << "There were no " << DC_CONFIG_KEY
686 << " (configuration) values for the " << DYNAMIC_CONFIG_KEY << " named: "
687 << dcit->first << " SKIPPING!" << endl);
688 }
689 else {
690
691 best_matching_config = dcit;
692 longest_match = match_length;
693 best_matching_config_name = dcit->first;
694 BESDEBUG(MODULE, prolog << "Found new best " << DYNAMIC_CONFIG_KEY << " match for '" << name
695 << "' " << DYNAMIC_CONFIG_KEY << ": " << best_matching_config_name << endl);
696 }
697 }
698 }
699 }
700 }
701
702 if( longest_match==0 || best_matching_config==dynamic_confg.end() ){
703 BESDEBUG(MODULE, prolog << "None of the " << DYNAMIC_CONFIG_KEY
704 << " regex patterns matched the name: " << name << endl);
705 return;
706 }
707
708 {
709 stringstream msg;
710 msg << prolog << "Using " << DYNAMIC_CONFIG_KEY << ":" << best_matching_config_name << " for: " << name << endl;
711 BESDEBUG(MODULE, msg.str());
712 INFO_LOG( msg.str());
713 }
714
715 // Now load the specific keys from the dynamic config;
716 auto cit = best_matching_config->second.find(DC_CONFIG_KEY);
717 for(auto vit=cit->second.begin(); vit != cit->second.end(); vit++){
718 // Each value of this vector should be a regular BESKeys kvp. i.e. "BES.LogName=./opendap.log"
719 // Which we just feed into the keys, since we just backed them up...
720 BESDEBUG(MODULE, prolog << "Adding dynamic configuration BES Key: " << *vit << endl);
721 set_key(*vit);
722 }
723 d_dynamic_config_in_use = true;
724
725 BESDEBUG(MODULE, prolog << "END" << endl);
726#endif
727
728 BESDEBUG("bes:keys",dump());
729}
730
737void TheBESKeys::dump(ostream &strm) const
738{
739 strm << dump();
740}
741
747string TheBESKeys::dump() const
748{
749 stringstream ss;
750 ss << BESIndent::LMarg << "BESKeys::dump - (" << (void *) this << ")" << endl;
751 BESIndent::Indent();
752 ss << BESIndent::LMarg << "key file:" << d_keys_file_name << endl;
753
754 if (!d_the_keys.empty()) {
755 ss << BESIndent::LMarg << " keys:" << endl;
756 BESIndent::Indent();
757 for (auto &p: d_the_keys) { // p is the key value pair
758 ss << BESIndent::LMarg << p.first << ": ";
759 for (auto &value: p.second) {
760 ss << value << " ";
761 }
762 ss << endl;
763 }
764 BESIndent::UnIndent();
765 }
766 else {
767 ss << BESIndent::LMarg << "keys: none" << endl;
768 }
769 BESIndent::UnIndent();
770 return ss.str();
771}
exception thrown if internal error encountered
Regular expression matching.
Definition BESRegex.h:89
int match(const char *s, int len, int pos=0) const
Does the pattern match.
Definition BESRegex.cc:70
static std::string lowercase(const std::string &s)
Definition BESUtil.cc:257
static void trim_if_trailing_slash(std::string &value)
If the string ends in a slash, remove it This function works for empty strings (doing nothing)....
Definition BESUtil.cc:113
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
std::string get_as_config() const
static TheBESKeys * TheKeys()
Access to the singleton.
Definition TheBESKeys.cc:85
void set_key(const std::string &key, const std::string &val, bool addto=false)
allows the user to set key/value pairs from within the application.
static int read_int_key(const std::string &key, int default_value)
Read an integer-valued key from the bes.conf file.
void get_values(const std::string &s, std::vector< std::string > &vals, bool &found)
Retrieve the values of a given key, if set.
virtual std::string dump() const
dumps information about this object
static std::string ConfigFile
Definition TheBESKeys.h:117
static bool read_bool_key(const std::string &key, bool default_value)
Read a boolean-valued key from the bes.conf file.
static std::string read_string_key(const std::string &key, const std::string &default_value)
Read a string-valued key from the bes.conf file.
static unsigned long read_ulong_key(const std::string &key, unsigned long default_value)
Read an integer-valued key from the bes.conf file.
void reload_keys()
Reload the keys. Erase the existing keys and reload them from the file. This uses the name of the key...
void set_keys(const std::string &key, const std::vector< std::string > &values, bool addto)
allows the user to set key/value pairs from within the application.
static uint64_t read_uint64_key(const std::string &key, uint64_t default_value)
Read an integer-valued key from the bes.conf file.
void load_dynamic_config(const std::string &name)
Loads the the applicable dynamic configuration or nothing if no configuration is applicable.