User Defined Runtime Configuration Modification
Background
Building or Modifying the Configuration on Server Start Up
In order to have access to the runtime in-memory server configuration (as eventually used by quasar servers to build the address space), a developer needs to ‘inject’ their own handler (i.e. implementation) into quasar’s ‘configure’ method described below ````
typedef boost::function ConfigXmlDecoratorFunction;
bool configure (std::string fileName,
AddressSpace::ASNodeManager *nm, ConfigXmlDecoratorFunction
configXmlDecoratorFunction = ConfigXmlDecoratorFunction()); // 'empty' function by default.
class UserSpecificConfigurationHandler
{
...etc...
public:
bool operator()(Configuration::Configuration& theConfiguration);
};
A couple of key points to note here
From the parameter list above, instances of this functor have access to the in-memory runtime configuration object; i.e. the object from which quasar will construct the runtime OPC-UA address space.
This runtime configuration object is an instance of an xsd-cxx generated class (generated from the configuration schema, which is in turn generated from the Design.xml file). The xsd-cxx library supports direct serialization of runtime objects to XML (i.e. persistence).
which is passed to the configure function above by implementation code like ````
...etc...
UserSpecificConfigurationHandler configurationHandlerInstance(...args...);
return configure (fileName, nm, configurationHandlerInstance);
...etc...
The implementation of method
UserSpecificConfigurationHandler::operator()(Configuration::Configuration& theConfiguration)
is up to the developer. Typically, quasar developer implementations of
this functor use the underlying system programming interface (e.g. the
hardware API, in the case where the OPC-UA server provides a middleware
interface to hardware) to query the underlying sytem and build the
configuration object accordingly.
Wiring in the ConfigXmlDecoratorFunction instance
The final step is to ensure that the function configure
above is
called with the correct arguments; namely with the developer’s
implementation of ConfigXmlDecoratorFunction as the 3rd argument. As is
often the case in quasar, injecting user specifc code involves
overriding a virtual function. In this case, the virtual function to
override is: ````
bool BaseQuasarServer::overridableConfigure(const std::string& fileName, AddressSpace::ASNodeManager *nm);
A typical developer override of this function would be along the lines of the following pseudo code ````
bool QuasarServer::overridableConfigure(const std::string& fileName, AddressSpace::ASNodeManager *nm)
{
if([command line switch active for discovery mode])
{
LOG(Log::INF) << "Server running in discovery mode, address space will be built via a process of hardware discovery";
UserSpecificConfigurationHandler configurationHandlerInstance([command line value for where to write persisted configuration XML file]);
return configure (fileName, nm, configurationHandler);
}
else
{
LOG(Log::INF) << "Server running in regular mode, address space will be built from contents of config.xml";
return configure(fileName, nm);
}
}
Persisting the Configuration in an XML file
As described above, type definition ConfigXmlDecoratorFunction
describes a single parameter Configuration::Configuration&
, this
parameter is an instance of an XSD generated C++ class which handles
both in-memory object loading from an XML file (deserialization) and,
the key point here, writing the contents of the in-memory object to an
XML file (serialization). To persist an in-memory configuration then, we
need simply only call serialization methods from xsd-cxx. In the user
specific configuration handler example class described above
UserSpecificConfigurationHandler
, this just means that once the
in-memory configuration is complete; it can be written to an XML file by
calling (something like) the following code excerpt. ````
void UserSpecificConfigurationHandler::writeToFile(const Configuration::Configuration& theConfiguration) const
{
ofstream configurationFile;
try
{
configurationFile.open(some command line specified path for the config serialization, ofstream::out | ofstream::trunc);
}
catch(...all sorts of errors....)
{
...and handle...
}
if(configurationFile.is_open())
{
try
{
xml_schema::namespace_infomap nsMap;
nsMap[""].name = "http://cern.ch/quasar/Configuration";
nsMap[""].schema = "../Configuration/Configuration.xsd";
Configuration::configuration(configurationFile, theConfiguration, nsMap); // actual write executed on this line
}
catch(...all sorts of errors....)
{
...and handle...
}
}