odin-detector/odin-data

View on GitHub
cpp/common/include/IpcMessage.h

Summary

Maintainability
Test Coverage
/*!
 * IpcMessage.h - Frame Receiver Inter-Process Communication JSON Message format class
 *
 *  Created on: Jan 26, 2015
 *      Author: Tim Nicholls, STFC Application Engineering Group
 */

#ifndef IPCMESSAGE_H_
#define IPCMESSAGE_H_

#include <iostream>
#include <string>
#include <exception>
#include <algorithm>
#include <map>
#include <sstream>
#include <time.h>

#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/bimap.hpp"

namespace OdinData
{
//! IpcMessageException - custom exception class implementing "what" for error string
class IpcMessageException : public std::exception
{
public:

  //! Create IpcMessageException with no message
  IpcMessageException(void) throw() :
      what_("")
  { };

  //! Creates IpcMessageExcetpion with informational message
  IpcMessageException(const std::string what) throw() :
      what_(what)
  {};

  //! Returns the content of the informational message
  virtual const char* what(void) const throw()
  {
    return what_.c_str();
  };

  //! Destructor
  ~IpcMessageException(void) throw() {};

private:

  // Member variables
  const std::string what_;  //!< Informational message about the exception

}; // IpcMessageException

} // namespace OdinData

// Override rapidsjon assertion mechanism before including appropriate headers
#ifdef RAPIDJSON_ASSERT
#undef RAPIDJSON_ASSERT
#endif
#define RAPIDJSON_ASSERT(x) if (!(x)) throw OdinData::IpcMessageException("rapidjson assertion thrown");
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/error/en.h"
#include "rapidjson/pointer.h"

namespace OdinData
{

//! IpcMessage - inter-process communication JSON message format class
class IpcMessage
{

public:

  //! Type attribute of IPC message
  enum MsgType {
    MsgTypeIllegal = -1,  //!< Illegal message
    MsgTypeCmd,           //!< Command
    MsgTypeAck,           //!< Message acknowledgement
    MsgTypeNack,          //!< Message no-ackonowledgement
    MsgTypeNotify,        //!< Notify message
  };

  //! Value attribute of IPC message
  enum MsgVal {
    MsgValIllegal = -1,              //!< Illegal value
    MsgValCmdReset,                  //!< Reset command message
    MsgValCmdStatus,                 //!< Status command message
    MsgValCmdConfigure,              //!< Configure command message
    MsgValCmdRequestConfiguration,   //!< Request configuration command message
    MsgValCmdExecute,                //!< Execute a command message
    MsgValCmdRequestCommands,        //!< Request available commands message
    MsgValCmdRequestVersion,         //!< Request version information message
    MsgValCmdBufferConfigRequest,    //!< Buffer configuration request
    MsgValCmdBufferPrechargeRequest, //!< Buffer precharge request
    MsgValCmdResetStatistics,        //!< Reset statistics command
    MsgValCmdShutdown,               //!< Process shutdown request
    MsgValNotifyIdentity,            //!< Identity notification message
    MsgValNotifyFrameReady,          //!< Frame ready notification message
    MsgValNotifyFrameRelease,        //!< Frame release notification message
    MsgValNotifyBufferConfig,        //!< Buffer configuration notification
    MsgValNotifyBufferPrecharge,     //!< Buffer precharge notification
    MsgValNotifyStatus,              //!< Status notification 
  };

  //! Internal bi-directional mapping of message type from string to enumerated MsgType
  typedef boost::bimap<std::string, MsgType> MsgTypeMap;
  //! Internal bi-directional mapping of message type from string to enumerated MsgType
  typedef MsgTypeMap::value_type MsgTypeMapEntry;
  //! Internal bi-directional mapping of message value from string to enumerated MsgVal
  typedef boost::bimap<std::string, MsgVal> MsgValMap;
  //! Internal bi-directional mapping of message value from string to enumerated MsgVal
  typedef MsgValMap::value_type MsgValMapEntry;

  IpcMessage(MsgType msg_type=MsgTypeIllegal, MsgVal msg_val=MsgValIllegal, bool strict_validation=true);

  IpcMessage(const char* json_msg, bool strict_validation=true);

  IpcMessage(const rapidjson::Value& value,
             MsgType msg_type=MsgTypeIllegal,
             MsgVal msg_val=MsgValIllegal,
             bool strict_validation=true);

  //! Updates parameters from another IPCMessage.
  void update(const IpcMessage& other);

  //! Updates parameters from a rapidJSON object.
  void update(const rapidjson::Value& param_val, std::string param_prefix="");

  //! Gets the value of a named parameter in the message.
  //!
  //! This template method returns the value of the specified parameter stored in the
  //! params block of the message. If the block or parameter is missing, an exception
  //! of type IpcMessageException is thrown.
  //!
  //! \param param_name - string name of the parameter to return
  //! \return The value of the parameter if present, otherwise an exception is thrown

  template<typename T> T get_param(std::string const& param_name) const
  {
    // Locate the params block and throw exception if absent
    rapidjson::Value::ConstMemberIterator itr = doc_.FindMember("params");
    if (itr == doc_.MemberEnd())
    {
      throw IpcMessageException("Missing params block in message");
    }

    if (param_name.find("/") != param_name.npos) {
      std::string nodeName = param_name.substr(0, param_name.find("/"));
      std::string subParam = param_name.substr(param_name.find("/") + 1, param_name.npos);

      OdinData::IpcMessage node(this->get_param<const rapidjson::Value &>(nodeName));
      return node.get_param<T>(subParam);
    }
    else {
      // Locate parameter within block and throw exception if absent, otherwise return value
      rapidjson::Value::ConstMemberIterator param_itr = itr->value.FindMember(param_name.c_str());
      if (param_itr == itr->value.MemberEnd())
      {
        std::stringstream ss;
        throw IpcMessageException(ss.str());
      }
      return get_value<T>(param_itr);
    }

  }

  //! Gets the value of a named parameter in the message.
  //!
  //! This template method returns the value of the specified parameter stored in the
  //! params block of the message. if the block or parameter is missing, the specified
  //! default value is returned in its place.
  //!
  //! \param param_name - string name of the parameter to return
  //! \param default_value - default value to return if parameter not present in message
  //! \return The value of the parameter if present, otherwise the specified default value

  template<typename T> T get_param(std::string const& param_name, T const& default_value)
  {
    T the_value = default_value;

    // Locate the params block and find parameter within in if present
    rapidjson::Value::ConstMemberIterator itr = doc_.FindMember("params");
    if (itr != doc_.MemberEnd())
    {
      rapidjson::Value::ConstMemberIterator param_itr = itr->value.FindMember(param_name.c_str());
      if (param_itr != itr->value.MemberEnd())
      {
        the_value = get_value<T>(param_itr);
      }
    }

    return the_value;
  }

  //! Returns a vector of all parameter names contained in the message.
  //!
  //! \return A vector of all parameter names in this message
  std::vector<std::string> get_param_names() const;

  //! Returns true if the parameter is found within the message
  bool has_param(const std::string& param_name) const;

  //! Sets the value of a named parameter in the message.
  //!
  //! This template method sets the value of a named parameter in the message,
  //! creating that block and/orparameter if necessary.  Complex names can be
  //! supplied to generate complex parameter structures.
  //!
  //! \param param_name - string name of the parameter to set
  //! \param param_value - value of parameter to set

  template<typename T> void set_param(const std::string& param_name, T const& param_value)
  {
    bool found_array = false;
    std::vector<std::string> names;
    // Split the name by / character
    std::stringstream ss(param_name);
    std::string item;
    while (getline(ss, item, '/')) {
      names.push_back(item);
    }
    // Get the top level name
    std::string tl_param_name = names[0];
    names.erase(names.begin());

    // Pointer to the rapidjson value
    rapidjson::Value *next;
    // Check for array notation
    std::string array_end = "[]";
    if (0 == tl_param_name.compare(tl_param_name.length() - array_end.length(), array_end.length(), array_end)){
      found_array = true;
    }
    if (found_array){
      tl_param_name = tl_param_name.substr(0, tl_param_name.size()-2);
    }
    // Does the param exist
    if (!this->has_param(tl_param_name)){
      this->internal_set_param(tl_param_name, rapidjson::Value().SetObject());
    }
    // Get the reference to the parameter
    next = &(doc_["params"][rapidjson::Value().SetObject().SetString(tl_param_name.c_str(), doc_.GetAllocator())]);

    std::vector<std::string>::iterator iter;
    for (iter = names.begin(); iter != names.end(); ++iter){
      if (*iter == names.back()){
        // Check for array notation
        std::string array_end = "[]";
        if (0 == (*iter).compare((*iter).length() - array_end.length(), array_end.length(), array_end)){
          found_array = true;
        }
      }
      std::string index = *iter;
      if (found_array){
        index = index.substr(0, index.size()-2);
      }
      // For each entry check to see if it already exists
      rapidjson::Value::MemberIterator param_itr = next->FindMember(index.c_str());
      if (param_itr == next->MemberEnd()){
        // This one doesn't exist so create it
        rapidjson::Value newval;
        newval.SetObject();
        next->AddMember(rapidjson::Value().SetObject().SetString(index.c_str(), doc_.GetAllocator()), newval, doc_.GetAllocator());
      }
      next = &(*next)[index.c_str()];
    }
    if (!found_array){
      set_value(*next, param_value);
    } else {
      if (!next->IsArray()){
        next->SetArray();
      }
      rapidjson::Value val;
      set_value(val, param_value);
      next->PushBack(val, doc_.GetAllocator());
    }
  }

  //! Sets the nack message type with a reason parameter
  void set_nack(const std::string& reason);

  //! Indicates if message has necessary attributes with legal values
  bool is_valid(void);

  //! Returns type attribute of message
  const MsgType get_msg_type(void) const;

  //! Returns value attribute of message
  const MsgVal get_msg_val(void) const;

  //! Returns id attribute of message
  const unsigned int get_msg_id(void) const;

  //! Returns message timestamp as a string in ISO8601 extended format
  const std::string get_msg_timestamp(void) const;

  //! Returns message timstamp as tm structure
  const struct tm get_msg_datetime(void) const;

  //! Sets the message type attribute
  void set_msg_type(MsgType const msg_type);

  //! Sets the message value attribute
  void set_msg_val(MsgVal const msg_val);

  //! Sets the message id attribute
  void set_msg_id(unsigned int msg_id);

  //! Returns a JSON-encoded string of the message
  const char* encode(void);

  //! Returns a JSON-encoded string of the message parameters at a specified path
  const char* encode_params(const std::string& param_path = std::string());

  //! Copies parameters at a specified into the specified JSON value object
  void copy_params(rapidjson::Value& param_obj, const std::string& param_path = std::string());

  //! Overloaded equality relational operator
  friend bool operator ==(IpcMessage const& lhs_msg, IpcMessage const& rhs_msg);

  //! Overloaded inequality relational operator
  friend bool operator !=(IpcMessage const& lhs_msg, IpcMessage const& rhs_msg);

  //! Overloaded stream insertion operator
  friend std::ostream& operator <<(std::ostream& os, IpcMessage& the_msg);

private:

  //! Sets the value of a named parameter in the message.
  //!
  //! This template method sets the value of a named parameter in the message,
  //! creating that block and/orparameter if necessary.
  //!
  //! \param param_name - string name of the parameter to set
  //! \param param_value - value of parameter to set

  template<typename T> void internal_set_param(std::string const& param_name, T const& param_value)
  {
    rapidjson::Document::AllocatorType& allocator = doc_.GetAllocator();

    // Create the params block if it doesn't exist
    rapidjson::Value::ConstMemberIterator itr = doc_.FindMember("params");
    if (itr == doc_.MemberEnd())
    {
      rapidjson::Value params;
      params.SetObject();
      doc_.AddMember("params", params, allocator);
    }
    rapidjson::Value& params = doc_["params"];

    // Now search through the params block for the parameter, creating it if missing, and set
    // the value appropriately
    rapidjson::Value::MemberIterator param_itr = params.FindMember(param_name.c_str());
    if (param_itr == params.MemberEnd())
    {
      rapidjson::Value param_name_val(param_name.c_str(), allocator);
      rapidjson::Value param_value_val;
      set_value(param_value_val, param_value);
      params.AddMember(param_name_val, param_value_val, allocator);
    }
    else
    {
      set_value(const_cast<rapidjson::Value&>(param_itr->value), param_value);
    }
  }


  //! Gets the value of a message attribute.
  //!
  //! This private template method returns the value of a message attribute. If the
  //! attribute is mssing, an IpcMessageException is thrown
  //!
  //! \param attr_name - name of the attribte to return
  //! \return value of the attribute if present, otherwise an exception is thrown

  template<typename T> T get_attribute(std::string const& attr_name)
  {
    rapidjson::Value::ConstMemberIterator itr = doc_.FindMember(attr_name.c_str());

    if (itr == doc_.MemberEnd())
    {
      std::stringstream ss;
      ss << "Missing attribute " << attr_name;
      throw IpcMessageException(ss.str());
    }
    return get_value<T>(itr);
  }

  //! Gets the value of a message attribute.
  //!
  //! This private template method returns the value of a message attribute. If the
  //! attribute is mssing, the default value specified in the arguments is returned.
  //!
  //! \param attr_name - name of the attribute to return
  //! \param default_value - default value to return if attribute missing
  //! \return value of the attribute if present, otherwise an exception is thrown

  template<typename T> T get_attribute(std::string const& attr_name, T const& default_value)
  {
    rapidjson::Value::ConstMemberIterator itr = doc_.FindMember(attr_name.c_str());
    return itr == doc_.MemberEnd() ? default_value : this->get_value<T>(itr);
  }

  //! Sets the value of a message attribute.
  //!
  //! The private template method sets the value of a message attribute, creating the
  //! attribute if not already present in the message.
  //!
  //! \param attr_name - name of the attribute to set
  //! \param attr_value - value to set

  template<typename T> void set_attribute(std::string const& attr_name, T const& attr_value)
  {
    rapidjson::Document::AllocatorType& allocator = doc_.GetAllocator();

    // Search through the document for the attribute, creating it if missing, and set
    // the value appropriately
    rapidjson::Value::MemberIterator itr = doc_.FindMember(attr_name.c_str());
    if (itr == doc_.MemberEnd())
    {
      rapidjson::Value attr_name_val(attr_name.c_str(), allocator);
      rapidjson::Value attr_value_val;
      set_value(attr_value_val, attr_value);
      doc_.AddMember(attr_name_val, attr_value_val, allocator);
    }
    else
    {
      set_value(const_cast<rapidjson::Value&>(itr->value), attr_value);
    }
  }

  //! Gets the value of a message attribute.
  //!
  //! This private template method gets the value of a message attribute referenced by
  //! the iterator provided as an argument. Explicit specializations of this method
  //! are provided, mapping each of the RapidJSON attribute types onto the appropriate
  //! return value.
  //!
  //! \param itr - RapidJSON const member iterator referencing the attribute to access
  //! \return - value of the attribute, with the appropriate type

  template<typename T> T get_value(rapidjson::Value::ConstMemberIterator& itr) const;

  //! Sets the value of a message attribute.
  //!
  //! This private template method sets the value of a message attribute referenced by
  //! the RapidJSON value object passed as an argument. Explicit specialisations of this
  //! method are provided below, mapping the value type specified onto each of the
  //! RapidJSON atttribute types.
  //!
  //! \param value_obj - RapidJSON value object to set value of
  //! \param value - value to set

  template<typename T> void set_value(rapidjson::Value& value_obj, T const& value);

  //! Initialise the internal message type bidirectional map
  void msg_type_map_init();

  //! Maps a message type string to a valid enumerated MsgType
  MsgType valid_msg_type(std::string msg_type_name);

  //! Maps an enumerated MsgType message type to the equivalent string
  std::string valid_msg_type(MsgType msg_type);

  //! Initialise the internal message value bidirectional map
  void msg_val_map_init();

  //! Maps a message value string to a valid enumerated MsgVal
  MsgVal valid_msg_val(std::string msg_val_name);

  //! Maps an enumerated MsgVal message value to the equivalent string
  std::string valid_msg_val(MsgVal msg_val);

  //! Maps a message timestamp onto a the internal timestamp representation
  boost::posix_time::ptime valid_msg_timestamp(std::string msg_timestamp_text);

  //! Maps an internal message timestamp representation to an ISO8601 extended format string
  std::string valid_msg_timestamp(boost::posix_time::ptime msg_timestamp);

  //! Indicates if the message has a params block
  bool has_params(void) const;

  // Private member variables

  bool strict_validation_;                  //!< Strict validation enabled flag
  rapidjson::Document doc_;                 //!< RapidJSON document object
  MsgType msg_type_;                        //!< Message type attribute
  MsgVal msg_val_;                          //!< Message value attribute
  boost::posix_time::ptime msg_timestamp_;  //!< Message timestamp (internal representation)
  unsigned int msg_id_;                     //!< Message id attribute

  rapidjson::StringBuffer encode_buffer_;   //!< Encoding buffer used to encode message to JSON string
  static MsgTypeMap msg_type_map_;          //!< Bi-directional message type map
  static MsgValMap msg_val_map_;            //!< Bi-directional message value map

}; // IpcMessage


} // namespace OdinData
#endif /* IPCMESSAGE_H_ */