Skip to content
Snippets Groups Projects
ConfigService.cpp 18.8 KiB
Newer Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/Support.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/FilterChannel.h"
Roman Tolchenov's avatar
Roman Tolchenov committed
#include "MantidKernel/SignalChannel.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/Exception.h"
#include "Poco/Util/LoggingConfigurator.h"
#include "Poco/Util/SystemConfiguration.h"
#include "Poco/Util/PropertyFileConfiguration.h"
Nick Draper's avatar
Nick Draper committed
#include "Poco/LoggingFactory.h"
Nick Draper's avatar
Nick Draper committed
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
  namespace Kernel
  {
    //-------------------------------
    // Private member functions
    //-------------------------------

    /// Private constructor for singleton class
    ConfigServiceImpl::ConfigServiceImpl() : 
  m_pConf(NULL), m_pSysConfig(NULL),
    g_log(Logger::get("ConfigService")), 
    m_vConfigPaths(), m_mAbsolutePaths(),
    m_strBaseDir(""), m_PropertyString(""),
    m_properties_file_name("Mantid.properties"),
    m_user_properties_file_name("Mantid.user.properties"),
    m_vDataSearchDirs()
  {
    //getting at system details
    m_pSysConfig = new WrappedObject<Poco::Util::SystemConfiguration>;
    m_pConf = 0;

    //Register the FilterChannel with the Poco logging factory
    Poco::LoggingFactory::defaultFactory().registerChannelClass("FilterChannel",new Poco::Instantiator<Poco::FilterChannel, Poco::Channel>);

    //Register the SignalChannel with the Poco logging factory
    Poco::LoggingFactory::defaultFactory().registerChannelClass("SignalChannel",new Poco::Instantiator<Poco::SignalChannel, Poco::Channel>);

    // Define a directory that serves as the 'application directory'. This is where we expect to find the Mantid.properties file
    // It cannot simply be the current directory since the application may be called from a different place, .i.e.
    // on Linux where the bin directory is in the path and the app is run from a different location.
    // We have two scenarios:
    //  1) The framework is compiled into an executable and its directory is then considered as the base or,
    //  2) The framework is in a stand-alone library and is created from within another executing application
    //     e.g. Python or Matlab (only two at the moment). We can only use the current directory here as there
    //     is no programmatic way of determing where the library that contains this class is. 

    // A MANTID environmental variable might solve all of this??

    std::string callingApplication = Poco::Path(getPathToExecutable()).getFileName();
    // the cases used in the names varies on different systems so we do this case insensitive
    std::transform(callingApplication.begin(), callingApplication.end(),
      callingApplication.begin(), tolower);
    if ( callingApplication.find("python") != std::string::npos || callingApplication.find("matlab") != std::string::npos)
      m_strBaseDir = Mantid::Kernel::getDirectoryOfExecutable();
    //Fill the list of possible relative path keys that may require conversion to absolute paths
    m_vConfigPaths.resize(5, "");
    m_vConfigPaths[0] = "plugins.directory";
    m_vConfigPaths[1] = "instrumentDefinition.directory";
    m_vConfigPaths[2] = "pythonscripts.directory";
    m_vConfigPaths[3] = "ManagedWorkspace.FilePath";
    m_vConfigPaths[3] = "defaultsave.directory";

    //attempt to load the default properties file that resides in the directory of the executable
    loadConfig( getBaseDir() + m_properties_file_name);
    //and then append the user properties
    loadConfig( getOutputDir() + m_user_properties_file_name, true);

    g_log.debug() << "ConfigService created." << std::endl;
    g_log.debug() << "Configured base directory of application as " << getBaseDir() << std::endl;
  }

  /// Private copy constructor for singleton class
  ConfigServiceImpl::ConfigServiceImpl(const ConfigServiceImpl&) : g_log(Logger::get("ConfigService"))
  {
  }
  /** Private Destructor
  *  Prevents client from calling 'delete' on the pointer handed out by Instance
  */
  ConfigServiceImpl::~ConfigServiceImpl()
  {
    Kernel::Logger::shutdown();
    delete m_pSysConfig;
    delete m_pConf;                // potential double delete???
  }

  /**
  * Searches the stored list for keys that have been loaded from the config file and may contain
  * relative paths. Any it find are converted to absolute paths and stored separately
  */
  void ConfigServiceImpl::convertRelativeToAbsolute()
  {
    if( m_vConfigPaths.empty() ) return;
    std::string execdir(getBaseDir());
    m_mAbsolutePaths.clear();
    std::vector<std::string>::const_iterator send = m_vConfigPaths.end();
    for( std::vector<std::string>::const_iterator sitr = m_vConfigPaths.begin(); sitr != send; ++sitr )
    {
      if( !m_pConf->hasProperty(*sitr) ) continue;

      std::string value(m_pConf->getString(*sitr));
      try 
        if( Poco::Path(value).isRelative() )
        {
          m_mAbsolutePaths.insert(std::make_pair(*sitr, Poco::Path(execdir).resolve(value).toString()));
        }
        g_log.error() << "Incorrect path syntax detected in properties file for variable \"" << *sitr << "\"" << std::endl;  
        m_mAbsolutePaths.insert(std::make_pair(*sitr, execdir));
  /**
  * Create the store of data search paths from the 'datasearch.directories' key within the Mantid.properties file.
  * The value of the key should be a semi-colon separated list of directories
  */
  void ConfigServiceImpl::defineDataSearchPaths()
  {
    m_vDataSearchDirs.clear();
    std::string paths = getString("datasearch.directories");
    //Nothing to do
    if( paths.empty() ) return;
    int options = Poco::StringTokenizer::TOK_TRIM + Poco::StringTokenizer::TOK_IGNORE_EMPTY;
    Poco::StringTokenizer tokenizer(paths, ";,", options);
    Poco::StringTokenizer::Iterator iend = tokenizer.end();
    m_vDataSearchDirs.reserve(tokenizer.count());
    const std::string execdir = getBaseDir();
    for( Poco::StringTokenizer::Iterator itr = tokenizer.begin(); itr != iend; ++itr )
      std::string entry = *itr;
      // MG 05/10/09: When the Poco::FilePropertyConfiguration object reads its key/value pairs it 
      // treats a backslash as the start of an escape sequence. If the next character does not
      // form a valid sequence then the backslash is removed from the stream. This has the effect
      // of giving malformed paths when using Windows-style directories. E.g C:\Mantid ->C:Mantid
      // and Poco::Path::isRelative throws an exception on this
      bool is_relative(false);
        is_relative = Poco::Path(entry).isRelative();
        g_log.error() << "Malformed path detected in datasearch.directories variable, skipping \"" << entry << "\"\n";
        continue;
      if( is_relative )
      {
        entry = Poco::Path().resolve(entry).toString();
      }

      Poco::Path token = Poco::Path(entry).makeDirectory();
      if( Poco::File(token).exists() )
      {
        m_vDataSearchDirs.push_back(token.toString());
      }
      else
      {
        g_log.information() << "Ignoring search path \"" << token.toString() << "\", path does not exist.\n";
      }
  /**
  * writes a basic placeholder user.properties file to disk
  * any errors are caught and logged, but not propagated
  */
  void ConfigServiceImpl::createUserPropertiesFile() const
  {
    try
    {
      std::fstream filestr ((getOutputDir() + m_user_properties_file_name).c_str(), std::fstream::out);

      filestr << "# This file can be used to override any properties for this installation." << std::endl;
      filestr << "# Any properties found in this file will override any that are found in the Mantid.Properties file" << std::endl;
      filestr << "# As this file will not be replaced with futher installations of Mantid it is a safe place to put " << std::endl;
      filestr << "# properties that suit your particular installation." << std::endl;
      filestr << "" << std::endl;
      filestr << "#for example" << std::endl;
      filestr << "#uncommenting the line below will set the number of algorithms to retain interim results for to be 90" << std::endl;
      filestr << "#overriding any value set in the Mantid.properties file" << std::endl;
      filestr << "#algorithms.retained = 90" << std::endl;

      filestr.close();
    }
    catch (std::runtime_error ex)
      g_log.error()<<"Unable to write out user.properties file to " << getOutputDir() << m_user_properties_file_name
        << " error: " << ex.what() << std::endl;
  /**
  * Provides a default Configuration string to use if the config file cannot be loaded.
  * @returns The string value of default properties
  */
  const std::string ConfigServiceImpl::defaultConfig() const
  {
    std::string propFile = 
      "# logging configuration"
      "# root level message filter (drop to debug for more messages)"
      "logging.loggers.root.level = debug"
      "# splitting the messages to many logging channels"
      "logging.loggers.root.channel.class = SplitterChannel"
      "logging.loggers.root.channel.channel1 = consoleChannel"
      "logging.loggers.root.channel.channel2 = fileFilterChannel"
      "logging.loggers.root.channel.channel3 = signalChannel"
      "# output to the console - primarily for console based apps"
      "logging.channels.consoleChannel.class = ConsoleChannel"
      "logging.channels.consoleChannel.formatter = f1"
      "# specfic filter for the file channel raising the level to warning (drop to debug for debugging)"
      "logging.channels.fileFilterChannel.class= FilterChannel"
      "logging.channels.fileFilterChannel.channel= fileChannel"
      "logging.channels.fileFilterChannel.level= warning"
      "# output to a file (For error capturing and debugging)"
      "logging.channels.fileChannel.class = debug"
      "logging.channels.fileChannel.path = ../logs/mantid.log"
      "logging.channels.fileChannel.formatter.class = PatternFormatter"
      "logging.channels.fileChannel.formatter.pattern = %Y-%m-%d %H:%M:%S,%i [%I] %p %s - %t"
      "logging.formatters.f1.class = PatternFormatter"
      "logging.formatters.f1.pattern = %s-[%p] %t"
      "logging.formatters.f1.times = UTC;"
      "# SignalChannel - Passes messages to the MantidPlot User interface"
      "logging.channels.signalChannel.class = SignalChannel";
    return propFile;
  }


  //-------------------------------
  // Public member functions
  //-------------------------------

  /** Loads the config file provided.
  *  If the file contains logging setup instructions then these will be used to setup the logging framework.
  *
  *  @param filename The filename and optionally path of the file to load
  *  @param append If false (default) then any previous configuration is discarded, otherwise the new keys are added, and repeated keys will override existing ones.
  */
  void ConfigServiceImpl::loadConfig(const std::string& filename, const bool append)
  {
    delete m_pConf;
    if (!append)
    {
      //remove the previous property string
      m_PropertyString = "";
    }
      std::ifstream propFile(filename.c_str(),std::ios::in);
      bool good = propFile.good();

      //slurp in entire file - extremely unlikely delimter used as an alternate to \n
      std::string temp; 
      getline(propFile,temp,'');
      propFile.close();

      // check if we have failed to open the file
      if ((!good) || (temp==""))
        if (filename == getOutputDir() + m_user_properties_file_name)
        {
          //write out a fresh file
          createUserPropertiesFile();
        }
        else
        {
          throw Exception::FileError("Cannot open file",filename);
        }
      //store the property string
      if((append) && (m_PropertyString!=""))
      {
        m_PropertyString = m_PropertyString + "\n" + temp;
    }
    catch (std::exception& e)
    {
      //there was a problem loading the file - it probably is not there
      std::cerr << "Problem loading the configuration file " << filename << " " << e.what() << std::endl;
        // if we have no property values then take the default
        m_PropertyString = defaultConfig();
    //use the cached property string to initialise the POCO property file
    std::istringstream istr(m_PropertyString);
    m_pConf = new WrappedObject<Poco::Util::PropertyFileConfiguration>(istr);
      //Ensure that the logging directory exists
      Poco::Path logpath(getString("logging.channels.fileChannel.path"));
      if( logpath.toString().empty() || getOutputDir() != getBaseDir() )
        std::string logfile = getOutputDir() + "mantid.log";
        logpath.assign(logfile);
        m_pConf->setString("logging.channels.fileChannel.path", logfile);
      //make this path point to the parent directory and create it if it does not exist
      logpath.makeParent();
      if( !logpath.toString().empty() )
      //configure the logging framework
      Poco::Util::LoggingConfigurator configurator;
      configurator.configure(m_pConf);
      std::cerr << "Trouble configuring the logging framework " << e.what()<<std::endl;
    //Ensure that any relative paths given in the configuration file are relative to the current directory
    convertRelativeToAbsolute();
    //Configure search paths that have been loaded from the properties file
    defineDataSearchPaths();
  }


  /** Searches for a string within the currently loaded configuaration values and 
  *  returns the value as a string. If the key is one of those that was a possible relative path
  *  then the local store is searched first.
  *
  *  @param keyName The case sensitive name of the property that you need the value of.
  *  @returns The string value of the property, or an empty string if the key cannot be found
  */
  std::string ConfigServiceImpl::getString(const std::string& keyName)
  {
    std::map<std::string, std::string>::const_iterator mitr = m_mAbsolutePaths.find(keyName);
    if( mitr != m_mAbsolutePaths.end() ) 
      g_log.debug()<<"Unable to find " << keyName << " in the properties file" << std::endl;
      retVal = "";
    return retVal;
  }

  /** Searches for a string within the currently loaded configuaration values and 
  *  attempts to convert the values to the template type supplied.
  *
  *  @param keyName The case sensitive name of the property that you need the value of.
  *  @param out     The value if found
  *  @returns A success flag - 0 on failure, 1 on success
  */
  template<typename T>
  int ConfigServiceImpl::getValue(const std::string& keyName, T& out)
  {
    std::string strValue = getString(keyName);
    int result = StrFunc::convert(strValue,out);
    return result;
  }

  /** Searches for the string within the environment variables and returns the 
  *  value as a string.
  *
  *  @param keyName The name of the environment variable that you need the value of.
  *  @returns The string value of the property
  */
  std::string ConfigServiceImpl::getEnvironment(const std::string& keyName)	
  {
    return m_pSysConfig->getString("system.env." + keyName);
  }

  /** Gets the name of the host operating system
  *
  *  @returns The name pf the OS version
  */
  std::string ConfigServiceImpl::getOSName()
  {
    return m_pSysConfig->getString("system.osName");
  }

  /** Gets the name of the computer running Mantid
  *
  *  @returns The  name of the computer
  */
  std::string ConfigServiceImpl::getOSArchitecture()
  {
    return m_pSysConfig->getString("system.osArchitecture");
  }

  /** Gets the name of the operating system Architecture
  *
  * @returns The operating system architecture
  */
  std::string ConfigServiceImpl::getComputerName()
  {
    return m_pSysConfig->getString("system.nodeName");
  }

  /** Gets the name of the operating system version
  *
  * @returns The operating system version
  */
  std::string ConfigServiceImpl::getOSVersion()
  {
    return m_pSysConfig->getString("system.osVersion");
  }

  /** Gets the absolute path of the current directory containing the dll
  *
  * @returns The absolute path of the current directory containing the dll
  */
  std::string ConfigServiceImpl::getCurrentDir()
  {
    return m_pSysConfig->getString("system.currentDir");
  }

  /** Gets the absolute path of the temp directory 
  *
  * @returns The absolute path of the temp directory 
  */
  std::string ConfigServiceImpl::getTempDir()
  {
    return m_pSysConfig->getString("system.tempDir");
  }

  /**
  * Gets the directory that we consider to be the bse directory. Basically, this is the 
  * executable directory when running normally or the current directory on startup when
  * running through Python on the command line
  * @returns The directory to consider as the base directory, including a trailing slash
  */
  std::string ConfigServiceImpl::getBaseDir() const
  {
    return m_strBaseDir;
  }

  /**
  * Return the directory that Mantid should use for writing files. A trailing slash is appended
  * so that filenames can more easily be concatenated with this
  */
  std::string ConfigServiceImpl::getOutputDir() const
  {
    Poco::Path datadir(m_pSysConfig->getString("system.homeDir"));
    datadir.append(".mantid");
    // Create the directory if it doesn't already exist
    Poco::File(datadir).createDirectory();
    return datadir.toString() + "/";
  const std::vector<std::string>& ConfigServiceImpl::getDataSearchDirs() const
  {
    return m_vDataSearchDirs;
  }
  template DLLExport int ConfigServiceImpl::getValue(const std::string&,double&);
  template DLLExport int ConfigServiceImpl::getValue(const std::string&,std::string&);
  template DLLExport int ConfigServiceImpl::getValue(const std::string&,int&);