You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1306 lines
39 KiB
C++
1306 lines
39 KiB
C++
/***************************************************************************
|
|
* Copyright (C) gempa GmbH *
|
|
* All rights reserved. *
|
|
* Contact: gempa GmbH (seiscomp-dev@gempa.de) *
|
|
* *
|
|
* GNU Affero General Public License Usage *
|
|
* This file may be used under the terms of the GNU Affero *
|
|
* Public License version 3.0 as published by the Free Software Foundation *
|
|
* and appearing in the file LICENSE included in the packaging of this *
|
|
* file. Please review the following information to ensure the GNU Affero *
|
|
* Public License version 3.0 requirements will be met: *
|
|
* https://www.gnu.org/licenses/agpl-3.0.html. *
|
|
* *
|
|
* Other Usage *
|
|
* Alternatively, this file may be used in accordance with the terms and *
|
|
* conditions contained in a signed written agreement between you and *
|
|
* gempa GmbH. *
|
|
***************************************************************************/
|
|
|
|
|
|
#ifndef SEISCOMP_SYSTEM_APPLICATION_H
|
|
#define SEISCOMP_SYSTEM_APPLICATION_H
|
|
|
|
#include <boost/shared_ptr.hpp>
|
|
|
|
#include <seiscomp/core/exceptions.h>
|
|
#include <seiscomp/core/interruptible.h>
|
|
#include <seiscomp/config/config.h>
|
|
#include <seiscomp/system/commandline.h>
|
|
#include <seiscomp/system/settings.h>
|
|
#include <seiscomp/system/environment.h>
|
|
#include <seiscomp/system/schema.h>
|
|
|
|
|
|
namespace Seiscomp {
|
|
|
|
namespace Logging {
|
|
class Output;
|
|
}
|
|
|
|
namespace System {
|
|
|
|
|
|
class Application;
|
|
|
|
|
|
namespace Detail {
|
|
|
|
|
|
template <typename T>
|
|
T getConfig(const Application *app, const std::string &symbol, bool asPath);
|
|
|
|
std::string join(const std::string &prefix, const char *relativeName);
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Application class to write commandline applications easily using
|
|
* \brief configuration files and commandline options.
|
|
*
|
|
* The class Application works as follows:
|
|
*
|
|
* \code
|
|
* exec()
|
|
* init()
|
|
* run()
|
|
* done()
|
|
* \endcode
|
|
*
|
|
* All of the above methods are virtual. A derived class can reimplement
|
|
* each method to fit its needs.
|
|
* The Application class does all the administrative work:
|
|
* - Reading from configuration files
|
|
* - Handling commandline parameters
|
|
* - Process forking when creating daemons
|
|
* - Signal handling
|
|
*
|
|
* ### Detailed worflow
|
|
*
|
|
* In the simple call sequence above the most complex implementation is
|
|
* hidden inside \ref init(). It deals with handling the
|
|
* commandline, reading configuration files, setup logging and other various
|
|
* tasks. Many of the steps can be intercepted and re-implemented if necessary.
|
|
* Always good practice is to call the implemented method of the base class
|
|
* first and check the result, e.g. when re-implementing \ref init().
|
|
* @code
|
|
* bool MyApplication::init() {
|
|
* if ( !Application::init() ) {
|
|
* SEISCOMP_ERROR("Error on initializing the application");
|
|
* return false;
|
|
* }
|
|
*
|
|
* // Do custom initializations
|
|
* return true;
|
|
* }
|
|
* @endcode
|
|
*
|
|
* If any of the functions called from within @ref init() returns false then
|
|
* initialization is cancelled and @ref init() returns false.
|
|
*
|
|
* * init() (virtual)
|
|
* * createCommandLineDescription() (virtual)
|
|
* * initConfiguration() (virtual)
|
|
* * printUsage() (virtual)
|
|
* * printVersion() (virtual)
|
|
* * handleCommandLineOptions() (virtual)
|
|
* * validateParameters() (virtual)
|
|
* * handleInitializationError() (virtual)
|
|
* * printConfigVariables() (virtual)
|
|
* * validateSchemaParameters() (virtual)
|
|
* * handlePreFork() (virtual)
|
|
* * forkProcess() (virtual)
|
|
* * run() (virtual)
|
|
* * Is pure virtual and needs to be implemented.
|
|
* * done() (virtual)
|
|
* * Sets internal exist flag and causes isExitRequested() to return
|
|
* false.
|
|
*
|
|
* ### Settings
|
|
*
|
|
* An important part of an application are settings. The term settings refers
|
|
* to a collection of several options. Let's illusttrate it with an example.
|
|
* An application introduces two parameters, param1 and param2 whereas the
|
|
* first one is an integer and the second a string. The imperative approach
|
|
* to read the two parameters is:
|
|
*
|
|
* @code
|
|
* void MyApplication::createCommandLineDescription() {
|
|
* Application::createCommandLineDescription();
|
|
*
|
|
* commandline().addGroup("My params");
|
|
* commandline().addOption("My params", "param1", "Sets param1", &_param1);
|
|
* commandline().addOption("My params", "param2", "Sets param2", &_param2);
|
|
* }
|
|
*
|
|
* bool MyApplication::initConfiguration() {
|
|
* if ( !Application::initConfiguration() )
|
|
* return false;
|
|
*
|
|
* try { _param1 = configGetInt("param1"); }
|
|
* catch ( ... ) {}
|
|
*
|
|
* try { _param2 = configGetString("param2"); }
|
|
* catch ( ... ) {}
|
|
* }
|
|
* @endcode
|
|
*
|
|
* This procedure has to be repeated over and over again for each new
|
|
* parameter. When dealing with arrays of settings then one has to repeat
|
|
* even more boilerplate code.
|
|
*
|
|
* The declarative approach is more efficient and easy to use. An example
|
|
* to illustrate the concept:
|
|
*
|
|
* @code
|
|
* class MyApplication : public Application {
|
|
* public:
|
|
* MyApplication(int argc, char **argv)
|
|
* : Application(argc, argv) {
|
|
* // Bind the below settings structure to the application. Commandline
|
|
* // and configuration files are handled transparently.
|
|
* bindSettings(&_settings);
|
|
* }
|
|
*
|
|
* private:
|
|
* struct Settings : AbstractSettings {
|
|
* int param1;
|
|
* string param2;
|
|
*
|
|
* virtual void accept(SettingsLinker &linker) {
|
|
* linker & cfg(param1, "param1") & cli("My params", "param1", "Sets param1")
|
|
* & cfg(param2, "param2") & cli("My params", "param2", "Sets param2");
|
|
* }
|
|
* } _settings;
|
|
* };
|
|
* @endcode
|
|
*
|
|
* Here a settings structure is declared (which can be nested and also
|
|
* contain arrays of other structures) which is bound to the application.
|
|
* The only requirement for the root settings type is that it inherits from
|
|
* \ref AbstractSettings. All other composite types below must implement
|
|
* an accept method which takes \ref SettingsLinker as the only argument passed
|
|
* via a reference.
|
|
*
|
|
* @code
|
|
* void accept(SettingsLinker &linker);
|
|
* @endcode
|
|
*/
|
|
class SC_SYSTEM_CORE_API Application : public Core::InterruptibleObject {
|
|
// ----------------------------------------------------------------------
|
|
// Public types
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
typedef std::vector<std::string> Arguments;
|
|
|
|
enum Stage {
|
|
COMMANDLINE,
|
|
CONFIGURATION,
|
|
LOGGING,
|
|
PLUGINS,
|
|
ST_QUANTITY
|
|
};
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// X'truction
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
Application(int argc, char** argv);
|
|
~Application();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Operators
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
int operator()();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Public functions
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
//! Returns a list of commandline parameters
|
|
const Arguments &arguments() const;
|
|
|
|
//! Returns the commandline interface to add groups and options
|
|
CommandLine &commandline();
|
|
const CommandLine &commandline() const;
|
|
|
|
//! Returns the local configuration object
|
|
const Config::Config &configuration() const;
|
|
|
|
//! Returns the path of the application (arg[0])
|
|
const char* path() const;
|
|
|
|
/**
|
|
* Returns the name of the application used for section lookup
|
|
* in the configuration repository.
|
|
* @return The name of the application
|
|
*/
|
|
const std::string& name() const;
|
|
|
|
/**
|
|
* Adds a pacakge search path to the pluginregistry. This call
|
|
* is equal to
|
|
* \code
|
|
* Seiscomp::System::PluginRegistry::Instance()->addPackagePath(package);
|
|
* \endcode
|
|
* @param path
|
|
*/
|
|
void addPluginPackagePath(const std::string &package);
|
|
|
|
//! Returns the version string
|
|
const char *frameworkVersion() const;
|
|
|
|
/**
|
|
* Enters the mainloop and waits until exit() is called
|
|
* or a appropriate signal has been fired (e.g. SIGTERM).
|
|
* @return The value that was set with to exit()
|
|
*/
|
|
int exec();
|
|
|
|
/**
|
|
* Exit the application and set the returnCode.
|
|
* @param returnCode The value returned from exec()
|
|
*/
|
|
virtual void exit(int returnCode);
|
|
|
|
/**
|
|
* Exit the application and set the returnCode to 0.
|
|
* This call is equivalent to exit(0).
|
|
*/
|
|
void quit();
|
|
|
|
/**
|
|
* Returns whether exit has been requested or not.
|
|
* This query is important within own run loops to
|
|
* check for an abort criteria.
|
|
*/
|
|
bool isExitRequested() const;
|
|
|
|
//! Prints the program usage. The default implementation
|
|
//! prints the commandline options only
|
|
virtual void printUsage() const;
|
|
|
|
//! Returns the path to the crashhandler
|
|
const std::string& crashHandler() const;
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Initialization configuration functions
|
|
// This functions have to be called before the init() method
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
//! Enables the daemon mode to be selectable via commandline
|
|
void setDaemonEnabled(bool enable);
|
|
|
|
//! Enables/disables logging of context (source file + line number)
|
|
void setLoggingContext(bool);
|
|
|
|
//! Enables/disables logging of component
|
|
void setLoggingComponent(bool);
|
|
|
|
//! Enables/disables logging to stderr
|
|
void setLoggingToStdErr(bool);
|
|
|
|
//! Adds a certain component to the logging output
|
|
void addLoggingComponentSubscription(const std::string&);
|
|
|
|
//! Closes the logging backend
|
|
void closeLogging();
|
|
|
|
//! Returns the applications version. The default implementation
|
|
//! returns nullptr and uses the global framework version instead.
|
|
virtual const char *version();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Static public members
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
//! Returns the pointer to the application's instance.
|
|
static Application *Instance();
|
|
|
|
/**
|
|
* Enabled/disables signal handling.
|
|
* It is enabled by default.
|
|
* NOTE: Call this method BEFORE construction when disabling signal
|
|
* handling.
|
|
* @param termination enables/disables SIGTERM, SIGINT
|
|
* @param crash enables/disables SIGSEGV, SIGABRT
|
|
*/
|
|
static void HandleSignals(bool termination, bool crash);
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Protected functions
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
//! Only reimplement this in derived classes that are supposed to
|
|
//! be inherited again. This is not meant for user code. Use
|
|
//! \ref createCommandLineDescription() instead.
|
|
//! If this method is being implemented, the base implementation
|
|
//! must be called.
|
|
virtual void createBaseCommandLineDescription();
|
|
|
|
//! Reimplement this method to add additional commandline groups
|
|
//! and/or options
|
|
virtual void createCommandLineDescription();
|
|
|
|
//! This method is called right after the commandline has been parsed
|
|
//! and before parameters are validated. This is useful to run
|
|
//! commands such as --version or custom queries.
|
|
//! Return true if an option has been handled and the application
|
|
//! should quit. The default implementation just returns false,
|
|
virtual bool handleCommandLineOptions();
|
|
|
|
//! This method is called just before the process would fork if
|
|
//! daemon mode is requested. Even without daemon mode it will
|
|
//! be called. All plugins have been loaded already and e.g. an
|
|
//! additional commandline check can be performed which would
|
|
//! require a particular plugin to be loaded.
|
|
//! Return false if the application should be terminated. The default
|
|
//! implementation just returns true.
|
|
virtual bool handlePreFork();
|
|
|
|
//! This method can be used to verify custom configuration or
|
|
//! commandline parameters
|
|
virtual bool validateParameters();
|
|
|
|
//! Initialization method.
|
|
virtual bool init();
|
|
|
|
/**
|
|
* Starts the mainloop until exit() or quit() is called.
|
|
* The default implementation waits for messages in blocking mode
|
|
* and calls handleMessage() whenever a new message arrives.
|
|
*/
|
|
virtual bool run() = 0;
|
|
|
|
//! Cleanup method called before exec() returns.
|
|
virtual void done();
|
|
|
|
/**
|
|
* Forks the process.
|
|
* @return The result of forking
|
|
*/
|
|
virtual bool forkProcess();
|
|
|
|
//! Opens the configuration file and reads the state variables
|
|
virtual bool initConfiguration();
|
|
|
|
//! Loads plugins
|
|
virtual bool initPlugins();
|
|
|
|
/**
|
|
* Prints the version information to stdout
|
|
*/
|
|
virtual void printVersion();
|
|
|
|
/**
|
|
* Prints all available configuration variables
|
|
*/
|
|
virtual void printConfigVariables();
|
|
|
|
/**
|
|
* Returns lists of schema modules and plugins to validate in
|
|
* method validateSchemaParameters
|
|
*/
|
|
virtual void schemaValidationNames(std::vector<std::string> &modules,
|
|
std::vector<std::string> &plugins) const;
|
|
|
|
/**
|
|
* Validates configuration variables use by application against
|
|
* description xml file
|
|
*/
|
|
virtual bool validateSchemaParameters();
|
|
|
|
//! Handles the interrupt request from outside
|
|
virtual void handleInterrupt(int) override;
|
|
|
|
/**
|
|
* Derived class can implement this method to react on
|
|
* errors while initialization. The default implementation
|
|
* does nothing.
|
|
* @param stage The stage where the error occured
|
|
*/
|
|
virtual bool handleInitializationError(int stage);
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Verbosity handlers
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
//! Callback method to display a message regarding the current
|
|
//! initialization state
|
|
virtual void showMessage(const char*);
|
|
|
|
//! Callback method to display a warning regarding the current
|
|
//! initialization state
|
|
virtual void showWarning(const char*);
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Configuration query functions
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
/**
|
|
* Read a single value from the application's configuration.
|
|
* All configuration query methods throw exceptions when
|
|
* the query could not be resolved or the requested format
|
|
* did not match.
|
|
* This documentation applies to all configGet* functions.
|
|
* @param query The query
|
|
* @return The requested value
|
|
*/
|
|
bool configGetBool(const std::string& query) const;
|
|
int configGetInt(const std::string& query) const;
|
|
double configGetDouble(const std::string& query) const;
|
|
std::string configGetString(const std::string& query) const;
|
|
|
|
/**
|
|
* @brief Method that resolves a string variable and produces a
|
|
* canonicalized absolute pathname.
|
|
* @param query The query
|
|
* @return The path
|
|
*/
|
|
std::string configGetPath(const std::string& query) const;
|
|
|
|
std::vector<bool> configGetBools(const std::string& query) const;
|
|
std::vector<int> configGetInts(const std::string& query) const;
|
|
std::vector<double> configGetDoubles(const std::string& query) const;
|
|
std::vector<std::string> configGetStrings(const std::string& query) const;
|
|
|
|
/**
|
|
* Write a singel value to the local section of the clients
|
|
* configuration file.
|
|
*/
|
|
void configSetBool(const std::string& query, bool v);
|
|
void configSetInt(const std::string& query, int v);
|
|
void configSetDouble(const std::string& query, double v);
|
|
void configSetString(const std::string& query, const std::string &v);
|
|
|
|
void configSetBools(const std::string& query, const std::vector<bool>&);
|
|
void configSetInts(const std::string& query, const std::vector<int>&);
|
|
void configSetDoubles(const std::string& query, const std::vector<double>&);
|
|
void configSetStrings(const std::string& query, const std::vector<std::string>&);
|
|
|
|
void configUnset(const std::string& query);
|
|
|
|
bool saveConfiguration();
|
|
|
|
|
|
template <typename T>
|
|
struct OptionBinding {
|
|
enum Flags {
|
|
NoFlags = 0x00,
|
|
IsKey = 0x01,
|
|
InterpretAsPath = 0x02,
|
|
CLIPrintDefault = 0x04,
|
|
CLIIsSwitch = 0x08
|
|
};
|
|
|
|
OptionBinding(T &value,
|
|
int flags,
|
|
const char *configFileRelativeSymbol,
|
|
const char *cliGroup = nullptr,
|
|
const char *cliAbsoluteSymbol = nullptr,
|
|
const char *cliDesc = nullptr)
|
|
: value(value)
|
|
, flags(flags)
|
|
, configFileRelativeSymbol(configFileRelativeSymbol)
|
|
, cliGroup(cliGroup)
|
|
, cliAbsoluteSymbol(cliAbsoluteSymbol)
|
|
, cliDesc(cliDesc) {}
|
|
|
|
bool isKey() { return flags & IsKey; }
|
|
bool printDefault() { return flags & CLIPrintDefault; }
|
|
bool isSwitch() { return flags & CLIIsSwitch; }
|
|
|
|
T &value;
|
|
int flags;
|
|
const char *configFileRelativeSymbol;
|
|
const char *cliGroup;
|
|
const char *cliAbsoluteSymbol;
|
|
const char *cliDesc;
|
|
};
|
|
|
|
template <typename T>
|
|
struct OptionBinding< std::vector<T> > {
|
|
enum Flags {
|
|
NoFlags = 0x00,
|
|
IsKey = 0x01,
|
|
InterpretAsPath = 0x02,
|
|
CLIPrintDefault = 0x04,
|
|
CLIIsSwitch = 0x08
|
|
};
|
|
|
|
typedef std::function<void(T &instance)> InitFunction;
|
|
|
|
OptionBinding(std::vector<T> &value,
|
|
int flags,
|
|
const char *configFileRelativeSymbol,
|
|
const char *cliGroup = nullptr,
|
|
const char *cliAbsoluteSymbol = nullptr,
|
|
const char *cliDesc = nullptr,
|
|
InitFunction ctor = nullptr)
|
|
: value(value)
|
|
, ctor(ctor)
|
|
, flags(flags)
|
|
, configFileRelativeSymbol(configFileRelativeSymbol)
|
|
, cliGroup(cliGroup)
|
|
, cliAbsoluteSymbol(cliAbsoluteSymbol)
|
|
, cliDesc(cliDesc) {}
|
|
|
|
bool isKey() { return flags & IsKey; }
|
|
bool printDefault() { return flags & CLIPrintDefault; }
|
|
bool isSwitch() { return flags & CLIIsSwitch; }
|
|
|
|
std::vector<T> &value;
|
|
InitFunction ctor;
|
|
int flags;
|
|
const char *configFileRelativeSymbol;
|
|
const char *cliGroup;
|
|
const char *cliAbsoluteSymbol;
|
|
const char *cliDesc;
|
|
};
|
|
|
|
|
|
class OptionLinker {
|
|
public:
|
|
OptionLinker() : _stage(None) {}
|
|
|
|
|
|
public:
|
|
void bind(CommandLine *cli) {
|
|
setStage(BindCli);
|
|
_external.cli = cli;
|
|
}
|
|
|
|
void get(const CommandLine *cli) {
|
|
setStage(GetCli);
|
|
_external.constCli = cli;
|
|
}
|
|
|
|
void get(const Application *app) {
|
|
setStage(GetCfg);
|
|
_external.constApp = app;
|
|
}
|
|
|
|
void dump(std::ostream &os) {
|
|
setStage(Print);
|
|
_external.os = &os;
|
|
}
|
|
|
|
|
|
public:
|
|
// A single non-array option
|
|
template <typename T, typename V>
|
|
void visitSingle(V &visitor, OptionBinding<T> &visitedItem) {
|
|
switch ( _stage ) {
|
|
case None:
|
|
break;
|
|
case BindCli:
|
|
if ( visitedItem.cliAbsoluteSymbol ) {
|
|
if ( visitedItem.cliGroup )
|
|
_external.cli->addGroup(visitedItem.cliGroup);
|
|
CliLinkHelper<T, IsNativelySupported<T>::value>::process(*this, visitedItem);
|
|
}
|
|
break;
|
|
case GetCli:
|
|
if ( !visitedItem.cliAbsoluteSymbol )
|
|
return;
|
|
if ( !CliGetHelper<T, IsNativelySupported<T>::value>::process(*this, visitedItem) )
|
|
visitor.setError(std::string("Invalid commandline value for ") + visitedItem.cliAbsoluteSymbol);
|
|
break;
|
|
case GetCfg:
|
|
if ( !visitedItem.isKey() && !visitedItem.configFileRelativeSymbol )
|
|
return;
|
|
if ( !CfgLinkHelper<T, IsNativelySupported<T>::value>::process(*this, visitedItem, visitor.configPrefix) )
|
|
visitor.setError("Invalid configuration value for " + Detail::join(visitor.configPrefix, visitedItem.configFileRelativeSymbol));
|
|
break;
|
|
case PutCfg:
|
|
break;
|
|
case Print:
|
|
if ( visitedItem.configFileRelativeSymbol )
|
|
*_external.os << Detail::join(visitor.configPrefix, visitedItem.configFileRelativeSymbol);
|
|
else if ( visitedItem.cliAbsoluteSymbol )
|
|
*_external.os << "--" << visitedItem.cliAbsoluteSymbol;
|
|
else if ( visitedItem.isKey() )
|
|
*_external.os << "*KEY*";
|
|
else
|
|
return;
|
|
*_external.os << ": ";
|
|
PrintHelper<T, IsNativelySupported<T>::value>::process(*_external.os, visitedItem.value);
|
|
*_external.os << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// A single array option
|
|
template <typename T, typename V>
|
|
void visitSingle(V &visitor, OptionBinding< std::vector<T> > &visitedItem) {
|
|
switch ( _stage ) {
|
|
case None:
|
|
break;
|
|
case BindCli:
|
|
if ( visitedItem.cliAbsoluteSymbol ) {
|
|
if ( visitedItem.cliGroup )
|
|
_external.cli->addGroup(visitedItem.cliGroup);
|
|
CliLinkHelper<std::vector<T>, IsNativelySupported<T>::value>::process(*this, visitedItem);
|
|
}
|
|
break;
|
|
case GetCli:
|
|
if ( !visitedItem.cliAbsoluteSymbol )
|
|
return;
|
|
if ( !CliGetHelper<std::vector<T>, IsNativelySupported<T>::value>::process(*this, visitedItem) )
|
|
visitor.setError(std::string("Invalid commandline value for ") + visitedItem.cliAbsoluteSymbol);
|
|
break;
|
|
case GetCfg:
|
|
if ( !visitedItem.configFileRelativeSymbol )
|
|
return;
|
|
if ( !CfgLinkHelper<std::vector<T>, IsNativelySupported<T>::value>::process(*this, visitedItem, visitor.configPrefix) )
|
|
visitor.setError("Invalid configuration value for " + Detail::join(visitor.configPrefix, visitedItem.configFileRelativeSymbol));
|
|
break;
|
|
case PutCfg:
|
|
break;
|
|
case Print:
|
|
if ( visitedItem.configFileRelativeSymbol ) {
|
|
if ( *visitedItem.configFileRelativeSymbol ) {
|
|
*_external.os << Detail::join(visitor.configPrefix, visitedItem.configFileRelativeSymbol);
|
|
}
|
|
else {
|
|
*_external.os << visitor.configPrefix;
|
|
}
|
|
}
|
|
else if ( visitedItem.cliAbsoluteSymbol )
|
|
*_external.os << "--" << visitedItem.cliAbsoluteSymbol;
|
|
else if ( visitedItem.isKey() )
|
|
*_external.os << "*KEY*";
|
|
else
|
|
return;
|
|
*_external.os << ": ";
|
|
PrintHelper<std::vector<T>, IsNativelySupported<T>::value>::process(*_external.os, visitedItem.value);
|
|
*_external.os << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// An array option consisting of composites
|
|
template <typename T, typename V>
|
|
void visitMultiple(V &visitor, OptionBinding< std::vector<T> > &visitedItem) {
|
|
switch ( _stage ) {
|
|
case None:
|
|
break;
|
|
case BindCli:
|
|
case GetCli:
|
|
visitor.push(visitedItem);
|
|
for ( size_t i = 0; i < visitedItem.value.size(); ++i )
|
|
visitedItem.value[i].accept(visitor);
|
|
visitor.pop();
|
|
break;
|
|
case GetCfg:
|
|
try {
|
|
std::vector<std::string> items;
|
|
items = Detail::getConfig< std::vector<std::string> >(
|
|
_external.constApp,
|
|
Detail::join(
|
|
visitor.configPrefix,
|
|
visitedItem.configFileRelativeSymbol
|
|
),
|
|
false
|
|
);
|
|
std::string oldKey = _key;
|
|
visitor.push(visitedItem);
|
|
for ( size_t i = 0; i < items.size(); ++i ) {
|
|
T value;
|
|
if ( visitedItem.ctor ) {
|
|
visitedItem.ctor(value);
|
|
}
|
|
|
|
OptionBinding<T> item(value, false, items[i].c_str());
|
|
std::string oldKey = _key;
|
|
visitor.push(item);
|
|
_key = items[i];
|
|
item.value.accept(visitor);
|
|
if ( visitor.success() )
|
|
visitedItem.value.push_back(value);
|
|
visitor.pop();
|
|
_key = oldKey;
|
|
}
|
|
visitor.pop();
|
|
_key = oldKey;
|
|
}
|
|
catch ( ... ) {}
|
|
break;
|
|
case PutCfg:
|
|
break;
|
|
case Print:
|
|
if ( visitedItem.configFileRelativeSymbol ) {
|
|
*_external.os << Detail::join(visitor.configPrefix, visitedItem.configFileRelativeSymbol);
|
|
*_external.os << ": ";
|
|
if ( visitedItem.value.empty() )
|
|
*_external.os << "[]" << std::endl;
|
|
else {
|
|
*_external.os << "[" << visitedItem.value.size() << "]" << std::endl;
|
|
std::string oldKey = _key;
|
|
visitor.push(visitedItem);
|
|
for ( size_t i = 0; i < visitedItem.value.size(); ++i ) {
|
|
*_external.os << "{" << std::endl;
|
|
visitedItem.value[i].accept(visitor);
|
|
*_external.os << "}" << std::endl;
|
|
}
|
|
visitor.pop();
|
|
_key = oldKey;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Helpers
|
|
private:
|
|
template <typename T>
|
|
T key() const {
|
|
return Generic::Detail::MustMatch<std::string,T>::get(_key);
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
struct IsNativelySupported {
|
|
enum {
|
|
value = Generic::Detail::IsClassType<T>::value ?
|
|
(
|
|
boost::is_same<std::string,T>::value ?
|
|
1
|
|
:
|
|
0
|
|
)
|
|
:
|
|
1
|
|
};
|
|
};
|
|
|
|
|
|
template <typename T, int IS_SUPPORTED>
|
|
struct CliLinkHelper {};
|
|
|
|
template <typename T>
|
|
struct CliLinkHelper<T,0> {
|
|
template <typename P>
|
|
static void process(P &proc, OptionBinding<T> &visitedItem) {
|
|
using namespace Seiscomp::Core;
|
|
proc._proxyValueStore[&visitedItem.value] = toString(visitedItem.value);
|
|
proc._external.cli->addOption(
|
|
visitedItem.cliGroup?visitedItem.cliGroup:"Generic",
|
|
visitedItem.cliAbsoluteSymbol,
|
|
visitedItem.cliDesc,
|
|
&proc._proxyValueStore[&visitedItem.value],
|
|
visitedItem.printDefault()
|
|
);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CliLinkHelper<T,1> {
|
|
template <typename P>
|
|
static void process(P &proc, OptionBinding<T> &visitedItem) {
|
|
if ( visitedItem.isSwitch() ) {
|
|
proc._external.cli->addOption(
|
|
visitedItem.cliGroup?visitedItem.cliGroup:"Generic",
|
|
visitedItem.cliAbsoluteSymbol,
|
|
visitedItem.cliDesc
|
|
);
|
|
}
|
|
else {
|
|
proc._external.cli->addOption(
|
|
visitedItem.cliGroup?visitedItem.cliGroup:"Generic",
|
|
visitedItem.cliAbsoluteSymbol,
|
|
visitedItem.cliDesc,
|
|
&visitedItem.value,
|
|
visitedItem.printDefault()
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CliLinkHelper<std::vector<T>,1> {
|
|
template <typename P>
|
|
static void process(P &proc, OptionBinding< std::vector<T> > &visitedItem) {
|
|
proc._external.cli->addOption(
|
|
visitedItem.cliGroup?visitedItem.cliGroup:"Generic",
|
|
visitedItem.cliAbsoluteSymbol,
|
|
visitedItem.cliDesc,
|
|
&visitedItem.value
|
|
);
|
|
}
|
|
};
|
|
|
|
|
|
template <typename T, int IS_SUPPORTED>
|
|
struct CliGetHelper {};
|
|
|
|
template <typename T>
|
|
struct CliGetHelper<T,0> {
|
|
template <typename P>
|
|
static bool process(P &proc, OptionBinding<T> &visitedItem) {
|
|
using namespace Seiscomp::Core;
|
|
try {
|
|
bool hasOption = false;
|
|
const char *s = strchr(visitedItem.cliAbsoluteSymbol, ',');
|
|
if ( s ) {
|
|
size_t len = s - visitedItem.cliAbsoluteSymbol;
|
|
s = visitedItem.cliAbsoluteSymbol;
|
|
Core::trim(s, len);
|
|
hasOption = proc._external.constCli->hasOption(std::string(s, len));
|
|
}
|
|
else
|
|
hasOption = proc._external.constCli->hasOption(visitedItem.cliAbsoluteSymbol);
|
|
|
|
if ( hasOption )
|
|
return fromString(visitedItem.value, proc._proxyValueStore[&visitedItem.value]);
|
|
else
|
|
return true;
|
|
}
|
|
catch ( std::exception & ) {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CliGetHelper<T,1> {
|
|
template <typename P>
|
|
static bool process(P &proc, OptionBinding<T> &visitedItem) {
|
|
if ( visitedItem.cliAbsoluteSymbol && visitedItem.isSwitch() ) {
|
|
const char *s = strchr(visitedItem.cliAbsoluteSymbol, ',');
|
|
if ( s ) {
|
|
size_t len = static_cast<size_t>(s - visitedItem.cliAbsoluteSymbol);
|
|
s = visitedItem.cliAbsoluteSymbol;
|
|
Core::trim(s, len);
|
|
if ( proc._external.constCli->hasOption(std::string(s, len)) ) {
|
|
visitedItem.value = true;
|
|
}
|
|
}
|
|
else if ( proc._external.constCli->hasOption(visitedItem.cliAbsoluteSymbol) ) {
|
|
visitedItem.value = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CliGetHelper<std::vector<T>,1> {
|
|
template <typename P>
|
|
static bool process(P &, OptionBinding< std::vector<T> > &) {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
template <typename T, int IS_SUPPORTED>
|
|
struct CfgLinkHelper {};
|
|
|
|
template <typename T>
|
|
struct CfgLinkHelper<T,0> {
|
|
template <typename P>
|
|
static bool process(P &proc,
|
|
OptionBinding<T> &visitedItem,
|
|
const std::string &prefix) {
|
|
try {
|
|
std::string tmp;
|
|
if ( visitedItem.isKey() )
|
|
tmp = proc.template key<std::string>();
|
|
else
|
|
tmp = Detail::getConfig<std::string>(
|
|
proc._external.constApp,
|
|
Detail::join(prefix, visitedItem.configFileRelativeSymbol),
|
|
visitedItem.flags & OptionBinding<T>::InterpretAsPath
|
|
);
|
|
return fromString(visitedItem.value, tmp);
|
|
}
|
|
catch ( ... ) {}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CfgLinkHelper<std::vector<T>,0> {
|
|
template <typename P>
|
|
static bool process(P &proc,
|
|
OptionBinding< std::vector<T> > &visitedItem,
|
|
const std::string &prefix) {
|
|
try {
|
|
std::vector<std::string> tmp;
|
|
if ( visitedItem.isKey() )
|
|
tmp = proc.template key<std::vector<std::string> >();
|
|
else
|
|
tmp = Detail::getConfig<std::vector<std::string> >(
|
|
proc._external.constApp,
|
|
Detail::join(prefix, visitedItem.configFileRelativeSymbol),
|
|
visitedItem.flags & OptionBinding< std::vector<T> >::InterpretAsPath
|
|
);
|
|
visitedItem.value.resize(tmp.size());
|
|
for ( size_t i = 0; i < tmp.size(); ++i ) {
|
|
if ( !fromString(visitedItem.value[i], tmp[i]) )
|
|
return false;
|
|
}
|
|
}
|
|
catch ( ... ) {}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct CfgLinkHelper<T,1> {
|
|
template <typename P>
|
|
static bool process(P &proc,
|
|
OptionBinding<T> &visitedItem,
|
|
const std::string &prefix) {
|
|
if ( visitedItem.isKey() )
|
|
visitedItem.value = proc.template key<T>();
|
|
else {
|
|
try {
|
|
visitedItem.value = Detail::getConfig<T>(
|
|
proc._external.constApp,
|
|
Detail::join(prefix, visitedItem.configFileRelativeSymbol),
|
|
visitedItem.flags & OptionBinding<T>::InterpretAsPath
|
|
);
|
|
}
|
|
catch ( ... ) {}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T, int IS_SUPPORTED>
|
|
struct PrintHelper {};
|
|
|
|
template <typename T>
|
|
struct PrintHelper<T,0> {
|
|
static void process(std::ostream &os, const T &value) {
|
|
os << toString(value);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct PrintHelper<std::vector<T>,0> {
|
|
static void process(std::ostream &os, const std::vector<T> &value) {
|
|
if ( value.empty() ) {
|
|
os << "[]";
|
|
return;
|
|
}
|
|
|
|
for ( size_t i = 0; i < value.size(); ++i ) {
|
|
if ( i ) os << ", ";
|
|
PrintHelper<T,0>::process(os, value[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct PrintHelper<T,1> {
|
|
static void process(std::ostream &os, const T &value) {
|
|
os << value;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct PrintHelper<std::vector<T>,1> {
|
|
static void process(std::ostream &os, const std::vector<T> &value) {
|
|
if ( value.empty() )
|
|
os << "[]";
|
|
else
|
|
os << Core::toString(value);
|
|
}
|
|
};
|
|
|
|
|
|
private:
|
|
enum Stage {
|
|
None,
|
|
BindCli,
|
|
GetCli,
|
|
GetCfg,
|
|
PutCfg,
|
|
Print
|
|
};
|
|
|
|
void setStage(Stage s) {
|
|
_stage = s;
|
|
}
|
|
|
|
Stage _stage;
|
|
std::string _key; //!< The current array item key value
|
|
std::map<void*, std::string> _proxyValueStore;
|
|
|
|
// Output structures depending on the stage
|
|
union {
|
|
std::ostream *os;
|
|
CommandLine *cli;
|
|
const CommandLine *constCli;
|
|
Application *app;
|
|
const Application *constApp;
|
|
} _external;
|
|
};
|
|
|
|
|
|
typedef Generic::SettingsVisitor<
|
|
OptionBinding, OptionLinker
|
|
> SettingsLinker;
|
|
|
|
|
|
class AbstractSettings {
|
|
public:
|
|
virtual ~AbstractSettings() {}
|
|
|
|
public:
|
|
virtual void accept(SettingsLinker &linker) = 0;
|
|
|
|
template <typename T>
|
|
static OptionBinding<T> key(T &boundValue) {
|
|
return OptionBinding<T>(boundValue, OptionBinding<T>::IsKey, nullptr);
|
|
}
|
|
|
|
template <typename T>
|
|
static OptionBinding<T> cfg(T &boundValue, const char *name) {
|
|
return OptionBinding<T>(boundValue, 0, name);
|
|
}
|
|
|
|
template <typename T>
|
|
static OptionBinding< std::vector<T> > cfg(std::vector<T> &boundValue, const char *name, typename OptionBinding< std::vector<T> >::InitFunction ctor = nullptr) {
|
|
return OptionBinding< std::vector<T> >(boundValue, 0, name, nullptr, nullptr, nullptr, ctor);
|
|
}
|
|
|
|
template <typename T>
|
|
static OptionBinding<T> cfgAsPath(T &boundValue, const char *name) {
|
|
return OptionBinding<T>(boundValue, OptionBinding<T>::InterpretAsPath, name);
|
|
}
|
|
|
|
template <typename T>
|
|
static OptionBinding<T> cli(T &boundValue, const char *group, const char *option,
|
|
const char *desc, bool default_ = false, bool switch_ = false) {
|
|
int flags = 0;
|
|
if ( default_ ) flags |= OptionBinding<T>::CLIPrintDefault;
|
|
if ( switch_ ) flags |= OptionBinding<T>::CLIIsSwitch;
|
|
return OptionBinding<T>(boundValue, flags, nullptr, group, option, desc);
|
|
}
|
|
|
|
template <typename T>
|
|
static OptionBinding<T> cliAsPath(T &boundValue, const char *group, const char *option,
|
|
const char *desc, bool default_ = false, bool switch_ = false) {
|
|
int flags = OptionBinding<T>::InterpretAsPath;
|
|
if ( default_ ) flags |= OptionBinding<T>::CLIPrintDefault;
|
|
if ( switch_ ) flags |= OptionBinding<T>::CLIIsSwitch;
|
|
return OptionBinding<T>(boundValue, flags, nullptr, group, option, desc);
|
|
}
|
|
|
|
static OptionBinding<bool> cliSwitch(bool &boundValue, const char *group, const char *option,
|
|
const char *desc) {
|
|
return OptionBinding<bool>(boundValue, OptionBinding<bool>::CLIIsSwitch, nullptr, group, option, desc);
|
|
}
|
|
};
|
|
|
|
|
|
void bindSettings(AbstractSettings *settings);
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Private functions
|
|
// ----------------------------------------------------------------------
|
|
private:
|
|
std::string argumentStr(const std::string &query) const;
|
|
|
|
bool parseCommandLine();
|
|
int acquireLockfile(const std::string &lockfile);
|
|
|
|
void initCommandLine();
|
|
bool initLogging();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Implementation
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
static Application *_instance;
|
|
|
|
typedef std::vector<std::string> ComponentList;
|
|
|
|
SettingsLinker _settingsLinker;
|
|
|
|
static bool _handleCrash;
|
|
static bool _handleTermination;
|
|
|
|
int _argc;
|
|
char **_argv;
|
|
|
|
std::string _name;
|
|
|
|
Arguments _arguments;
|
|
boost::shared_ptr<CommandLine> _commandline;
|
|
|
|
Logging::Output *_logger;
|
|
|
|
// Initialization configuration
|
|
Config::Config _configuration;
|
|
|
|
int _returnCode;
|
|
bool _exitRequested;
|
|
|
|
std::string _version;
|
|
|
|
std::list<AbstractSettings*> _settings;
|
|
|
|
|
|
struct BaseSettings : AbstractSettings {
|
|
BaseSettings();
|
|
|
|
std::string alternativeConfigFile;
|
|
bool enableDaemon;
|
|
std::string crashHandler;
|
|
std::string lockfile;
|
|
std::string plugins;
|
|
std::string certificateStoreDirectory;
|
|
|
|
struct Logging {
|
|
unsigned int verbosity{2};
|
|
bool quiet{false};
|
|
bool trace{false};
|
|
bool debug{false};
|
|
#ifndef WIN32
|
|
bool syslog{false};
|
|
#endif
|
|
bool context{false};
|
|
int component{-1};
|
|
bool toStdout{false};
|
|
bool UTC{false};
|
|
std::string alternativeLogFile;
|
|
ComponentList components;
|
|
|
|
struct File {
|
|
struct Rotator {
|
|
bool enable{true};
|
|
int timeSpan{60 * 60 * 24}; /* one day*/
|
|
int archiveSize{7}; /* one week archive */
|
|
int maxFileSize{100 * 1024 * 1024}; /* max 100MB per logfile */
|
|
|
|
void accept(SettingsLinker &linker) {
|
|
linker
|
|
& cfg(timeSpan, "timeSpan")
|
|
& cfg(archiveSize, "archiveSize")
|
|
& cfg(maxFileSize, "maxFileSize");
|
|
}
|
|
} rotator;
|
|
|
|
void accept(SettingsLinker &linker) {
|
|
linker
|
|
& cfg(rotator.enable, "rotator")
|
|
& cfg(rotator, "rotator");
|
|
}
|
|
} file;
|
|
|
|
void accept(SettingsLinker &linker) {
|
|
linker
|
|
& cfg(verbosity, "level")
|
|
& cfg(context, "context")
|
|
& cfg(component, "component")
|
|
& cfg(components, "components")
|
|
& cfg(syslog, "syslog")
|
|
& cfg(toStdout, "stderr")
|
|
& cfg(UTC, "utc")
|
|
& cfg(file, "file")
|
|
|
|
& cli(
|
|
quiet,
|
|
"Verbose", "quiet,q",
|
|
"Quiet mode: no logging output"
|
|
)
|
|
& cli(verbosity,
|
|
"Verbose", "verbosity",
|
|
"Verbosity level [0..4]",
|
|
false)
|
|
& cli(context,
|
|
"Verbose", "print-context",
|
|
"Print source file and line number",
|
|
false
|
|
)
|
|
& cli(component,
|
|
"Verbose", "print-component",
|
|
"Print the log component (default: file:1, stdout:0)",
|
|
false
|
|
)
|
|
& cli(
|
|
components,
|
|
"Verbose", "component",
|
|
"Limits the logging to a certain component. "
|
|
"This option can be given more than once"
|
|
)
|
|
& cli(
|
|
toStdout,
|
|
"Verbose", "console",
|
|
"Send log output to stdout"
|
|
)
|
|
& cli(
|
|
debug,
|
|
"Verbose", "debug",
|
|
"debug mode: --verbosity=4 --console=1",
|
|
false, true
|
|
)
|
|
& cli(
|
|
trace,
|
|
"Verbose", "trace",
|
|
"trace mode: --verbosity=4 --console=1 --print-component=1 --print-context=1",
|
|
false, true
|
|
)
|
|
& cli(
|
|
alternativeLogFile,
|
|
"Verbose", "log-file",
|
|
"Use alternative log file"
|
|
)
|
|
& cli(
|
|
UTC,
|
|
"Verbose", "log-utc",
|
|
"Use UTC instead of local timezone"
|
|
)
|
|
#ifndef WIN32
|
|
& cli(
|
|
syslog,
|
|
"Verbose", "syslog,s",
|
|
"Use syslog",
|
|
false, true
|
|
)
|
|
#endif
|
|
;
|
|
}
|
|
} logging;
|
|
|
|
virtual void accept(SettingsLinker &linker);
|
|
} _baseSettings;
|
|
};
|
|
|
|
|
|
inline CommandLine &Application::commandline() {
|
|
return *_commandline;
|
|
}
|
|
|
|
inline const CommandLine &Application::commandline() const {
|
|
return *_commandline;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|