Add first version

First version of C++ SDL API.

Refer README file for instructions how to generate API documentation.

Current version will find database via environment variables, thus RIC
DBaaS service must be up and running before starting the application
pod which uses SDL C++ API.

Change-Id: Ia3c95ac856b293827b680a9f6451a229f69f35f9
Signed-off-by: Rolf Badorek <rolf.badorek@nokia.com>
diff --git a/src/configurationreader.cpp b/src/configurationreader.cpp
new file mode 100644
index 0000000..85ab627
--- /dev/null
+++ b/src/configurationreader.cpp
@@ -0,0 +1,323 @@
+/*
+   Copyright (c) 2018-2019 Nokia.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+#include "private/abort.hpp"
+#include "private/configurationreader.hpp"
+#include <boost/property_tree/json_parser.hpp>
+#include <sdl/exception.hpp>
+#include "private/createlogger.hpp"
+#include "private/databaseconfiguration.hpp"
+#include "private/logger.hpp"
+#include "private/namespaceconfigurations.hpp"
+#include "private/namespacevalidator.hpp"
+#include "private/system.hpp"
+
+using namespace shareddatalayer;
+
+namespace
+{
+    template <typename T>
+    T get(const boost::property_tree::ptree& ptree, const std::string& param, const std::string& sourceName)
+    {
+        try
+        {
+            return ptree.get<T>(param);
+        }
+        catch (const boost::property_tree::ptree_bad_path&)
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "missing \"" << param << '\"';
+            throw Exception(os.str());
+        }
+        catch (const boost::property_tree::ptree_bad_data& e)
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "invalid \"" << param << "\": \"" << e.data<boost::property_tree::ptree::data_type>() << '\"';
+            throw Exception(os.str());
+        }
+    }
+
+    void validateAndSetDbType(const std::string& type, DatabaseConfiguration& databaseConfiguration,
+                              const std::string& sourceName)
+    {
+        try
+        {
+            databaseConfiguration.checkAndApplyDbType(type);
+        }
+        catch (const std::exception& e)
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << e.what();
+            throw Exception(os.str());
+        }
+    }
+
+    void validateAndSetDbServerAddress(const std::string& address, DatabaseConfiguration& databaseConfiguration,
+                                       const std::string& sourceName)
+    {
+        try
+        {
+            databaseConfiguration.checkAndApplyServerAddress(address);
+        }
+        catch (const std::exception& e)
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "invalid \"address\": \"" << address << "\" " << e.what();
+            throw Exception(os.str());
+        }
+    }
+
+    void parseDatabaseServerConfiguration(DatabaseConfiguration& databaseConfiguration,
+                                          const boost::property_tree::ptree& ptree,
+                                          const std::string& sourceName)
+    {
+        const auto address(get<std::string>(ptree, "address", sourceName));
+        validateAndSetDbServerAddress(address, databaseConfiguration, sourceName);
+    }
+
+    void parseDatabaseServersConfiguration(DatabaseConfiguration& databaseConfiguration,
+                                           const boost::property_tree::ptree& ptree,
+                                           const std::string& sourceName)
+    {
+        const auto servers(ptree.get_child_optional("servers"));
+        if (servers)
+            for(const auto& server : *servers)
+                parseDatabaseServerConfiguration(databaseConfiguration, server.second, sourceName);
+        else
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "missing \"servers\"";
+            throw Exception(os.str());
+        }
+    }
+
+    void parseDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration,
+                                    const boost::property_tree::ptree& ptree,
+                                    const std::string& sourceName)
+    {
+        const auto type(get<std::string>(ptree, "type", sourceName));
+        validateAndSetDbType(type, databaseConfiguration, sourceName);
+
+        parseDatabaseServersConfiguration(databaseConfiguration, ptree, sourceName);
+    }
+
+    void parseDatabaseConfigurationTree(DatabaseConfiguration& databaseConfiguration,
+                                        const boost::optional<boost::property_tree::ptree>& databaseConfigurationPtree,
+                                        const std::string& sourceName)
+    {
+        if (databaseConfigurationPtree)
+            parseDatabaseConfiguration(databaseConfiguration, *databaseConfigurationPtree, sourceName);
+    }
+
+    void parseDatabaseServersConfigurationFromString(DatabaseConfiguration& databaseConfiguration,
+                                                     const std::string& serverConfiguration,
+                                                     const std::string& sourceName)
+    {
+        size_t base(0);
+        auto done(false);
+        do
+        {
+            auto split = serverConfiguration.find(',', base);
+            done = std::string::npos == split;
+            validateAndSetDbServerAddress(serverConfiguration.substr(base, done ? std::string::npos : split-base),
+                                          databaseConfiguration,
+                                          sourceName);
+            base = split+1;
+        } while (!done);
+    }
+
+    void validateNamespacePrefix(const std::string& prefix,
+                                 const std::string& sourceName)
+    {
+        if (!isValidNamespaceSyntax(prefix))
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "\"namespacePrefix\": \"" << prefix << "\""
+               << " contains some of these disallowed characters: "
+               << getDisallowedCharactersInNamespace();
+            throw Exception(os.str());
+        }
+    }
+
+    void validateEnableNotifications(bool enableNotifications, bool useDbBackend,
+                                     const std::string& sourceName)
+    {
+        if (enableNotifications && !useDbBackend)
+        {
+            std::ostringstream os;
+            os << "Configuration error in " << sourceName << ": "
+               << "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
+            throw Exception(os.str());
+        }
+    }
+
+    void parseNsConfiguration(NamespaceConfigurations& namespaceConfigurations,
+                              const std::string& namespacePrefix,
+                              const boost::property_tree::ptree& ptree,
+                              const std::string& sourceName)
+    {
+        const auto useDbBackend(get<bool>(ptree, "useDbBackend", sourceName));
+        const auto enableNotifications(get<bool>(ptree, "enableNotifications", sourceName));
+
+        validateNamespacePrefix(namespacePrefix, sourceName);
+        validateEnableNotifications(enableNotifications, useDbBackend, sourceName);
+
+        namespaceConfigurations.addNamespaceConfiguration({namespacePrefix, useDbBackend, enableNotifications, sourceName});
+    }
+
+    void parseNsConfigurationMap(NamespaceConfigurations& namespaceConfigurations,
+                                 std::unordered_map<std::string, std::pair<boost::property_tree::ptree, std::string>>& namespaceConfigurationMap)
+    {
+        for (const auto &namespaceConfigurationMapItem : namespaceConfigurationMap )
+            parseNsConfiguration(namespaceConfigurations, namespaceConfigurationMapItem.first, namespaceConfigurationMapItem.second.first, namespaceConfigurationMapItem.second.second);
+    }
+}
+
+ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
+    ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
+{
+}
+
+ConfigurationReader::ConfigurationReader(const Directories& directories,
+                                         System& system,
+                                         std::shared_ptr<Logger> logger):
+	dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
+	dbHostEnvVariableValue({}),
+	dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
+	dbPortEnvVariableValue({}),
+    jsonDatabaseConfiguration(boost::none),
+    logger(logger)
+{
+    auto envStr = system.getenv(dbHostEnvVariableName.c_str());
+    if (envStr)
+    {
+        dbHostEnvVariableValue = envStr;
+        sourceForDatabaseConfiguration = dbHostEnvVariableName;
+        auto envStr = system.getenv(dbPortEnvVariableName.c_str());
+        if (envStr)
+            dbPortEnvVariableValue = envStr;
+    }
+
+    readConfigurationFromDirectories(directories);
+}
+
+ConfigurationReader::~ConfigurationReader()
+{
+}
+
+void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
+{
+    for (const auto& i : findConfigurationFiles(directories))
+        readConfiguration(i, i);
+}
+
+void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
+{
+    jsonNamespaceConfigurations.clear();
+    readConfiguration(const_cast<std::istream&>(input), "<istream>");
+}
+
+template<typename T>
+void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
+{
+    boost::property_tree::ptree propertyTree;
+
+    try
+    {
+        boost::property_tree::read_json(input, propertyTree);
+    }
+    catch (const boost::property_tree::json_parser::json_parser_error& e)
+    {
+        std::ostringstream os;
+        os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
+        os << e.message();
+        logger->error() << os.str();
+        throw Exception(os.str());
+    }
+
+    // Environment variable configuration overrides json configuration
+    if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
+    {
+        const auto databaseConfiguration(propertyTree.get_child_optional("database"));
+        if (databaseConfiguration)
+        {
+            jsonDatabaseConfiguration = databaseConfiguration;
+            sourceForDatabaseConfiguration = currentSourceName;
+        }
+    }
+
+    const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
+    if (namespaceConfigurations)
+    {
+        for(const auto& namespaceConfiguration : *namespaceConfigurations)
+        {
+            const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
+            jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
+        }
+    }
+}
+
+void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
+{
+    if (!databaseConfiguration.isEmpty())
+        SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
+
+    try
+    {
+        if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
+        {
+            // Currently hard coded to redis-standalone, because RIC dbaas does not support Redis cluster configuration.
+        	validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
+        	if (dbPortEnvVariableValue.empty())
+        		parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
+        	else
+        		parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
+        }
+        else
+            parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
+    }
+    catch (const std::exception& e)
+    {
+        logger->error() << e.what();
+        throw;
+    }
+}
+
+void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
+{
+    if (!namespaceConfigurations.isEmpty())
+        SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
+
+    try
+    {
+        parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
+    }
+    catch(const std::exception& e)
+    {
+        logger->error() << e.what();
+        throw;
+    }
+}
+
+
+template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
+template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);