Skip to content
Snippets Groups Projects
Configuration.hpp 9.00 KiB
/*
 * @project      The CERN Tape Archive (CTA)
 * @copyright    Copyright © 2021-2022 CERN
 * @license      This program is free software, distributed under the terms of the GNU General Public
 *               Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". You can
 *               redistribute it and/or modify it under the terms of the GPL Version 3, or (at your
 *               option) any later version.
 *
 *               This program is distributed in the hope that it will be useful, but WITHOUT ANY
 *               WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *               PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *               In applying this licence, CERN does not waive the privileges and immunities
 *               granted to it by virtue of its status as an Intergovernmental Organization or
 *               submit itself to any jurisdiction.
 */

#pragma once

#include <string>
#include <map>

#include "common/exception/Exception.hpp"
#include "common/log/LogContext.hpp"
#include "common/log/Logger.hpp"
#include "common/utils/utils.hpp"

namespace cta { namespace common {

    /**
     * represents a category from the CTA configuration file
     */
    typedef std::map<std::string, std::string> ConfCategory;

    /**
     * a class representing the configuration of castor.
     * This configurations is obtained from the local file given in the
     * constructor and will be updated regularly. The time between two
     * updates is taken from the Config/ExpirationDelay entry of the
     * configuration itself and defaults to 5mn if no such entry is found
     */
    class Configuration {

    public:

      /**
       * Private exceptions for this
       */
      CTA_GENERATE_EXCEPTION_CLASS(InvalidConfigEntry);
      CTA_GENERATE_EXCEPTION_CLASS(NoEntry);

    public:

      /**
       * constructor
       * @param fileName the file that should be used to build the configuration
       */
      explicit Configuration(const std::string& fileName);

      /**
       * copy constructor
       * @param other instance of CastorConfiguration class
       */
      Configuration(const Configuration & other);

      /**
       * destructor
       */
      virtual ~Configuration();
      /**
       * assignment operator
       * @param other instance of CastorConfiguration class
       */
      Configuration & operator=(const Configuration & other);

      /**
       * Retrieves a configuration entry.
       *
       * If this method is passed a logger object then it will log the value
       * of the configuration entry together with an indication of whether the
       * value was found in the castor configuration file or whether the
       * specified default value was used instead.
       *
       * @param category the category of the entry
       * @param key the key of the entry
       * @param defaultValue the value to be returned if the configuration entry
       * is not in the configuration file
       * @param log pointer to nullptr or an optional logger object
       */
      const std::string& getConfEntString(const std::string &category,
        const std::string &key, const std::string &defaultValue,
        cta::log::Logger *const log = nullptr);

      /**
       * Retrieves a configuration entry.
       *
       * Besides other possible exceptions, this method throws a
       * cta::exception::NoEntry exception if the specified configuration
       * entry is not in the configuration file.
       *
       * If this method is passed a logger object then this method will log the
       * the value of the configuration entry.
       *
       * @param category the category of the entry
       * @param key the key of the entry
       * @param log pointer to nullptr or an optional logger object
       */
      const std::string& getConfEntString(const std::string &category,
        const std::string &key, cta::log::Logger *const log = nullptr);

      /**
       * Retrieves a configuration entry as an integer.
       *
       * If this method is passed a logger object then it will log the value
       * of the configuration entry together with an indication of whether the
       * value was found in the castor configuration file or whether the
       * specified default value was used instead.
       *
       * @param category category of the configuration parameter
       * @param name category of the configuration parameter
       * @param defaultValue the value to be returned if the configuration entry
       * is not in the configuration file
       * @param log pointer to nullptr or an optional logger object
       * @return the integer value
       */
      template<typename T> T getConfEntInt(const std::string &category,
        const std::string &key, const T defaultValue,
        cta::log::Logger *const log = nullptr)  {
        std::string strValue;
        try {
          strValue = getConfEntString(category, key);
        } catch(cta::exception::Exception &ex) {
          if(nullptr != log) {
            std::list<cta::log::Param> params = {
              cta::log::Param("category", category),
              cta::log::Param("key", key),
              cta::log::Param("value", defaultValue),
              cta::log::Param("source", "DEFAULT")};
            (*log)(log::INFO, "Configuration entry", params);
          }
          return defaultValue;
        }

        if (!utils::isValidUInt(strValue.c_str())) {
          InvalidConfigEntry ex;
          ex.getMessage() << "Failed to get configuration entry " << category <<
            ":" << key << ": Value is not a valid unsigned integer: value=" <<
            strValue;
          throw ex;
        }

        T value;
        std::stringstream ss;
        ss << strValue;
        ss >> value;

        if(nullptr != log) {
          std::list<cta::log::Param> params = {
            cta::log::Param("category", category),
            cta::log::Param("key", key),
            cta::log::Param("value", value),
            cta::log::Param("source", m_fileName)};
          (*log)(log::INFO, "Configuration entry", params);
        }

        return value;
      }

      /**
       * Retrieves a configuration entry as an integer.
       *
       * Besides other possible exceptions, this method throws a
       * cta::exception::NoEntry exception if the specified configuration
       * entry is not in the configuration file.
       *
       * @param category category of the configuration parameter
       * @param name category of the configuration parameter
       * @param log pointer to nullptr or an optional logger object
       * @return the integer value
       */
      template<typename T> T getConfEntInt(const std::string &category,
        const std::string &key, cta::log::Logger *const log = nullptr)  {
        const std::string strValue = getConfEntString(category, key);

        if (!utils::isValidUInt(strValue.c_str())) {
          InvalidConfigEntry ex;
          ex.getMessage() << "Failed to get configuration entry " << category <<
            ":" << key << ": Value is not a valid unsigned integer: value=" <<
            strValue;
          throw ex;
        }

        T value;
        std::stringstream ss;
        ss << strValue;
        ss >> value;

        if(nullptr != log) {
          std::list<cta::log::Param> params = {
            cta::log::Param("category", category),
            cta::log::Param("key", key),
            cta::log::Param("value", value),
            cta::log::Param("source", m_fileName)};
          (*log)(log::INFO, "Configuration entry", params);
        }

        return value;
      }
    private:

      /**
       * check whether the configuration should be renewed
       */
      bool isStale() ;

      /**
       * tries to renew the configuration.
       * That is : take the write lock to do it, check whether it's needed
       * and do it only if needed before releasing the lock
       */
      void tryToRenewConfig() ;

      /**
       * gets current timeout value (in seconds)
       * this function does not take any lock while reading the
       * configuration. So it should never be called without holding
       * a read or a write lock
       */
      int getTimeoutNolock() ;

      /**
       * renews the configuration
       * this function does not take any lock while renewing the
       * configuration. So it should never be called without holding
       * the write lock
       */
      void renewConfigNolock() ;

    private:

      /**
       * fileName to be used when updating the configuration
       */
      std::string m_fileName;

      /**
       * last time we've updated the configuration
       */
      time_t m_lastUpdateTime;

      /**
       * the dictionnary of configuration items
       * actually a dictionnary of ConfCategories, which are dictionnaries of entries
       */
      std::map<std::string, ConfCategory> m_config;

      /**
       * lock to garantee safe access to the configuration, lastUpdateTime and timeout
       */
      pthread_rwlock_t m_lock;

    };

  } // namespace common
} // namespace castor