virtual bool overridableConfigure(const std::string& fileName, AddressSpace::ASNodeManager *nm);
The custom override is invoked before the object tree is processed
by the Configurator logic. Server specific configuration decoration
logic is added by providing an implementation of ConfigXmlDecoratorFunction
(signature below) and passing that instance to an invocation of
function configure (signature below).typedef std::function<bool (Configuration::Configuration&)> ConfigXmlDecoratorFunction;
bool configure (std::string fileName,
AddressSpace::ASNodeManager *nm, ConfigXmlDecoratorFunction
configXmlDecoratorFunction = ConfigXmlDecoratorFunction()); // 'empty' function by default.
/**
* push_back is a configuration decoration helper function. This function *MUST*
* be used when decorating the configuration object tree (i.e. adding new
* object instances). This function handles both
* 1. Addition: adding the object in the specified tree location.
* 2. Content Ordering: maintaining the ordering mechanism quasar uses to process
* configuration elements in the correct order.
*
* @param parent: Parent tree node.
* @param children: The collection of children (owned by the parent) to which the
* new child will be appended.
* @param child: The new child object
* @param childTypeId: The ordering type ID (as gen'd by xsdcxx) of the new child
* object
*/
template< typename TParent, typename TChildren, typename TChild >
void push_back(TParent& parent, TChildren& children, const TChild& child, const size_t childTypeId)
Server design is copied here to provide the composition
structure.
<?xml version="1.0" encoding="UTF-8"?> <d:design projectShortName="" xmlns:d="http://cern.ch/quasar/Design" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://cern.ch/quasar/Design Design.xsd "> <d:class name="InnerClass"> <d:devicelogic/> <d:configentry dataType="OpcUa_UInt32" name="configInstanceId"/> <d:cachevariable initializeWith="valueAndStatus" name="runtimeInstanceId" ...etc.../> </d:class> <d:class name="AnotherInnerClass"> <d:devicelogic/> <d:configentry dataType="OpcUa_UInt32" name="configInstanceId"/> <d:cachevariable initializeWith="valueAndStatus" name="runtimeInstanceId" ...etc.../> </d:class> <d:class name="ConfiguredClass"> <d:devicelogic/> <d:configentry dataType="OpcUa_UInt32" name="configInstanceId"/> <d:cachevariable initializeWith="valueAndStatus" name="runtimeInstanceId" ...etc.../> <d:hasobjects instantiateUsing="configuration" class="InnerClass"/> <d:hasobjects instantiateUsing="configuration" class="AnotherInnerClass"/> </d:class> <d:root> <d:hasobjects instantiateUsing="configuration" class="ConfiguredClass"/> </d:root> </d:design>
bool QuasarServer::decorateConfiguration(Configuration::Configuration& theConfig) constThe object instatiation above (in blue) are direct calls to xsdcxx generated code: i.e. xsdcxx class constructors. Note that the actual constructors depend on the server design (these classes are generated from the configuration XSD which is generated from Design XML).
{
LOG(Log::INF) <<__FUNCTION__<< " starting server specific configuration decoration";
// Goal: extend given configuration (theConfig) AT RUNTIME (initialisation) as follows
//
// theConfig (contains contents of config.xml)
// +
// |_ConfiguredClass(1000)
// |_InnerClass(1001)
// |_AnotherInnerClass(1002)
// |_InnerClass(1003)
// |_AnotherInnerClass(1004)
// |_ConfiguredClass(2000)
// |_InnerClass(2001)
// |_AnotherInnerClass(2002)
// |_InnerClass(2003)
// |_AnotherInnerClass(2004)
// |_ConfiguredClass(3000)
// |_InnerClass(3001)
// |_AnotherInnerClass(3002)
// |_InnerClass(3003)
// |_AnotherInnerClass(3004)
// Create & populate objects locally, then add to theConfig tree using quasar decoration utility function
for(int i=1000; i<=3000; i+=1000)
{
Configuration::ConfiguredClass parent("parentDevice"+std::to_string(i), i);
for(int j = i+1; j<=i+4; ++j)
{
if(j%2)
{
Configuration::InnerClass child("childDevice"+std::to_string(j), j);
Configuration::DecorationUtils::push_back(parent, parent.InnerClass(), child,
Configuration::ConfiguredClass::InnerClass_id);
}
else
{
Configuration::AnotherInnerClass child("anotherChildDevice"+std::to_string(j), j);
Configuration::DecorationUtils::push_back(parent, parent.AnotherInnerClass(), child,
Configuration::ConfiguredClass::AnotherInnerClass_id);
}
}
Configuration::DecorationUtils::push_back(theConfig, theConfig.ConfiguredClass(), parent,
Configuration::Configuration::ConfiguredClass_id);
}
LOG(Log::INF) <<__FUNCTION__<< " completed server specific configuration decoration";
return true;
}
This section is here for information: There is no action required by server developers. Internally, quasar calls validateContentOrder during initialisation to verify the configuration object tree is valid with respect to the content order mechanism, including any additional objects that were added to the tree during configuration decoration. Below is some deliberately erroneous code and the corresponding error message quasar logs as it exist (due to validateContentOrder failing).
Erroneous code: Does not call required function Configuration::DecorationUtils::push_back
to add an object to the tree during decoration. This breaks the
xsdcxx content order mechanism.
if(j%2)
{
Configuration::InnerClass child("childDevice"+std::to_string(j), j);
parent.InnerClass().push_back(child); // WRONG ! INVALIDATES CONTENT ORDER
}
Code such as this results in quasar terminating on startup - the
server throws an exception with a message.
2020-05-19 17:37.04.106695 [BaseQuasarServer.cpp:156, ERR] Exception caught in BaseQuasarServer::serverRun: [validateContentOrder ERROR parent has [2] child objects unregistered in content order]
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) <<__FUNCTION__<< " server specific override invoked, configuration will be decorated";
ConfigXmlDecoratorFunction decoratorFn = std::bind(&QuasarServer::decorateConfiguration, this, std::placeholders::_1);
return configure(fileName, nm, decoratorFn);
}
else
{
LOG(Log::INF) <<__FUNCTION__<< " server running in regular mode, configuration will be as per config.xml";
return configure(fileName, nm);
}
}
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
(parsing/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. Once in-memory configuration
decoration is complete; it can be written to an XML file by calling
(something like) the following pseudo-code excerpt:
std::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... }
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... }