1 /* $Id: ncbi_config.cpp 534857 2017-05-03 12:26:07Z ivanov $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Anatoliy Kuznetsov
27 *
28 * File Description:
29 * Parameters tree implementations
30 *
31 * ===========================================================================
32 */
33
34 #include <ncbi_pch.hpp>
35 #include <corelib/ncbistd.hpp>
36 #include <corelib/ncbi_config.hpp>
37 #include <corelib/ncbidll.hpp>
38 #include <corelib/ncbireg.hpp>
39 #include <corelib/error_codes.hpp>
40
41 #include <algorithm>
42 #include <memory>
43 #include <set>
44
45
46 #define NCBI_USE_ERRCODE_X Corelib_Config
47
48
49 BEGIN_NCBI_SCOPE
50
51
52 static const char* kSubNode = ".SubNode";
53 static const char* kSubSection = ".SubSection";
54 static const char* kNodeName = ".NodeName";
55 static const char* kIncludeSections = ".Include";
56
57
58 static
s_List2Set(const list<string> & src,set<string> * dst)59 void s_List2Set(const list<string>& src, set<string>* dst)
60 {
61 ITERATE(list<string>, it, src) {
62 dst->insert(*it);
63 }
64 }
65
66
s_IsSubNode(const string & str)67 bool s_IsSubNode(const string& str)
68 {
69 if (NStr::CompareNocase(kSubNode, str) == 0) {
70 return true;
71 }
72 if (NStr::CompareNocase(kSubSection, str) == 0) {
73 return true;
74 }
75 return false;
76 }
77
78
79 typedef CConfig::TParamTree TParamTree;
80 typedef CConfig::TParamValue TParamValue;
81 typedef map<TParamTree*, set<string> > TSectionMap;
82
83
s_AddOrReplaceSubNode(TParamTree * node_ptr,const string & element_name,const string & element_value)84 void s_AddOrReplaceSubNode(TParamTree* node_ptr,
85 const string& element_name,
86 const string& element_value)
87 {
88 TParamTree* existing_node = const_cast<TParamTree*>
89 (node_ptr->FindNode(element_name,
90 TParamTree::eImmediateSubNodes));
91 if ( existing_node ) {
92 existing_node->GetValue().value = element_value;
93 }
94 else {
95 node_ptr->AddNode(TParamValue(element_name, element_value));
96 }
97 }
98
99
s_FindSubNode(const string & path,TParamTree * tree_root)100 TParamTree* s_FindSubNode(const string& path,
101 TParamTree* tree_root)
102 {
103 list<string> name_list;
104 list<TParamTree*> node_list;
105
106 NStr::Split(path, "/", name_list,
107 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
108 tree_root->FindNodes(name_list, &node_list);
109 return node_list.empty() ? 0 : *node_list.rbegin();
110 }
111
112
s_ParseSubNodes(const string & sub_nodes,TParamTree * parent_node,TSectionMap & inc_sections,set<string> & rm_sections)113 void s_ParseSubNodes(const string& sub_nodes,
114 TParamTree* parent_node,
115 TSectionMap& inc_sections,
116 set<string>& rm_sections)
117 {
118 list<string> sub_list;
119 NStr::Split(sub_nodes, ",; \t\n\r", sub_list,
120 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
121 set<string> sub_set;
122 s_List2Set(sub_list, &sub_set);
123 ITERATE(set<string>, sub_it, sub_set) {
124 unique_ptr<TParamTree> sub_node(new TParamTree);
125 size_t pos = sub_it->rfind('/');
126 if (pos == string::npos) {
127 sub_node->GetKey() = *sub_it;
128 } else {
129 // extract the last element in the path
130 sub_node->GetKey() = sub_it->substr(pos + 1, sub_it->length());
131 }
132 inc_sections[sub_node.get()].insert(*sub_it);
133 rm_sections.insert(*sub_it);
134 parent_node->AddNode(sub_node.release());
135 }
136 }
137
138
s_IsParentNode(TParamTree * parent,TParamTree * child)139 bool s_IsParentNode(TParamTree* parent, TParamTree* child)
140 {
141 TParamTree* node = child;
142 while ( node ) {
143 if (node == parent) {
144 return true;
145 }
146 node = (TParamTree*)node->GetParent();
147 }
148 return false;
149 }
150
151
s_IncludeNode(TParamTree * parent_node,const TParamTree * inc_node)152 void s_IncludeNode(TParamTree* parent_node,
153 const TParamTree* inc_node)
154 {
155 TParamTree::TNodeList_CI sub_it = inc_node->SubNodeBegin();
156 TParamTree::TNodeList_CI sub_end = inc_node->SubNodeEnd();
157 for ( ; sub_it != sub_end; ++sub_it) {
158 TParamTree* sub_node =
159 parent_node->FindSubNode((*sub_it)->GetKey());
160 if ( sub_node ) {
161 // Update the existing subtree to include all missing nodes
162 s_IncludeNode(sub_node, *sub_it);
163 }
164 else {
165 // Copy the whole subtree
166 parent_node->AddNode(new TParamTree(**sub_it));
167 }
168 }
169 }
170
171
s_ExpandSubNodes(TSectionMap & inc_sections,TParamTree * tree_root,TParamTree * node)172 void s_ExpandSubNodes(TSectionMap& inc_sections,
173 TParamTree* tree_root,
174 TParamTree* node)
175 {
176 TSectionMap::iterator current;
177 if ( node ) {
178 current = inc_sections.find(node);
179 }
180 else {
181 current = inc_sections.begin();
182 node = current->first;
183 }
184 if (current != inc_sections.end()) {
185 // Node has included sections, expand them first.
186 ITERATE(set<string>, inc_it, current->second) {
187 TParamTree* inc_node = s_FindSubNode(*inc_it, tree_root);
188 if ( !inc_node ) {
189 continue;
190 }
191 if ( s_IsParentNode(inc_node, node) ) {
192 _TRACE(Error << "Circular section reference: "
193 << node->GetKey() << "->" << *inc_it);
194 continue; // skip the offending subnode
195 }
196 s_ExpandSubNodes(inc_sections, tree_root, inc_node);
197 s_IncludeNode(node, inc_node);
198 }
199 inc_sections.erase(current);
200 }
201 // In case there are includes on deeper levels expand them too
202 TParamTree::TNodeList_I sub_it = node->SubNodeBegin();
203 TParamTree::TNodeList_I sub_end = node->SubNodeEnd();
204 for ( ; sub_it != sub_end; ++sub_it) {
205 s_ExpandSubNodes(inc_sections, tree_root, *sub_it);
206 }
207 }
208
209
210 struct SNodeNameUpdater
211 {
212 typedef set<TParamTree*> TNodeSet;
213 TNodeSet& rm_node_name;
SNodeNameUpdaterSNodeNameUpdater214 SNodeNameUpdater(TNodeSet& node_set) : rm_node_name(node_set) {}
215
216 ETreeTraverseCode operator()(TParamTree& node,
217 int /* delta_level */);
218 };
219
220
operator ()(TParamTree & node,int)221 ETreeTraverseCode SNodeNameUpdater::operator()(TParamTree& node,
222 int /* delta_level */)
223 {
224 if (NStr::CompareNocase(node.GetKey(), kNodeName) == 0) {
225 TParamTree* parent = node.GetParent();
226 if ( parent && !node.GetValue().value.empty() ) {
227 parent->GetKey() = node.GetValue().value;
228 rm_node_name.insert(&node);
229 }
230 }
231 return eTreeTraverse;
232 }
233
234
ConvertRegToTree(const IRegistry & reg)235 CConfig::TParamTree* CConfig::ConvertRegToTree(const IRegistry& reg)
236 {
237 unique_ptr<TParamTree> tree_root(new TParamTree);
238
239 list<string> sections;
240 reg.EnumerateSections(§ions);
241
242 // find the non-redundant set of top level sections
243
244 set<string> all_section_names;
245 s_List2Set(sections, &all_section_names);
246
247 // Collect included and sub-noded names for each section.
248 TSectionMap inc_sections;
249 // Nodes used in .SubNode must be removed from the tree root.
250 set<string> rm_sections;
251
252 ITERATE(set<string>, name_it, all_section_names) {
253 const string& section_name = *name_it;
254 TParamTree* node_ptr;
255 if (section_name.find('/') == string::npos) {
256 unique_ptr<TParamTree> node(new TParamTree);
257 node->GetKey() = section_name;
258 tree_root->AddNode(node_ptr = node.release());
259 } else {
260 list<string> sub_node_list;
261 NStr::Split(section_name, "/", sub_node_list,
262 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
263 node_ptr = tree_root->FindOrCreateNode(sub_node_list);
264 }
265
266 bool have_explicit_name = false;
267
268 // Create section entries
269 list<string> entries;
270 reg.EnumerateEntries(section_name, &entries);
271
272 ITERATE(list<string>, eit, entries) {
273 const string& element_name = *eit;
274 const string& element_value = reg.Get(section_name, element_name);
275
276 if (NStr::CompareNocase(element_name, kNodeName) == 0) {
277 have_explicit_name = true;
278 }
279 if (NStr::CompareNocase(element_name, kIncludeSections) == 0) {
280 list<string> inc_list;
281 NStr::Split(element_value, ",; \t\n\r", inc_list,
282 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
283 s_List2Set(inc_list, &inc_sections[node_ptr]);
284 continue;
285 }
286 if (s_IsSubNode(element_name)) {
287 s_ParseSubNodes(element_value,
288 node_ptr,
289 inc_sections,
290 rm_sections);
291 continue;
292 }
293
294 s_AddOrReplaceSubNode(node_ptr, element_name, element_value);
295 }
296 // Force node name to prevent overriding it by includes
297 if ( !have_explicit_name ) {
298 node_ptr->AddNode(TParamValue(kNodeName, node_ptr->GetKey()));
299 }
300 }
301 s_ExpandSubNodes(inc_sections, tree_root.get(), tree_root.get());
302
303 // Remove nodes used in .SubNode
304 ITERATE(set<string>, rm_it, rm_sections) {
305 TParamTree* rm_node = s_FindSubNode(*rm_it, tree_root.get());
306 if ( rm_node ) {
307 rm_node->GetParent()->RemoveNode(rm_node);
308 }
309 }
310
311 // Rename nodes as requested and remove .NodeName entries
312 set<TParamTree*> rm_node_names;
313 SNodeNameUpdater name_updater(rm_node_names);
314 TreeDepthFirstTraverse(*tree_root, name_updater);
315 ITERATE(set<TParamTree*>, rm_it, rm_node_names) {
316 (*rm_it)->GetParent()->RemoveNode(*rm_it);
317 }
318
319 /*
320 set<string> all_sections;
321 set<string> sub_sections;
322 set<string> top_sections;
323 set<string> inc_sections;
324
325 s_List2Set(sections, &all_sections);
326
327 {{
328 ITERATE(list<string>, it, sections) {
329 const string& section_name = *it;
330 s_GetSubNodes(reg, section_name, &sub_sections);
331 s_GetIncludes(reg, section_name, &inc_sections);
332 }
333 set<string> non_top;
334 non_top.insert(sub_sections.begin(), sub_sections.end());
335 //non_top.insert(inc_sections.begin(), inc_sections.end());
336 insert_iterator<set<string> > ins(top_sections, top_sections.begin());
337 set_difference(all_sections.begin(), all_sections.end(),
338 non_top.begin(), non_top.end(),
339 ins);
340 }}
341
342 ITERATE(set<string>, sit, top_sections) {
343 const string& section_name = *sit;
344
345 TParamTree* node_ptr;
346 if (section_name.find('/') == string::npos) {
347 unique_ptr<TParamTree> node(new TParamTree);
348 node->GetKey() = section_name;
349 tree_root->AddNode(node_ptr = node.release());
350 } else {
351 list<string> sub_node_list;
352 NStr::Split(section_name, "/", sub_node_list);
353 node_ptr = tree_root->FindOrCreateNode( sub_node_list);
354 }
355
356 // Get section components
357
358 list<string> entries;
359 reg.EnumerateEntries(section_name, &entries);
360
361 // Include other sections before processing any values
362 s_ParamTree_IncludeSections(reg, section_name, node_ptr);
363
364 ITERATE(list<string>, eit, entries) {
365 const string& element_name = *eit;
366 const string& element_value = reg.Get(section_name, element_name);
367
368 if (NStr::CompareNocase(element_name, kIncludeSections) == 0) {
369 continue;
370 }
371 if (NStr::CompareNocase(element_name, kNodeName) == 0) {
372 node_ptr->GetKey() = element_value;
373 continue;
374 }
375 if (s_IsSubNode(element_name)) {
376 s_ParamTree_SplitConvertSubNodes(reg, element_value, node_ptr);
377 continue;
378 }
379
380 s_AddOrReplaceSubNode(node_ptr, element_name, element_value);
381 } // ITERATE eit
382
383 } // ITERATE sit
384 */
385
386 return tree_root.release();
387 }
388
389
CConfig(TParamTree * param_tree,EOwnership own)390 CConfig::CConfig(TParamTree* param_tree, EOwnership own)
391 : m_ParamTree(param_tree, own)
392 {
393 if ( !param_tree ) {
394 m_ParamTree.reset(new TParamTree, eTakeOwnership);
395 }
396 }
397
398
CConfig(const IRegistry & reg)399 CConfig::CConfig(const IRegistry& reg)
400 {
401 m_ParamTree.reset(ConvertRegToTree(reg), eTakeOwnership);
402 _ASSERT(m_ParamTree.get());
403 }
404
405
CConfig(const TParamTree * param_tree)406 CConfig::CConfig(const TParamTree* param_tree)
407 {
408 if ( !param_tree ) {
409 m_ParamTree.reset(new TParamTree, eTakeOwnership);
410 }
411 else {
412 m_ParamTree.reset(const_cast<TParamTree*>(param_tree), eNoOwnership);
413 }
414 }
415
416
~CConfig()417 CConfig::~CConfig()
418 {
419 }
420
421
GetString(const string & driver_name,const string & param_name,EErrAction on_error,const string & default_value,const list<string> * synonyms)422 string CConfig::GetString(const string& driver_name,
423 const string& param_name,
424 EErrAction on_error,
425 const string& default_value,
426 const list<string>* synonyms)
427 {
428 return x_GetString(driver_name, param_name,
429 on_error, default_value, synonyms);
430 }
431
432
GetString(const string & driver_name,const string & param_name,EErrAction on_error,const list<string> * synonyms)433 const string& CConfig::GetString(const string& driver_name,
434 const string& param_name,
435 EErrAction on_error,
436 const list<string>* synonyms)
437 {
438 return x_GetString(driver_name, param_name,
439 on_error, kEmptyStr, synonyms);
440 }
441
442
x_GetString(const string & driver_name,const string & param_name,EErrAction on_error,const string & default_value,const list<string> * synonyms)443 const string& CConfig::x_GetString(const string& driver_name,
444 const string& param_name,
445 EErrAction on_error,
446 const string& default_value,
447 const list<string>* synonyms)
448 {
449 list<const TParamTree*> tns;
450 const TParamTree* tn = m_ParamTree->FindSubNode(param_name);
451
452 if (tn && !tn->GetValue().value.empty())
453 tns.push_back(tn);
454 if (synonyms) {
455 ITERATE(list<string>, it, *synonyms) {
456 tn = m_ParamTree->FindSubNode(*it);
457 if (tn && !tn->GetValue().value.empty())
458 tns.push_back(tn);
459 }
460 }
461 if (tns.empty()) {
462 if (on_error == eErr_NoThrow) {
463 return default_value;
464 }
465 string msg = "Cannot init plugin " + driver_name +
466 ", missing parameter:" + param_name;
467 if (synonyms) {
468 ITERATE(list<string>, it, *synonyms) {
469 if ( it == synonyms->begin() ) msg += " or ";
470 else msg += ", ";
471 msg += *it;
472 }
473 }
474
475 NCBI_THROW(CConfigException, eParameterMissing, msg);
476 }
477 if (tns.size() > 1 ) {
478 string msg = "There are more then 1 synonyms parameters (";
479 ITERATE(list<const TParamTree*>, it, tns) {
480 if (it != tns.begin()) msg += ", ";
481 msg += (*it)->GetKey();
482 }
483 msg += ") defined";
484 if (on_error == eErr_NoThrow) {
485 msg += " for driver " + driver_name + ". Default value is used.";
486 ERR_POST_X_ONCE(1, msg);
487 return default_value;
488 }
489 msg = "Cannot init plugin " + driver_name + ". " + msg;
490 NCBI_THROW(CConfigException, eSynonymDuplicate, msg);
491 }
492 return (*tns.begin())->GetValue().value;
493 }
494
495
GetInt(const string & driver_name,const string & param_name,EErrAction on_error,int default_value,const list<string> * synonyms)496 int CConfig::GetInt(const string& driver_name,
497 const string& param_name,
498 EErrAction on_error,
499 int default_value,
500 const list<string>* synonyms)
501 {
502 const string& param = GetString(driver_name, param_name, on_error, synonyms);
503
504 if (param.empty()) {
505 if (on_error == eErr_Throw) {
506 string msg = "Cannot init " + driver_name +
507 ", empty parameter:" + param_name;
508 NCBI_THROW(CConfigException, eParameterMissing, msg);
509 } else {
510 return default_value;
511 }
512 }
513
514 try {
515 return NStr::StringToInt(param);
516 }
517 catch (CStringException& ex)
518 {
519 if (on_error == eErr_Throw) {
520 string msg = "Cannot init " + driver_name +
521 ", incorrect parameter format:" +
522 param_name + " : " + param +
523 " " + ex.what();
524 NCBI_THROW(CConfigException, eInvalidParameter, msg);
525 } else {
526 string msg = "Configuration error " + driver_name +
527 ", incorrect parameter format:" +
528 param_name + " : " + param +
529 " " + ex.what() + ". Default value is used";
530 ERR_POST_X_ONCE(2, msg);
531 }
532 }
533 return default_value;
534 }
535
GetDataSize(const string & driver_name,const string & param_name,EErrAction on_error,unsigned int default_value,const list<string> * synonyms)536 Uint8 CConfig::GetDataSize(const string& driver_name,
537 const string& param_name,
538 EErrAction on_error,
539 unsigned int default_value,
540 const list<string>* synonyms)
541 {
542 const string& param = GetString(driver_name, param_name, on_error, synonyms);
543
544 if (param.empty()) {
545 if (on_error == eErr_Throw) {
546 string msg = "Cannot init " + driver_name +
547 ", empty parameter:" + param_name;
548 NCBI_THROW(CConfigException, eParameterMissing, msg);
549 } else {
550 return default_value;
551 }
552 }
553
554 try {
555 return NStr::StringToUInt8_DataSize(param);
556 }
557 catch (CStringException& ex)
558 {
559 if (on_error == eErr_Throw) {
560 string msg = "Cannot init " + driver_name +
561 ", incorrect parameter format:" +
562 param_name + " : " + param +
563 " " + ex.what();
564 NCBI_THROW(CConfigException, eInvalidParameter, msg);
565 } else {
566 string msg = "Configuration error " + driver_name +
567 ", incorrect parameter format:" +
568 param_name + " : " + param +
569 " " + ex.what() + ". Default value is used";
570 ERR_POST_X_ONCE(3, msg);
571 }
572 }
573 return default_value;
574 }
575
576
GetBool(const string & driver_name,const string & param_name,EErrAction on_error,bool default_value,const list<string> * synonyms)577 bool CConfig::GetBool(const string& driver_name,
578 const string& param_name,
579 EErrAction on_error,
580 bool default_value,
581 const list<string>* synonyms)
582 {
583 const string& param = GetString(driver_name, param_name, on_error, synonyms);
584
585 if (param.empty()) {
586 if (on_error == eErr_Throw) {
587 string msg = "Cannot init " + driver_name +
588 ", empty parameter:" + param_name;
589 NCBI_THROW(CConfigException, eParameterMissing, msg);
590 } else {
591 return default_value;
592 }
593 }
594
595 try {
596 return NStr::StringToBool(param);
597 }
598 catch (CStringException& ex)
599 {
600 if (on_error == eErr_Throw) {
601 string msg = "Cannot init " + driver_name +
602 ", incorrect parameter format:" +
603 param_name + " : " + param +
604 ". " + ex.what();
605 NCBI_THROW(CConfigException, eInvalidParameter, msg);
606 } else {
607 string msg = "Configuration error " + driver_name +
608 ", incorrect parameter format:" +
609 param_name + " : " + param +
610 " " + ex.what() + ". Default value is used";
611 ERR_POST_X_ONCE(4, msg);
612 }
613 }
614 return default_value;
615 }
616
GetDouble(const string & driver_name,const string & param_name,EErrAction on_error,double default_value,const list<string> * synonyms)617 double CConfig::GetDouble(const string& driver_name,
618 const string& param_name,
619 EErrAction on_error,
620 double default_value,
621 const list<string>* synonyms)
622 {
623 const string& param = GetString(driver_name, param_name, on_error, synonyms);
624
625 if (param.empty()) {
626 if (on_error == eErr_Throw) {
627 string msg = "Cannot init " + driver_name +
628 ", empty parameter:" + param_name;
629 NCBI_THROW(CConfigException, eParameterMissing, msg);
630 } else {
631 return default_value;
632 }
633 }
634
635 try {
636 return NStr::StringToDouble(param, NStr::fDecimalPosixOrLocal);
637 }
638 catch (CStringException& ex)
639 {
640 if (on_error == eErr_Throw) {
641 string msg = "Cannot init " + driver_name +
642 ", incorrect parameter format:" +
643 param_name + " : " + param +
644 " " + ex.what();
645 NCBI_THROW(CConfigException, eInvalidParameter, msg);
646 } else {
647 string msg = "Configuration error " + driver_name +
648 ", incorrect parameter format:" +
649 param_name + " : " + param +
650 " " + ex.what() + ". Default value is used";
651 ERR_POST_X_ONCE(5, msg);
652 }
653 }
654 return default_value;
655 }
656
GetErrCodeString(void) const657 const char* CConfigException::GetErrCodeString(void) const
658 {
659 switch (GetErrCode()) {
660 case eParameterMissing: return "eParameterMissing";
661 case eSynonymDuplicate: return "eSynonymDuplicate";
662 case eInvalidParameter: return "eInvalidParameter";
663 default: return CException::GetErrCodeString();
664 }
665 }
666
667
668 END_NCBI_SCOPE
669