PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/mordor/config.h

http://github.com/mozy/mordor
C Header | 390 lines | 210 code | 49 blank | 131 comment | 9 complexity | a17ccaf1c5d989354b3a2347ffdf1793 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #ifndef __CONFIG_H__
  2. #define __CONFIG_H__
  3. // Copyright (c) 2009 - Mozy, Inc.
  4. #include "predef.h"
  5. #include <set>
  6. #include <sstream>
  7. #include <string>
  8. #include <vector>
  9. #include <boost/function.hpp>
  10. #include <boost/lexical_cast.hpp>
  11. #include <boost/multi_index_container.hpp>
  12. #include <boost/multi_index/ordered_index.hpp>
  13. #include <boost/multi_index/global_fun.hpp>
  14. #include <boost/noncopyable.hpp>
  15. #include <boost/shared_ptr.hpp>
  16. #include <boost/signals2/signal.hpp>
  17. #include "assert.h"
  18. namespace Mordor {
  19. #ifdef WINDOWS
  20. class IOManager;
  21. #endif
  22. namespace JSON {
  23. class Value;
  24. }
  25. /*
  26. Configuration Variables (ConfigVars) are a mechanism for configuring the
  27. behavior of Mordor at runtime (e.g. without requiring recompilation).
  28. It is a generic and useful mechanim, and software that uses Mordor can
  29. define and use its own set of ConfigVars.
  30. ConfigVars are stored in a singleton key-value table.
  31. Typical usages of Configuration Variables include:
  32. -Variables to adjust the level of logging (e.g. "log.debugmask")
  33. -Variables to control the frequency of periodic timers
  34. -Variables to enable experimental features or debugging tools
  35. The key name of a ConfigVar can only contain lower case letters and digits
  36. and the "." separator. When defined using an environmental variable
  37. upper case characters are converted to lower case, and the "_" character
  38. can be used in place of ".".
  39. The value of a ConfigVar has a specific type, e.g. string, integer or
  40. double. To access the specific value it is necessary the use the correctly
  41. templated ConfigVar object. e.g. ConfigVar<std::string> to read a string.
  42. For convenience the toString() and fromString() can be used to
  43. access the value in a general way, for example when iterating through all the
  44. configuration variables.
  45. A ConfigVar can only be defined once (via the templated version of
  46. Config::lookup()), and this typically happens at global scope in a source code
  47. file, with the result assigning to a global variable.
  48. for example:
  49. static ConfigVar<std::string>::ptr g_servername =
  50. Config::lookup<std::string>("myapp.server",
  51. std::string("http://test.com"),
  52. "Main Server");
  53. Outside the source code where the ConfigVar is defined the variables can
  54. be read or written by performing a lookup using the non-templated version of
  55. Config::lookup()
  56. for example:
  57. static ConfigVarBase::ptr servername = Config::lookup("myapp.server");
  58. std::cout << servername.toString();
  59. To access the real value of a ConfigVar you would typically use a cast operation like this:
  60. ConfigVar<bool>::ptr boolVar = boost::dynamic_pointer_cast<ConfigVar<bool> >(Config::lookup('myapp.showui'))
  61. if (configVarPtr) {
  62. bool b = boolVar->val();
  63. ...
  64. }
  65. In this case the type specified must exactly match the type used when the
  66. ConfigVar was defined.
  67. In addition to programmatic access it is possible to override the default
  68. value of a ConfigVar using built in support for reading environmental
  69. variables (Config::loadFromEnvironment()), Windows registry settings
  70. (Config::loadFromRegistry()) etc. These mechanisms are optional and must
  71. be explicitly called from the code that uses Mordor. You could also easily
  72. extend this concept with your own code to read ConfigVars from ini files,
  73. Apple property lists, sql databases etc.
  74. Like any other global variable, ConfigVars should be used with some degree of
  75. constraint and common sense. For example they make sense for things that
  76. are primarily adjusted only during testing, for example to point a client
  77. to a test server rather than a default server or to increase the frequency of
  78. a periodic operation. But they should not be used as a replacement for clean
  79. APIs used during the regular software flow.
  80. */
  81. /// check if the name is a valid ConfigVar name
  82. /// @param name ConfigVar name
  83. /// @param allowDot Whether dot is allowed in the ConfigVar name
  84. /// @note ConfigVar name rule when allowDot value is
  85. /// - true: [a-z][a-z0-9]*(\.[a-z0-9]+)*
  86. /// - false: [a-z][a-z0-9]*
  87. /// @return if @p name is a valid ConfigVar name
  88. bool isValidConfigVarName(const std::string &name, bool allowDot = true);
  89. class ConfigVarBase : public boost::noncopyable
  90. {
  91. public:
  92. typedef boost::shared_ptr<ConfigVarBase> ptr;
  93. public:
  94. ConfigVarBase(const std::string &name, const std::string &description = "",
  95. bool lockable = false)
  96. : m_name(name),
  97. m_description(description),
  98. m_lockable(lockable)
  99. {}
  100. virtual ~ConfigVarBase() {}
  101. std::string name() const { return m_name; }
  102. std::string description() const { return m_description; }
  103. bool isLockable() const { return m_lockable; }
  104. /// onChange should not throw any exceptions
  105. boost::signals2::signal<void ()> onChange;
  106. /// @deprecated (use onChange directly)
  107. void monitor(boost::function<void ()> dg) { onChange.connect(dg); }
  108. virtual std::string toString() const = 0;
  109. /// @return If the new value was accepted
  110. virtual bool fromString(const std::string &str) = 0;
  111. private:
  112. std::string m_name, m_description;
  113. bool m_lockable;
  114. };
  115. template <class T>
  116. bool isConfigNotLocked(const T &);
  117. template <class T>
  118. class ConfigVar : public ConfigVarBase
  119. {
  120. public:
  121. struct BreakOnFailureCombiner
  122. {
  123. typedef bool result_type;
  124. template <typename InputIterator>
  125. bool operator()(InputIterator first, InputIterator last) const
  126. {
  127. try {
  128. for (; first != last; ++first)
  129. if (!*first) return false;
  130. } catch (...) {
  131. return false;
  132. }
  133. return true;
  134. }
  135. };
  136. typedef boost::shared_ptr<ConfigVar> ptr;
  137. typedef boost::signals2::signal<bool (const T&), BreakOnFailureCombiner> before_change_signal_type;
  138. typedef boost::signals2::signal<void (const T&)> on_change_signal_type;
  139. public:
  140. ConfigVar(const std::string &name, const T &defaultValue,
  141. const std::string &description = "", bool lockable = false)
  142. : ConfigVarBase(name, description, lockable),
  143. m_val(defaultValue)
  144. {
  145. // if Config is locked, should reject changes to lockable ConfigVars
  146. if (isLockable())
  147. beforeChange.connect(&isConfigNotLocked<T>);
  148. }
  149. std::string toString() const
  150. {
  151. return boost::lexical_cast<std::string>(m_val);
  152. }
  153. bool fromString(const std::string &str)
  154. {
  155. try {
  156. return val(boost::lexical_cast<T>(str));
  157. } catch (boost::bad_lexical_cast &) {
  158. return false;
  159. }
  160. }
  161. /// beforeChange gives the opportunity to reject the new value;
  162. /// return false or throw an exception to prevent the change
  163. before_change_signal_type beforeChange;
  164. /// onChange should not throw any exceptions
  165. on_change_signal_type onChange;
  166. // TODO: atomicCompareExchange and/or mutex
  167. T val() const { return m_val; }
  168. bool val(const T &v)
  169. {
  170. T oldVal = m_val;
  171. if (oldVal != v) {
  172. if (!beforeChange(v))
  173. return false;
  174. m_val = v;
  175. onChange(v);
  176. ConfigVarBase::onChange();
  177. }
  178. return true;
  179. }
  180. private:
  181. T m_val;
  182. };
  183. class Config
  184. {
  185. private:
  186. static std::string getName(const ConfigVarBase::ptr &var)
  187. {
  188. return var->name();
  189. }
  190. typedef boost::multi_index_container<
  191. ConfigVarBase::ptr,
  192. boost::multi_index::indexed_by<
  193. boost::multi_index::ordered_unique<
  194. boost::multi_index::global_fun<const ConfigVarBase::ptr &,
  195. std::string, &getName> >
  196. >
  197. > ConfigVarSet;
  198. public:
  199. #ifdef WINDOWS
  200. /// Encapsulates monitoring the registry for ConfigVar changes
  201. struct RegistryMonitor
  202. {
  203. friend class Config;
  204. public:
  205. typedef boost::shared_ptr<RegistryMonitor> ptr;
  206. private:
  207. RegistryMonitor(IOManager &iomanager, HKEY hKey,
  208. const std::wstring &subKey);
  209. public:
  210. RegistryMonitor(const RegistryMonitor &copy);
  211. ~RegistryMonitor();
  212. private:
  213. static void onRegistryChange(boost::weak_ptr<RegistryMonitor> self);
  214. private:
  215. IOManager &m_ioManager;
  216. HKEY m_hKey;
  217. HANDLE m_hEvent;
  218. };
  219. #endif
  220. public:
  221. /// Declare a ConfigVar
  222. ///
  223. /// @note A ConfigVar can only be declared once.
  224. /// @throws std::invalid_argument With what() == the name of the ConfigVar
  225. /// if the value is not valid.
  226. template <class T>
  227. static typename ConfigVar<T>::ptr lookup(const std::string &name,
  228. const T &defaultValue, const std::string &description = "",
  229. bool lockable = false)
  230. {
  231. if (!isValidConfigVarName(name))
  232. MORDOR_THROW_EXCEPTION(std::invalid_argument(name));
  233. MORDOR_ASSERT(vars().find(name) == vars().end());
  234. typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, defaultValue,
  235. description, lockable));
  236. vars().insert(v);
  237. return v;
  238. }
  239. //This signature of Lookup is used to perform a lookup for a
  240. //previously declared ConfigVar
  241. static ConfigVarBase::ptr lookup(const std::string &name);
  242. // Use to iterate all the ConfigVars
  243. static void visit(boost::function<void (ConfigVarBase::ptr)> dg);
  244. /// Load ConfigVars from command line arguments
  245. ///
  246. /// argv[0] is skipped (assumed to be the program name), and argc and argv
  247. /// are updated to remove any arguments that were used to set ConfigVars.
  248. /// Arguments can be of the form --configVarName=value or
  249. /// --configVarName value. Any arguments after a -- are ignored.
  250. /// @throws std::invalid_argument With what() == the name of the ConfigVar
  251. /// if the value was not successfully set
  252. static void loadFromCommandLine(int &argc, char *argv[]);
  253. // Update value of ConfigVars based on environmental variables.
  254. // This is done by iterating the environmental looking for any that match
  255. // the format KEY=VALUE for a previously declared ConfigVar.
  256. // The key is automatically converted to lowercase, and "_" can be
  257. // used in place of "."
  258. static void loadFromEnvironment();
  259. // Update value of ConfigVars based on json object.
  260. // If a config var not declared previously,
  261. // we will create a new var to save it.
  262. static void loadFromJSON(const JSON::Value &json);
  263. #ifdef WINDOWS
  264. static void loadFromRegistry(HKEY key, const std::string &subKey);
  265. static void loadFromRegistry(HKEY key, const std::wstring &subKey);
  266. /// @see RegistryMonitor
  267. static RegistryMonitor::ptr monitorRegistry(IOManager &ioManager, HKEY key,
  268. const std::string &subKey);
  269. /// @see RegistryMonitor
  270. static RegistryMonitor::ptr monitorRegistry(IOManager &ioManager, HKEY key,
  271. const std::wstring &subKey);
  272. #endif
  273. /// Set lock flag of the Config
  274. /// @param locked If set to true, it will lock those lockable Configvars from
  275. /// updating their values.
  276. static void lock(bool locked) { s_locked = locked; }
  277. static bool isLocked() { return s_locked; }
  278. private:
  279. static ConfigVarSet &vars()
  280. {
  281. static ConfigVarSet vars;
  282. return vars;
  283. }
  284. static bool s_locked;
  285. };
  286. template <class T>
  287. bool isConfigNotLocked(const T &)
  288. {
  289. return !Config::isLocked();
  290. }
  291. class Timer;
  292. class TimerManager;
  293. class Scheduler;
  294. /// Creates a timer that is controlled by a string ConfigVar
  295. ///
  296. /// The timer is automatically updated to use the current value of the
  297. /// ConfigVar, and the string is parsed with stringToMicroseconds from string.h
  298. boost::shared_ptr<Timer> associateTimerWithConfigVar(
  299. TimerManager &timerManager,
  300. boost::shared_ptr<ConfigVar<std::string> > configVar,
  301. boost::function<void ()> dg);
  302. /// Associate a scheduler with a ConfigVar
  303. ///
  304. /// Allows dynamically changing the number of threads associated with a
  305. /// scheduler. Negative values are taken to mean a multiplier of the number
  306. /// of available processor cores.
  307. void associateSchedulerWithConfigVar(Scheduler &scheduler,
  308. boost::shared_ptr<ConfigVar<int> > configVar);
  309. /// helper class to allow temporarily change the ConfigVar Value
  310. ///
  311. /// When an instance is created, the specified ConfigVar is hijacked to the @c
  312. /// tempValue, after the instance is @c reset() or destroyed, the value is
  313. /// restored.
  314. class HijackConfigVar
  315. {
  316. public:
  317. HijackConfigVar(const std::string &name, const std::string &value);
  318. const std::string& originValue() const { return m_oldValue; }
  319. void reset();
  320. ~HijackConfigVar();
  321. private:
  322. boost::shared_ptr<Mordor::ConfigVarBase> m_var;
  323. std::string m_oldValue;
  324. };
  325. }
  326. #endif