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(&sections);
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