PageRenderTime 82ms CodeModel.GetById 20ms app.highlight 33ms RepoModel.GetById 17ms app.codeStats 8ms

/mordor/config.h

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