Commit 31b31ad8 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

Merge ticket 172 (JSON on cc channel)

This merge expands isc::data::Element with full JSON support, and removes the binary wire format. Python side uses built-in JSON module.


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@2364 e5f2f494-b856-4b98-b285-d166d9295462
parents 141de43f 1b931026
......@@ -274,7 +274,7 @@ AuthSrvImpl::setDbFile(const isc::data::ElementPtr config) {
bool is_default;
string item("database_file");
ElementPtr value = cs_->getValue(is_default, item);
final = Element::createFromString("{}");
final = Element::createMap();
// If the value is the default, and we are running from
// a specific directory ('from build'), we need to use
......
......@@ -236,7 +236,7 @@ updateConfig(AuthSrv* server, const char* const dbfile,
const bool expect_success)
{
const ElementPtr config_answer =
server->updateConfig(Element::createFromString(dbfile));
server->updateConfig(Element::fromJSON(dbfile));
EXPECT_EQ(Element::map, config_answer->getType());
EXPECT_TRUE(config_answer->contains("result"));
......
This diff is collapsed.
......@@ -48,22 +48,10 @@ public:
// i'd like to use Exception here but we need one that is derived from
// runtime_error (as this one is directly based on external data, and
// i want to add some values to any static data string that is provided)
class ParseError : public std::runtime_error {
class JSONError : public isc::Exception {
public:
ParseError(const std::string &err) : std::runtime_error(err) {};
};
///
/// \brief A standard Data module exception that is thrown if an error
/// is found when decoding an Element from wire format
///
class DecodeError : public std::exception {
public:
DecodeError(std::string m = "Wire-format data is invalid") : msg(m) {}
~DecodeError() throw() {}
const char* what() const throw() { return msg.c_str(); }
private:
std::string msg;
JSONError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
///
......@@ -75,7 +63,7 @@ private:
///
/// Elements should in calling functions usually be referenced through
/// an \c ElementPtr, which can be created using the factory functions
/// \c Element::create() and \c Element::createFromString()
/// \c Element::create() and \c Element::fromJSON()
///
/// Notes to developers: Element is a base class, implemented by a
/// specific subclass for each type (IntElement, BoolElement, etc).
......@@ -97,48 +85,48 @@ protected:
public:
// any is a special type used in list specifications, specifying
// that the elements can be of any type
enum types { integer, real, boolean, string, list, map, any };
enum types { integer, real, boolean, null, string, list, map, any };
// base class; make dtor virtual
virtual ~Element() {};
/// \return the type of this element
int getType() { return type; };
/// \returns true if the other ElementPtr has the same type and
/// value
virtual bool equals(ElementPtr other) = 0;
// pure virtuals, every derived class must implement these
/// Returns a string representing the Element and all its
/// child elements; note that this is different from stringValue(),
/// which only returns the single value of a StringElement
/// A MapElement will be represented as { "name1": \<value1\>, "name2", \<value2\>, etc }
/// A ListElement will be represented as [ \<item1\>, \<item2\>, etc ]
/// All other elements will be represented directly
///
/// The resulting string will contain the Element in JSON format.
///
/// \return std::string containing the string representation
virtual std::string str() = 0;
std::string str();
/// Returns the wireformat for the Element and all its child
/// elements.
///
/// \param omit_length If this is non-zero, the item length will
/// be omitted from the wire format
/// \return std::string containing the element in wire format
std::string toWire(int omit_length = 1);
virtual void toWire(std::stringstream& out, int omit_length = 1) = 0;
std::string toWire();
void toWire(std::ostream& out);
/// \name pure virtuals, every derived class must implement these
/// \returns true if the other ElementPtr has the same type and
/// value
virtual bool equals(ElementPtr other) = 0;
/// Converts the Element to JSON format and appends it to
/// the given stringstream.
virtual void toJSON(std::ostream& ss) = 0;
/// \name Type-specific getters
///
///
/// \brief These functions only
/// work on their corresponding Element type. For all other
/// types, a TypeError is thrown.
/// If you want an exception-safe getter method, use
/// getValue() below
//@{
virtual int intValue() { isc_throw(TypeError, "intValue() called on non-integer Element"); };
virtual long int intValue() { isc_throw(TypeError, "intValue() called on non-integer Element"); };
virtual double doubleValue() { isc_throw(TypeError, "doubleValue() called on non-double Element"); };
virtual bool boolValue() { isc_throw(TypeError, "boolValue() called on non-Bool Element"); };
virtual std::string stringValue() { isc_throw(TypeError, "stringValue() called on non-string Element"); };
......@@ -155,13 +143,14 @@ public:
/// data to the given reference and returning true
///
//@{
virtual bool getValue(int& t);
virtual bool getValue(long int& t);
virtual bool getValue(double& t);
virtual bool getValue(bool& t);
virtual bool getValue(std::string& t);
virtual bool getValue(std::vector<ElementPtr>& t);
virtual bool getValue(std::map<std::string, ElementPtr>& t);
//@}
///
/// \name Exception-safe setters.
///
......@@ -170,7 +159,7 @@ public:
/// is of the correct type
///
//@{
virtual bool setValue(const int v);
virtual bool setValue(const long int v);
virtual bool setValue(const double v);
virtual bool setValue(const bool t);
virtual bool setValue(const std::string& v);
......@@ -191,21 +180,26 @@ public:
/// of bounds, this function throws an std::out_of_range exception.
/// \param i The position of the ElementPtr to return
virtual ElementPtr get(const int i);
/// Sets the ElementPtr at the given index. If the index is out
/// of bounds, this function throws an std::out_of_range exception.
/// \param i The position of the ElementPtr to set
/// \param element The ElementPtr to set at the position
virtual void set(const size_t i, ElementPtr element);
/// Adds an ElementPtr to the list
/// \param element The ElementPtr to add
virtual void add(ElementPtr element);
/// Removes the element at the given position. If the index is out
/// of nothing happens.
/// \param i The index of the element to remove.
virtual void remove(const int i);
/// Returns the number of elements in the list.
virtual size_t size();
//@}
/// \name MapElement functions
///
......@@ -216,16 +210,20 @@ public:
/// \param name The key of the Element to return
/// \return The ElementPtr at the given key
virtual ElementPtr get(const std::string& name);
/// Sets the ElementPtr at the given key
/// \param name The key of the Element to set
virtual void set(const std::string& name, ElementPtr element);
/// Remove the ElementPtr at the given key
/// \param name The key of the Element to remove
virtual void remove(const std::string& name);
/// Checks if there is data at the given key
/// \param name The key of the Element to remove
/// \return true if there is data at the key, false if not.
virtual bool contains(const std::string& name);
/// Recursively finds any data at the given identifier. The
/// identifier is a /-separated list of names of nested maps, with
/// the last name being the leaf that is returned.
......@@ -240,6 +238,7 @@ public:
/// null ElementPtr if it is not found, which can be checked with
/// Element::is_null(ElementPtr e).
virtual ElementPtr find(const std::string& identifier);
/// See \c Element::find()
/// \param identifier The identifier of the element to find
/// \param t Reference to store the resulting ElementPtr, if found.
......@@ -247,6 +246,7 @@ public:
virtual bool find(const std::string& identifier, ElementPtr& t);
//@}
/// \name Factory functions
// TODO: should we move all factory functions to a different class
......@@ -257,39 +257,52 @@ public:
/// \brief These functions simply wrap the given data directly
/// in an Element object, and return a reference to it, in the form
/// of an \c ElementPtr.
/// If there is a memory allocation problem, these functions will
/// return a NULL ElementPtr, which can be checked with
/// Element::is_null(ElementPtr ep).
/// These factory functions are exception-free (unless there is
/// no memory available, in which case bad_alloc is raised by the
/// underlying system).
/// (Note that that is different from an NullElement, which
/// represents an empty value, and is created with Element::create())
//@{
static ElementPtr create(const int i);
static ElementPtr create();
static ElementPtr create(const long int i);
static ElementPtr create(const int i) { return create(static_cast<long int>(i)); };
static ElementPtr create(const double d);
static ElementPtr create(const bool b);
static ElementPtr create(const std::string& s);
// need both std:string and char *, since c++ will match
// bool before std::string when you pass it a char *
static ElementPtr create(const char *s) { return create(std::string(s)); };
static ElementPtr create(const std::vector<ElementPtr>& v);
static ElementPtr create(const std::map<std::string, ElementPtr>& m);
static ElementPtr create(const char *s) { return create(std::string(s)); };
/// \brief Creates an empty ListElement type ElementPtr.
static ElementPtr createList();
/// \brief Creates an empty MapElement type ElementPtr.
static ElementPtr createMap();
//@}
/// \name Compound factory functions
/// \brief These functions will parse the given string representation
/// of a compound element. If there is a parse error, an exception
/// of the type isc::data::ParseError is thrown.
/// \brief These functions will parse the given string (JSON)
/// representation of a compound element. If there is a parse
/// error, an exception of the type isc::data::JSONError is thrown.
//@{
/// Creates an Element from the given string
/// Creates an Element from the given JSON string
/// \param in The string to parse the element from
/// \return An ElementPtr that contains the element(s) specified
/// in the given string.
static ElementPtr createFromString(const std::string& in);
/// Creates an Element from the given input stream
static ElementPtr fromJSON(const std::string& in);
/// Creates an Element from the given input stream containing JSON
/// formatted data.
///
/// \param in The string to parse the element from
/// \return An ElementPtr that contains the element(s) specified
/// in the given input stream.
static ElementPtr createFromString(std::istream& in) throw(ParseError);
static ElementPtr createFromString(std::istream& in, const std::string& file_name) throw(ParseError);
static ElementPtr fromJSON(std::istream& in) throw(JSONError);
static ElementPtr fromJSON(std::istream& in, const std::string& file_name) throw(JSONError);
/// Creates an Element from the given input stream, where we keep
/// track of the location in the stream for error reporting.
///
......@@ -301,9 +314,25 @@ public:
/// \return An ElementPtr that contains the element(s) specified
/// in the given input stream.
// make this one private?
static ElementPtr createFromString(std::istream& in, const std::string& file, int& line, int &pos) throw(ParseError);
static ElementPtr fromJSON(std::istream& in, const std::string& file, int& line, int &pos) throw(JSONError);
//@}
/// \name Type name conversion functions
/// Returns the name of the given type as a string
///
/// \param type The type to return the name of
/// \return The name of the type, or "unknown" if the type
/// is not known.
static std::string typeToName(Element::types type);
/// Converts the string to the corresponding type
/// Throws a TypeError if the name is unknown.
///
/// \param type_name The name to get the type of
/// \return the corresponding type value
static Element::types nameToType(const std::string& type_name);
/// \name Wire format factory functions
/// These function pparse the wireformat at the given stringstream
......@@ -313,11 +342,18 @@ public:
//@{
/// Creates an Element from the wire format in the given
/// stringstream of the given length.
/// Since the wire format is JSON, thise is the same as
/// fromJSON, and could be removed.
///
/// \param in The input stringstream.
/// \param length The length of the wireformat data in the stream
/// \return ElementPtr with the data that is parsed.
static ElementPtr fromWire(std::stringstream& in, int length);
/// Creates an Element from the wire format in the given string
/// Since the wire format is JSON, thise is the same as
/// fromJSON, and could be removed.
///
/// \param s The input string
/// \return ElementPtr with the data that is parsed.
static ElementPtr fromWire(const std::string& s);
......@@ -325,17 +361,16 @@ public:
};
class IntElement : public Element {
int i;
long int i;
public:
IntElement(int v) : Element(integer), i(v) { };
int intValue() { return i; }
IntElement(long int v) : Element(integer), i(v) { };
long int intValue() { return i; }
using Element::getValue;
bool getValue(int& t) { t = i; return true; };
bool getValue(long int& t) { t = i; return true; };
using Element::setValue;
bool setValue(const int v) { i = v; return true; };
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
bool setValue(const long int v) { i = v; return true; };
void toJSON(std::ostream& ss);
bool equals(ElementPtr other);
};
......@@ -349,8 +384,7 @@ public:
bool getValue(double& t) { t = d; return true; };
using Element::setValue;
bool setValue(const double v) { d = v; return true; };
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
void toJSON(std::ostream& ss);
bool equals(ElementPtr other);
};
......@@ -364,8 +398,14 @@ public:
bool getValue(bool& t) { t = b; return true; };
using Element::setValue;
bool setValue(const bool v) { b = v; return true; };
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
void toJSON(std::ostream& ss);
bool equals(ElementPtr other);
};
class NullElement : public Element {
public:
NullElement() : Element(null) {};
void toJSON(std::ostream& ss);
bool equals(ElementPtr other);
};
......@@ -379,8 +419,7 @@ public:
bool getValue(std::string& t) { t = s; return true; };
using Element::setValue;
bool setValue(const std::string& v) { s = v; return true; };
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
void toJSON(std::ostream& ss);
bool equals(ElementPtr other);
};
......@@ -388,7 +427,7 @@ class ListElement : public Element {
std::vector<ElementPtr> l;
public:
ListElement(std::vector<ElementPtr> v) : Element(list), l(v) {};
ListElement() : Element(list), l(std::vector<ElementPtr>()) {};
const std::vector<ElementPtr>& listValue() { return l; }
using Element::getValue;
bool getValue(std::vector<ElementPtr>& t) { t = l; return true; };
......@@ -401,8 +440,7 @@ public:
void add(ElementPtr e) { l.push_back(e); };
using Element::remove;
void remove(int i) { l.erase(l.begin() + i); };
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
void toJSON(std::ostream& ss);
size_t size() { return l.size(); }
bool equals(ElementPtr other);
};
......@@ -411,7 +449,8 @@ class MapElement : public Element {
std::map<std::string, ElementPtr> m;
public:
MapElement(const std::map<std::string, ElementPtr>& v) : Element(map), m(v) {};
MapElement() : Element(map), m(std::map<std::string, ElementPtr>()) {};
// TODO: should we have direct iterators instead of exposing the std::map here?
const std::map<std::string, ElementPtr>& mapValue() { return m; }
using Element::getValue;
bool getValue(std::map<std::string, ElementPtr>& t) { t = m; return true; };
......@@ -420,18 +459,12 @@ public:
using Element::get;
ElementPtr get(const std::string& s) { if (contains(s)) { return m[s]; } else { return ElementPtr();} };
using Element::set;
void set(const std::string& s, ElementPtr p) { m[s] = p; };
void set(const std::string& key, ElementPtr value);
using Element::remove;
void remove(const std::string& s) { m.erase(s); }
bool contains(const std::string& s) { return m.find(s) != m.end(); }
std::string str();
void toWire(std::stringstream& ss, int omit_length = 1);
void toJSON(std::ostream& ss);
//
// Encode into the CC wire format.
//
void toWire(std::ostream& ss);
// we should name the two finds better...
// find the element at id; raises TypeError if one of the
// elements at path except the one we're looking for is not a
......@@ -467,8 +500,12 @@ void removeIdentical(ElementPtr a, const ElementPtr b);
/// MapElements.
/// Every string,value pair in other is copied into element
/// (the ElementPtr of value is copied, this is not a new object)
/// Unless the value is an empty ElementPtr, in which case the
/// whole key is removed from element.
/// Unless the value is a NullElement, in which case the
/// key is removed from element, rather than setting the value to
/// the given NullElement.
/// This way, we can remove values from for instance maps with
/// configuration data (which would then result in reverting back
/// to the default).
/// Raises a TypeError if either ElementPtr is not a MapElement
void merge(ElementPtr element, const ElementPtr other);
......
This diff is collapsed.
......@@ -63,7 +63,7 @@ namespace cc {
class SessionImpl {
public:
SessionImpl() : sequence_(-1) { queue_ = Element::createFromString("[]"); }
SessionImpl() : sequence_(-1) { queue_ = Element::createList(); }
virtual ~SessionImpl() {}
virtual void establish(const char& socket_file) = 0;
virtual int getSocket() = 0;
......@@ -73,7 +73,7 @@ public:
virtual void readData(void* data, size_t datalen) = 0;
virtual void startRead(boost::function<void()> user_handler) = 0;
int sequence_; // the next sequence number to use
long int sequence_; // the next sequence number to use
std::string lname_;
ElementPtr queue_;
};
......@@ -321,7 +321,7 @@ Session::establish(const char* socket_file) {
// send a request for our local name, and wait for a response
//
ElementPtr get_lname_msg =
Element::createFromString("{ \"type\": \"getlname\" }");
Element::fromJSON("{ \"type\": \"getlname\" }");
sendmsg(get_lname_msg);
ElementPtr routing, msg;
......@@ -429,7 +429,7 @@ Session::recvmsg(ElementPtr& env, ElementPtr& msg,
msg = l_msg;
return true;
} else {
ElementPtr q_el = Element::createFromString("[]");
ElementPtr q_el = Element::createList();
q_el->add(l_env);
q_el->add(l_msg);
impl_->queue_->add(q_el);
......@@ -440,7 +440,7 @@ Session::recvmsg(ElementPtr& env, ElementPtr& msg,
void
Session::subscribe(std::string group, std::string instance) {
ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
ElementPtr env = Element::createMap();
env->set("type", Element::create("subscribe"));
env->set("group", Element::create(group));
......@@ -451,7 +451,7 @@ Session::subscribe(std::string group, std::string instance) {
void
Session::unsubscribe(std::string group, std::string instance) {
ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
ElementPtr env = Element::createMap();
env->set("type", Element::create("unsubscribe"));
env->set("group", Element::create(group));
......@@ -464,8 +464,8 @@ int
Session::group_sendmsg(ElementPtr msg, std::string group,
std::string instance, std::string to)
{
ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
int nseq = ++impl_->sequence_;
ElementPtr env = Element::createMap();
long int nseq = ++impl_->sequence_;
env->set("type", Element::create("send"));
env->set("from", Element::create(impl_->lname_));
......@@ -488,8 +488,8 @@ Session::group_recvmsg(ElementPtr& envelope, ElementPtr& msg,
int
Session::reply(ElementPtr& envelope, ElementPtr& newmsg) {
ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
int nseq = ++impl_->sequence_;
ElementPtr env = Element::createMap();
long int nseq = ++impl_->sequence_;
env->set("type", Element::create("send"));
env->set("from", Element::create(impl_->lname_));
......
......@@ -47,7 +47,7 @@ using namespace std;
using isc::data::Element;
using isc::data::ElementPtr;
using isc::data::ParseError;
using isc::data::JSONError;
namespace isc {
namespace config {
......@@ -56,7 +56,7 @@ namespace config {
ElementPtr
createAnswer()
{
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer = Element::fromJSON("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(0));
return answer;
......@@ -68,7 +68,7 @@ createAnswer(const int rcode, const ElementPtr arg)
if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
}
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer = Element::fromJSON("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(rcode));
answer_content->add(arg);
......@@ -78,7 +78,7 @@ createAnswer(const int rcode, const ElementPtr arg)
ElementPtr
createAnswer(const int rcode, const std::string& arg)
{
ElementPtr answer = Element::createFromString("{\"result\": [] }");
ElementPtr answer = Element::fromJSON("{\"result\": [] }");
ElementPtr answer_content = answer->get("result");
answer_content->add(Element::create(rcode));
answer_content->add(Element::create(arg));
......@@ -125,8 +125,8 @@ createCommand(const std::string& command)
ElementPtr
createCommand(const std::string& command, ElementPtr arg)
{
ElementPtr cmd = Element::createFromString("{}");
ElementPtr cmd_parts = Element::createFromString("[]");
ElementPtr cmd = Element::createMap();
ElementPtr cmd_parts = Element::createList();
cmd_parts->add(Element::create(command));
if (arg) {
cmd_parts->add(arg);
......@@ -175,7 +175,7 @@ ModuleCCSession::readModuleSpecification(const std::string& filename) {
try {
module_spec = moduleSpecFromFile(file, true);
} catch (ParseError pe) {
} catch (JSONError pe) {
cout << "Error parsing module specification file: " << pe.what() << endl;
exit(1);
} catch (ModuleSpecError dde) {
......@@ -252,10 +252,10 @@ ModuleCCSession::init(
std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
}
setLocalConfig(Element::createFromString("{}"));
setLocalConfig(Element::fromJSON("{}"));
// get any stored configuration from the manager
if (config_handler_) {
ElementPtr cmd = Element::createFromString("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
ElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
seq = session_.group_sendmsg(cmd, "ConfigManager");
session_.group_recvmsg(env, answer, false, seq);
ElementPtr new_config = parseAnswer(rcode, answer);
......@@ -274,7 +274,7 @@ ElementPtr
ModuleCCSession::handleConfigUpdate(ElementPtr new_config)
{
ElementPtr answer;
ElementPtr errors = Element::createFromString("[]");
ElementPtr errors = Element::createList();
if (!config_handler_) {
answer = createAnswer(1, module_name_ + " does not have a config handler");
} else if (!module_specification_.validate_config(new_config, false, errors)) {
......@@ -363,7 +363,7 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name)
session_.subscribe(module_name);
// Get the current configuration values for that module
ElementPtr cmd = Element::createFromString("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
ElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
ElementPtr env, answer;
int rcode;
......
......@@ -26,6 +26,16 @@ using namespace isc::data;
namespace isc {
namespace config {
//
// Return a part of a specification, as identified by the
// '/'-separated identifier.
// If it cannot be found, a DataNotFound error is thrown.
//
// Recursively goes through the Element. If it is a List,
// we search it contents to have 'items' (i.e. contain item_name)