Initial commit of Admission Control xAPP and E2AP/X2AP definitions
[ric-app/admin.git] / src / json / json_handler.cc
diff --git a/src/json/json_handler.cc b/src/json/json_handler.cc
new file mode 100644 (file)
index 0000000..a14b341
--- /dev/null
@@ -0,0 +1,686 @@
+/*
+==================================================================================
+
+        Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+   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.
+==================================================================================
+*/
+/*
+ Author : Ashwin Sridharan
+
+  
+*/
+
+#include <json_handler.hpp>
+#include <cstdio>
+
+TrieNode::TrieNode(int val): val_type(-1){
+  _id.tag = DataContainer::Types::integer;
+  _id.value.i = val;
+}
+
+TrieNode::TrieNode( std::string val) : val_type(-1){
+  _id.tag = DataContainer::Types::str;
+  _id.value.s.assign(val);
+}
+
+void TrieNode::set_value(const char * val){
+  _val.tag = DataContainer::Types::str;
+  _val.value.s.assign(val);  
+}
+
+
+void TrieNode::set_value(bool val){
+  _val.tag = DataContainer::Types::boolean;
+  _val.value.b = val;
+  
+}
+
+void TrieNode::set_value(int val){
+  _val.tag = DataContainer::Types::integer;
+  _val.value.i = val;
+  //std::cout <<"Assigned integer " << val << std::endl;
+
+    
+}
+
+void TrieNode::set_value(unsigned int val){
+  _val.tag = DataContainer::Types::uinteger;
+  _val.value.ui = val;
+  
+}
+
+void TrieNode::set_value( long int val){
+  _val.tag = DataContainer::Types::big_integer;
+  _val.value.l = val;
+}
+
+void TrieNode::set_value(unsigned  long int val){
+  _val.tag = DataContainer::Types::ubig_integer;
+  _val.value.ul = val;
+}
+
+void TrieNode::set_value(double val){
+  _val.tag = DataContainer::Types::real;
+  _val.value.f = val;
+    
+}
+
+
+void TrieNode::set_value(std::string val){
+  _val.tag = DataContainer::Types::str;
+  _val.value.s.assign(val);
+}
+
+void TrieNode::set_value(const char * c, size_t len){
+  _val.tag = DataContainer::Types::str;
+  _val.value.s.assign(c, len);
+}
+
+void TrieNode::add_child(TrieNode * node){
+  _children.push_back(node);
+};
+
+
+
+void TrieNode::print_id(void){
+  switch(_id.tag){
+
+  case DataContainer::Types::integer :
+    std::cout <<"Type = " << _id.tag << " Value = " << _id.value.i << std::endl;
+    break;
+  case DataContainer::Types::str :
+    std::cout <<"Type = " << _id.tag << " Value = " << _id.value.s << std::endl;
+    break;
+  default:
+    std::cerr<< "Error ! ID  not set or unknown type " << _id.tag;
+  }
+};
+
+void TrieNode::print_value(void){
+  switch(_val.tag){
+
+  case DataContainer::Types::boolean :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.b << std::endl;
+    break;
+  case DataContainer::Types::integer :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.i << std::endl;
+    break;
+  case DataContainer::Types::uinteger :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.ui << std::endl;
+    break;
+  case DataContainer::Types::big_integer :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.l << std::endl;
+    break;
+  case DataContainer::Types::ubig_integer :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.ul << std::endl;
+    break;
+  case DataContainer::Types::real :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.f << std::endl;
+    break;
+  case DataContainer::Types::str :
+    std::cout <<"Type = " << _val.tag << " Value = " << _val.value.s << std::endl;
+    break;
+  default:
+    std::cerr<< "Error ! Value not set or unknown type " << _val.tag;
+  }
+};
+    
+
+jsonHandler::jsonHandler(void):_is_root(false), _is_schema(false), _is_buffer(false){
+};
+
+
+
+
+void jsonHandler::load_file(std::string input_file, std::string &  contents ){
+
+  std::FILE *fp ;
+  try{
+    fp = std::fopen(input_file.c_str(), "rb");
+  }
+  catch(std::exception &e){
+    std::string error_string = "Error opening input schema file " + input_file;
+    throw std::runtime_error(error_string);
+  } 
+  
+  if (fp){
+    std::fseek(fp, 0, SEEK_END);
+    contents.resize(std::ftell(fp));
+    std::rewind(fp);
+    std::fread(&contents[0], 1, contents.size(), fp);
+    std::fclose(fp);
+  }
+  
+  else{
+    std::string error_string = "Error opening input  file " + input_file;
+    throw std::runtime_error(error_string);
+  }
+  
+}
+
+
+
+void jsonHandler::load_schema(std::string input_file){
+
+  load_file(input_file, _contents);
+  Document _doc;
+  if (_doc.Parse(_contents.c_str()).HasParseError()){
+    std::string error_string = input_file + " is invalid JSON" ;
+    throw std::runtime_error(error_string);
+  }
+  
+  _ref_schema_doc= std::make_unique<SchemaDocument>(_doc);
+  _is_schema = true;
+  
+  
+}
+
+void  jsonHandler::load_schema(std::string input_file, TrieNode * root){
+
+  load_file(input_file, _contents);
+  std::string response;
+  Document _doc;
+  if (_doc.Parse(_contents.c_str()).HasParseError()){
+    std::string error_string = input_file + " is invalid JSON" ;
+    throw std::runtime_error(error_string);
+  }
+  
+  // Get message schema
+  bool res;
+  Value  _schema_root;
+  Value &_root = _doc;
+  res = find_member(_root,  response, root, _schema_root);
+  if (res == false){
+    throw std::runtime_error(response);
+  }
+
+  _ref_schema_doc= std::make_unique<SchemaDocument>(_schema_root);
+  _is_schema = true;
+  
+}
+
+void jsonHandler::load_buffer(std::string input_file){
+  
+  load_file(input_file, _buffer);
+  Document _doc;
+  if (_doc.Parse(_buffer.c_str()).HasParseError()){
+    std::string error_string = input_file + " is invalid JSON" ;
+    throw std::runtime_error(error_string);
+  }   
+  _is_buffer = true;
+  
+}
+
+void jsonHandler::load_buffer(std::string input_file, TrieNode * root){
+  
+  load_file(input_file, _buffer);
+  Document _doc;
+  std::string response;
+  
+  if (_doc.Parse(_buffer.c_str()).HasParseError()){
+    std::string error_string = input_file + " is invalid JSON" ;
+    throw std::runtime_error(error_string);
+  }   
+
+  bool res;
+  Value _buffer_root;
+  res = find_member(_doc, response, root, _buffer_root);
+  if(res == false){
+    throw std::runtime_error(response);
+  }
+
+  StringBuffer out_buffer;
+  Writer<StringBuffer> writer(out_buffer);
+  _buffer_root.Accept(writer);
+  _buffer.assign(out_buffer.GetString(), out_buffer.GetLength());
+  _is_buffer = true;
+}
+
+
+std::string jsonHandler::get_buffer(void){
+  std::string response;  
+  if (_is_buffer){
+    response.assign(_buffer);
+  }
+  else{
+    response = "";
+  }
+  
+  return response;
+}
+
+
+  
+
+
+
+bool jsonHandler::find_member(const std::string schema, std::string & response, TrieNode * root, Value & TargetVal){
+
+  Document doc;
+  std::string contents(schema);
+  
+  if(doc.Parse(contents.c_str()).HasParseError()){
+    response.assign("Error Parsing JSON File");
+    return false;
+  }
+
+  return find_member(doc, response, root, TargetVal);
+  return true;
+};
+
+
+bool jsonHandler::find_member(Value & doc_root, std::string & response, TrieNode * root, Value & TargetVal){
+
+  if (!root){
+    response.assign("Null Trie root node");
+    return false;
+  }
+  //std::cout <<"LOoking for schema root" << std::endl;
+
+  Value & json_node = doc_root;
+  TrieNode * trie_node = root;  
+  Value::MemberIterator itr;
+  
+  while(1){
+
+    DataContainer const * d  = trie_node->get_id();
+    if (! d){
+      response.assign("Error could not find any id for trie node ");
+      return false;
+    }
+
+    if (d->tag == DataContainer::Types::integer && json_node.IsArray()){
+      if (json_node.Size() < d->value.i){
+       response.assign("Error json array size ");
+       response +=  std::to_string(json_node.Size())  +  " is smaller than trie node index " + std::to_string( d->value.i);
+       return false;
+      }
+      
+      if (trie_node->is_child()){
+       response.assign("Error child trie points to an array ? ");
+       return false;
+      }
+
+      trie_node = trie_node->get_children()[0];
+      json_node = json_node[d->value.i];
+    }
+    else if (d->tag == DataContainer::Types::str && json_node.IsObject()){
+      
+      itr = json_node.FindMember(d->value.s.c_str());
+      if (itr == json_node.MemberEnd()){
+       response.assign("Error ! Could not find key = ");
+       response +=  d->value.s;
+       return false;
+      }
+      if (trie_node->is_child()){
+       // reached end of trie 
+       if (itr->value.IsObject()){
+         TargetVal = itr->value.GetObject();
+         //std::cout <<"Reached root = " << itr->name.GetString() << std::endl;
+       }
+       else if (itr->value.IsArray()){
+         TargetVal = itr->value.GetArray();
+       }
+       else{
+         response.assign("Error ! JSON node selected  must be object or array in current version");
+         std::cerr << response << std::endl;
+         return false;
+       }
+       break;
+      }
+      else{
+       trie_node = trie_node->get_children()[0];
+       trie_node->print_id();
+       
+       if (itr->value.IsObject()){
+         json_node = itr->value.GetObject();
+       }
+       else if (itr->value.IsArray()){
+         json_node = itr->value.GetArray();
+       }
+       else{
+         std::string error_string= " Path must be an object or array";
+         response.assign(error_string);
+         return false;
+       }
+      }
+    }
+    else{
+      std::string error_string = "Mismatch when setting root  : Trie node is of type = " + std::to_string (d->tag) + " and json node is of type = " + std::to_string(json_node.GetType());
+      response.assign(error_string);
+      return false;
+    }
+
+  }
+
+  return true;
+
+}
+
+bool jsonHandler::is_valid(const char *message, int message_length,  std::string & response){
+
+  Document doc;
+  if (! _is_schema){
+    return false;
+  }
+
+  SchemaValidator validator(*(_ref_schema_doc.get()));
+  
+  // ensure message has terminator by translating to string ?
+  std::string message_s(message, message_length);
+
+  
+  // validate json 
+  if (doc.Parse(message_s.c_str()).HasParseError()){
+    
+    // return error message
+    std::string failed_message = "\"message\": \"Invalid JSON\"";
+    response.assign( failed_message );
+    return false;
+  }
+
+  
+  // Validate against our JSON input schema
+  if (!doc.Accept(validator)){
+    
+    StringBuffer sb;
+    validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
+    std::string failed_message = std::string("\"message\": \"Schema Violation:") + std::string(sb.GetString());
+    failed_message += std::string(" Invalid keyword :") + std::string(validator.GetInvalidSchemaKeyword()) + " \"";
+    response.assign(failed_message);
+    return false;
+    
+  }
+  response.assign("SUCCESS");
+  return true;
+  
+  
+}
+
+
+
+// should be thread safe since it can be expected to be called from multiple threads
+// only static external variable referenced is the schema (which should be read-only and hence ok ?)
+
+// Returns 0 if success
+// -1 if invalid json
+// -2 if invalid schema (assuming schema provided)
+// -3 unknown key
+// -4 no buffer available
+
+int jsonHandler::get_values(const  char *message, int message_length, std::string & response, TrieNode * root, std::vector<TrieNode *> & response_list){
+  
+  Document doc;
+
+  // ensure message has terminator by translating to string ?
+  std::string message_s(message, message_length);
+  
+  // validate json 
+  if (doc.Parse(message_s.c_str()).HasParseError()){
+    
+    // return error message
+    response.assign("Invalid JSON");
+    return -1;
+  }
+  
+  // Validate against our JSON input schema
+  if ( _is_schema){
+    SchemaValidator validator(*(_ref_schema_doc.get()));
+    
+    if (!doc.Accept(validator)){
+    
+      StringBuffer sb;
+      validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
+      response = std::string("Schema Violation ") + std::string(sb.GetString()) ;
+      response  += std::string(" Invalid keyword = ") + std::string(validator.GetInvalidSchemaKeyword()) + " \"";
+      return -2;
+    }
+
+  }
+  
+  Value & doc_root = doc;
+  bool res = traverse_doc(doc_root, root, true, response, response_list);
+  if (!res){
+    return -3;
+  }
+    
+  response.assign("SUCCESS");
+  return 0;
+}
+
+
+int jsonHandler::get_values( std::string & response, TrieNode * root, std::vector<TrieNode *> & response_list){
+  int res;
+  if (_is_buffer){
+    Document _doc;
+    _doc.Parse(_buffer.c_str());
+    Value & _buffer_root = _doc;
+    
+    res = traverse_doc(_buffer_root, root, true, response,  response_list);
+    return res;
+  }
+  else{
+    response = "Error !  No buffer loaded in json object for get";
+    return -4;
+  }
+}
+
+
+// If in get mode, return all values we can get
+// If in set mode, return false if we cannot set a value 
+bool jsonHandler::traverse_doc(Value & json_node, TrieNode * trie_node, bool get, std::string & response, std::vector<TrieNode* > & response_list ){
+
+  if (!trie_node){
+    response.assign(" Null Trie node ");
+    return  false;
+  }
+
+  bool res;
+  
+  DataContainer const * d  = trie_node->get_id();
+  if (! d){
+    response.assign(" Error could not find any id for trie node");
+    return false;
+  }
+  Value::MemberIterator itr;
+  
+
+  if (d->tag == DataContainer::Types::integer && json_node.IsArray()){
+    if (json_node.Size() < d->value.i){
+      response = "Error json array size " + std::to_string( json_node.Size()) +  " is smaller than trie node index " + std::to_string( d->value.i);
+      return false;
+    }
+    
+    if (trie_node->is_child()){
+      response.assign("Error child trie points to an array ? ");
+      return false;
+    }
+    
+    for (auto & e: trie_node->get_children()){
+      res = traverse_doc(json_node[d->value.i], e, get, response, response_list);
+      if (!res && ! get){                              
+       // if not in get mode and we hit a not found
+       // don't go any further, else move to next ...
+       return res;
+      }
+    }
+  }
+  
+  else if (d->tag == DataContainer::Types::str  && json_node.IsObject()){
+    itr = json_node.FindMember(d->value.s.c_str());
+    
+    if (itr == json_node.MemberEnd()){
+      response = "Error ! Could not find key " + d->value.s;
+      return false;
+    }
+
+    if (trie_node->is_child()){
+      // end of the line : do we get or set values  ?
+      bool is_set = false;
+      if (get){
+       if (itr->value.IsBool()){
+         trie_node->set_value(itr->value.GetBool());
+         is_set = true;
+       }
+       else if (itr->value.IsInt()){
+         trie_node->set_value(itr->value.GetInt());
+         is_set = true;
+       }
+       else if(itr->value.IsUint()){
+         trie_node->set_value(static_cast<unsigned int>(itr->value.GetUint()));
+         is_set = true;
+       }
+       else if(itr->value.IsUint64()){
+         trie_node->set_value(static_cast<unsigned long int>(itr->value.GetUint64()));
+         is_set = true;
+       }
+       else if (itr->value.IsInt64()){
+         trie_node->set_value(static_cast<long int>(itr->value.GetInt64()));
+         is_set = true;
+       }
+       else if ( itr->value.IsDouble()){
+         trie_node->set_value(itr->value.GetDouble());
+         is_set = true;
+       }
+       else if ( itr->value.IsString()){
+         trie_node->set_value(itr->value.GetString(), itr->value.GetStringLength());
+         is_set = true;
+       }
+       else{
+         response =  " json node corresponding to child node key  must of type bool, int or string. Is of type = "  + std::to_string(itr->value.GetType());
+         return false;
+       }
+
+       if (is_set){
+         response_list.push_back(trie_node);
+       }
+       
+       //std::cout <<"Set value of child node with key = " << d->value.s.c_str() << " Type = " << trie_node->get_type()  << std::endl;
+       
+      }
+      else{
+       DataContainer const  * d_val = trie_node->get_value();
+       if (d_val->tag == DataContainer::Types::boolean){
+         itr->value.SetBool(d_val->value.b);
+       }
+       else if (d_val->tag == DataContainer::Types::integer){
+         itr->value.SetInt(d_val->value.i);
+
+       }
+       else if (d_val->tag == DataContainer::Types::uinteger){
+         itr->value.SetUint(d_val->value.ui);
+
+       }
+       else if (d_val->tag == DataContainer::Types::big_integer){
+         itr->value.SetInt64(d_val->value.l);
+       }
+       else if (d_val->tag == DataContainer::Types::ubig_integer){
+         itr->value.SetUint64(d_val->value.ul);
+       }
+       else if (d_val->tag == DataContainer::Types::real){
+         itr->value.SetDouble(d_val->value.f);
+       }
+       else if (d_val->tag == DataContainer::Types::str){
+         itr->value.SetString(d_val->value.s.c_str(), d_val->value.s.length());                  
+       }
+       else{
+         response = " unknown type for child node value = " + std::to_string(d_val->tag) + " cannot set json node key = " +  d->value.s;
+         return false;
+       }
+      }      
+      return true;
+    }
+    else{
+      for (auto & e: trie_node->get_children()){
+       res = traverse_doc(itr->value, e, get, response, response_list);
+       if(res == false && ! get){
+         return false;
+       }
+      }
+    }
+  }
+  else{
+    response = "Mismatch : Trie node is of type = " + std::to_string(d->tag) + " while json node is of type = "  + std::to_string( json_node.GetType());
+    return false;
+  }
+
+  return true;
+  
+}
+
+
+int jsonHandler::set_values(const char * buffer, int len, std::string & response, std::vector<TrieNode *> root_nodes){
+  Document doc;
+  std::string message_s(buffer, len);
+  
+  // validate json 
+  if (doc.Parse(message_s.c_str()).HasParseError()){  
+    // return error message
+    response.assign("Invalid JSON");
+    return -1;
+  }
+
+  
+  Value & doc_root = doc;
+  // fake list to maintain signature for re-using traverse_doc
+  // since we don't return trie nodes when setting ...
+  std::vector<TrieNode *> fake_list;
+  for(auto const & e: root_nodes){
+    bool res = traverse_doc(doc_root, e, false, response, fake_list);
+    if (!res){
+      return -3;
+    }
+  }
+  
+  StringBuffer out_buffer;
+  Writer<StringBuffer> writer(out_buffer);
+  doc_root.Accept(writer);
+  response.assign(out_buffer.GetString(), out_buffer.GetLength());
+   return 0;
+ }  
+
+
+// wrapper if instead of providing buffer, we simply use stored json object and use it
+int jsonHandler::set_values(std::string & response, std::vector<TrieNode *> root_nodes){
+  if (_is_buffer){
+    std::vector<TrieNode *> fake_list;
+    Document _doc;
+    _doc.Parse(_buffer.c_str());
+    Value & _buffer_root = _doc;
+    
+    for(auto const & e: root_nodes){
+      bool res  = traverse_doc(_buffer_root, e,  false, response, fake_list);
+      if (!res){
+       return -3;
+      }
+    }
+    
+    StringBuffer out_buffer;
+    Writer<StringBuffer> writer(out_buffer);
+    _buffer_root.Accept(writer);
+    response.assign(out_buffer.GetString(), out_buffer.GetLength());
+    return 0;
+  }
+  else{
+    response = "Error ! " + std::string( __FILE__) + "," + std::to_string(__LINE__) + " :  No buffer loaded in json object to set";
+    return -1;
+  }
+  
+}