Tuesday, June 16, 2015

Thread Safe Singleton XMLReader with QMutex and boost property tree

Singleton design pattern is used widely when only one instance of an object needs to be created in the application domain. In order to achieve secure creation of singleton instance in multithreaded environments, locking mechanism must be taken into consideration to provide thread safety.

XML configuration settings file access can be handled by creating a thread-safe singleton XMLParser class. Boost library has got a Property Tree library, boost::property_tree , which populates a tree data structure that is representing the existing XML file content.

boost::property_tree makes it easy to load an existing XML file into application. In order to provide thread safety for singleton XMLParser , QT application development framework supplied QMutex class can be used.

Following sample project created by qt creator and contains following files :

1-SingletonXMLParser.pro
2-XmlParser.h
3-XmlParser.cpp
4-main.cpp



SingletonXMLParser.pro file contains project configuration :

QT       += core
TARGET    = SingletonXMLParser
CONFIG   += console
CONFIG   -= app_bundle
TEMPLATE  = app
SOURCES  += main.cpp \
            XmlParser.cpp
HEADERS  += XmlParser.h

In this example project, XMLParser.h header file contains the Settings struct which is going to hold data loaded from XML file. Also the name of the XML file is declared in the header file with a #define directive.

Requirements to make XMLParser class a Thread-Safe Singleton is handled by

1- Making constructor private
2- Adding a static XMLParser variable
3- Static Instance creator method
4- QMutex variable for locking


Example ConfSettings.xml file contains following data and placed under an accessible folder :


#ifndef XMLPARSER_H
#define XMLPARSER_H

#define CONF_SETTINGS_FILE "ConfSettings.xml"

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <QMutex>

struct Settings
{
    std::string ipAddress;
    std::string userName;
};

class XMLParser
{
public:
    static XMLParser& instance();
    ~XMLParser();
    Settings getSettings();

private:
    XMLParser();
    Settings configSettings;
    static std::auto_ptr<XMLParser> mInstance;
    static QMutex mMutex;
};

#endif // XMLPARSER_H


XMLParser.cpp file contains implementation details for

1- private constructor
2- static instance creator method
3- static variables


#include "XmlParser.h"

std::auto_ptr<XMLParser> XMLParser::mInstance;
QMutex XMLParser::mMutex(QMutex::Recursive);

XMLParser::XMLParser()
{
    boost::property_tree::ptree config;
    read_xml(CONF_SETTINGS_FILE, config);

    configSettings.ipAddress = config.get<std::string>("ftp.ipAddress");
    configSettings.userName = config.get<std::string>("ftp.userName");
}

XMLParser::~XMLParser()
{
}

XMLParser& XMLParser::instance()
{
    mMutex.lock();
    if (mInstance.get() == 0)
    {
        try
        {
            mInstance.reset(new XMLParser);
        }
        catch (std::bad_alloc&)
        {
            throw;
        }
    }
    mMutex.unlock();
    return *mInstance;
}

Settings XMLParser::getSettings()
{
    return configSettings;
}


In the constructor of the XMLParser class CONF_SETTINGS_FILE is loaded into boost::property_tree::ptree type config variable. Then by applying the get method of boost::property_tree::ptree required elements from the XML document are retrieved into the application.

Static instance() method of XMLParser provides thread safety by calling lock() and unlock() methods of QMutex instance.

Main method in the main.cpp file calls the static instance() method of thread-safe singleton XMLParser class and then loads the config settings into to Settings struct.

#include "XmlParser.h"

int main()
{
    Settings confSettings = XMLParser::instance().getSettings();

    std::cout << confSettings.ipAddress << std::endl;
    std::cout << confSettings.userName << std::endl;

    return 0;
}

Output of the execution of this program shows ipAddress and userName element values from ConfSettings.xml file on the terminal.