odin-detector/odin-data

View on GitHub
cpp/frameProcessor/src/FrameProcessorApp.cpp

Summary

Maintainability
Test Coverage
/*
 * FrameProcessorApp.cpp
 *
 *  Created on: 25 May 2016
 *      Author: Alan Greer
 */

#include <signal.h>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
using namespace std;

#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/xml/domconfigurator.h>
using namespace log4cxx;
using namespace log4cxx::helpers;

#include <boost/foreach.hpp>
#include <boost/program_options.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include <boost/filesystem.hpp>
namespace po = boost::program_options;

#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"
using namespace rapidjson;

#include "logging.h"
#include "FrameProcessorApp.h"
#include "FrameProcessorController.h"
#include "DebugLevelLogger.h"
#include "SegFaultHandler.h"
#include "version.h"
#include "stringparse.h"

using namespace FrameProcessor;

boost::shared_ptr<FrameProcessorController> FrameProcessorApp::controller_;

static bool has_suffix(const std::string &str, const std::string &suffix)
{
  return str.size() >= suffix.size() &&
      str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}

FrameProcessorApp::FrameProcessorApp(void) : config_file_("")
{
  // Retrieve and configure a logger instance
    OdinData::configure_logging_mdc(OdinData::app_path.c_str());
    BasicConfigurator::configure();
    logger_ = Logger::getLogger("FP.App");
}

FrameProcessorApp::~FrameProcessorApp()
{
  controller_.reset();
}

/** Parse command line arguments
 *
 * @return return code:
 *   * -1 if option parsing succeeded and we should continue running the application
 *   *  0 if specific command completed (e.g. --help) and we should exit with success
 *   *  1 if option parsing failed and we should exit with failure
 */
int FrameProcessorApp::parse_arguments(int argc, char** argv)
{
  int rc = -1;
  try
  {
    std::string config_file;

    // Declare a group of options that will allowed only on the command line
    po::options_description generic("Generic options");
    generic.add_options()
        ("help,h",
         "Print this help message")
        ("version,v",
         "Print program version string")
        ;

    po::options_description config("Configuration options");
    config.add_options()
        ("debug-level,d", po::value<unsigned int>()->default_value(debug_level), "Set the debug level")
        ("log-config,l", po::value<string>(), "Set the log4cxx logging configuration file")
        ("io-threads", po::value<unsigned int>()->default_value(OdinData::Defaults::default_io_threads), "Set number of IPC channel IO threads")
        ("ctrl", po::value<std::string>()->default_value("tcp://127.0.0.1:5004"), "Set the control endpoint")
        ("config,c", po::value<std::string>()->default_value(""), "File path of inital JSON config for controller")
    ;

    // Group the variables for parsing at the command line
    po::options_description cmdline_options;
    cmdline_options.add(generic).add(config);

    // Parse the command line options
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, cmdline_options), vm);
    po::notify(vm);

    // If the command-line help option was given, print help and exit
    if (vm.count("help"))
    {
      std::cout << "Usage: frameProcessor [options]" << std::endl << std::endl;
      std::cout << cmdline_options << std::endl;
      return 0;
    }

    // If the command line version option was given, print version and exit
    if (vm.count("version")) {
      std::cout << "frameProcessor version " << ODIN_DATA_VERSION_STR << std::endl;
      return 0;
    }

    if (vm.count("log-config"))
    {
      std::string log_config = vm["log-config"].as<string>();
      if (has_suffix(log_config, ".xml")) {
        log4cxx::xml::DOMConfigurator::configure(log_config);
      } else {
        PropertyConfigurator::configure(log_config);
      }
      LOG4CXX_DEBUG(logger_, "log4cxx config file is set to " << vm["log-config"].as<string>());
    }

    if (vm.count("debug-level"))
    {
      set_debug_level(vm["debug-level"].as<unsigned int>());
      LOG4CXX_DEBUG_LEVEL(1, logger_, "Debug level set to  " << debug_level);
    }

    if (vm.count("io-threads"))
    {
      io_threads_ = vm["io-threads"].as<unsigned int>();
      LOG4CXX_DEBUG_LEVEL(1, logger_, "Setting number of IO threads to " << vm["io-threads"].as<unsigned int>());
    }

    if (vm.count("ctrl"))
    {
      ctrl_channel_endpoint_ = vm["ctrl"].as<std::string>();
      LOG4CXX_DEBUG_LEVEL(1, logger_, "Setting control channel endpoint to " << ctrl_channel_endpoint_);
    }

    if (vm.count("config"))
    {
      config_file_ = vm["config"].as<std::string>();
      LOG4CXX_DEBUG_LEVEL(1, logger_, "Loading JSON configuration file " << config_file_);
    }

  }
  catch (po::unknown_option &e)
  {
    LOG4CXX_ERROR(logger_, "Error parsing command line arguments: " << e.what());
    rc = 1;
  }
  catch (Exception &e)
  {
    LOG4CXX_FATAL(logger_, "Got Log4CXX exception: " << e.what());
    rc = 1;
  }
  catch (exception &e)
  {
    LOG4CXX_ERROR(logger_, "Got exception during command line argument parsing: " << e.what());
    rc = 1;
  }
  catch (...)
  {
    LOG4CXX_FATAL(logger_, "Exception of unknown type!");
    throw;
  }

  return rc;
}

int FrameProcessorApp::run(void)
{

  int rc = 0;

  LOG4CXX_INFO(logger_, "frameProcessor version " << ODIN_DATA_VERSION_STR << " starting up");

    // Instantiate a controller
    controller_ = boost::shared_ptr<FrameProcessorController>(
      new FrameProcessorController(io_threads_)
    );

  try {

    // Configure the control channel for the FrameProcessor
    OdinData::IpcMessage ctrl_endpoint_cfg;
    OdinData::IpcMessage reply;
    ctrl_endpoint_cfg.set_param<std::string>("ctrl_endpoint", ctrl_channel_endpoint_);
    controller_->configure(ctrl_endpoint_cfg, reply);

    if (config_file_ != "") {
      // Attempt to open the file specified and read in the string as a JSON parameter set
      std::ifstream config_file_stream(config_file_.c_str());
      std::string config_text((std::istreambuf_iterator<char>(config_file_stream)), std::istreambuf_iterator<char>());

      // Check for empty JSON and throw an exception.
      if (config_text == "") {
        throw OdinData::OdinDataException("Incorrect or empty JSON configuration file specified");
      }

      // Parse the JSON file
      rapidjson::Document config_json;

      if (config_json.Parse(config_text.c_str()).HasParseError()) {
        std::stringstream msg;
        std::string error_snippet = extract_substr_at_pos(config_text, config_json.GetErrorOffset(), 15);
        msg << "Parsing JSON configuration failed at line "
          << extract_line_no(config_text, config_json.GetErrorOffset()) << ": "
          << rapidjson::GetParseError_En(config_json.GetParseError()) << " " << error_snippet;
        throw OdinData::OdinDataException(msg.str());
      }

      // Check if the top level object is an array
      OdinData::IpcMessage config_msg;
      if (config_json.IsArray()) {
        // Loop over the array submitting the child objects in order
        for (rapidjson::SizeType i = 0; i < config_json.Size(); ++i) {
          OdinData::IpcMessage config_msg(
            config_json[i], OdinData::IpcMessage::MsgTypeCmd, OdinData::IpcMessage::MsgValCmdConfigure
          );
          configure_controller(config_msg);
        }
      } else {
        // Single level JSON object
        OdinData::IpcMessage json_config_msg(
          config_json, OdinData::IpcMessage::MsgTypeCmd, OdinData::IpcMessage::MsgValCmdConfigure
        );
        configure_controller(config_msg);
      }
    }

    controller_->run();

    LOG4CXX_DEBUG_LEVEL(1, logger_, "frameProcessor stopped");

  }
  catch (OdinData::OdinDataException& e)
  {
    LOG4CXX_ERROR(logger_, "frameProcessor run failed: " << e.what());
    rc = 1;
  }
  catch (const std::exception& e) {
    LOG4CXX_ERROR(logger_, "Caught unhandled exception in frameProcessor, application will terminate: " << e.what());
    rc = 1;
  }

  return rc;
}

/**
 * Configure the controller with the given configuration message.
 *
 * Any runtime_error from the controller is caught and reported as an error.
 *
 * @param config_msg the message containing the configuration
 */
void FrameProcessorApp::configure_controller(OdinData::IpcMessage& config_msg)
{
  OdinData::IpcMessage reply;
  try {
    controller_->configure(config_msg, reply);
  } catch (const std::runtime_error& e) {
    LOG4CXX_ERROR(logger_, "Failed to configure controller with message " << config_msg.encode());
  }
}

int main (int argc, char** argv)
{
  int rc = -1;

  // Initialise unexpected fault handling
  OdinData::init_seg_fault_handler();

  // Set the locale and application path for logging
  setlocale(LC_CTYPE, "UTF-8");
  OdinData::app_path = argv[0];

  // Create a FrameProcessorApp instance
  FrameProcessor::FrameProcessorApp app;

  // Parse commnd line arguments
  rc = app.parse_arguments(argc, argv);

  if (rc == -1) {
    // Run the application
    rc = app.run();
  }

  return rc;
}