From a177e5e05a2accd61a57227c4ffa85ab4ce190c7 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Fri, 6 Nov 2015 11:04:58 +0100 Subject: [PATCH] Change output format for object queries fixes #10551 --- icinga-studio/mainform.cpp | 32 +--- lib/remote/apiclient.cpp | 42 +++-- lib/remote/apiclient.hpp | 9 +- lib/remote/httpserverconnection.cpp | 2 +- lib/remote/objectqueryhandler.cpp | 263 +++++++++++++++++----------- lib/remote/objectqueryhandler.hpp | 4 + 6 files changed, 208 insertions(+), 144 deletions(-) diff --git a/icinga-studio/mainform.cpp b/icinga-studio/mainform.cpp index 0044402dd..e41e37902 100644 --- a/icinga-studio/mainform.cpp +++ b/icinga-studio/mainform.cpp @@ -112,23 +112,15 @@ void MainForm::OnTypeSelected(wxTreeEvent& event) ApiType::Ptr type = m_Types[typeName.ToStdString()]; std::vector attrs; - attrs.push_back(type->Name.ToLower() + ".__name"); + attrs.push_back("__name"); m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectsCompletionHandler, this, _1, _2, true), std::vector(), attrs); } -static bool ApiObjectLessComparer(const String& nameAttr, const ApiObject::Ptr& o1, const ApiObject::Ptr& o2) +static bool ApiObjectLessComparer(const ApiObject::Ptr& o1, const ApiObject::Ptr& o2) { - std::map::const_iterator it1 = o1->Attrs.find(nameAttr); - if (it1 == o1->Attrs.end()) - return false; - - std::map::const_iterator it2 = o2->Attrs.find(nameAttr); - if (it2 == o2->Attrs.end()) - return false; - - return it1->second < it2->second; + return o1->Name < o2->Name; } void MainForm::ObjectsCompletionHandler(boost::exception_ptr eptr, const std::vector& objects, bool forward) @@ -151,21 +143,12 @@ void MainForm::ObjectsCompletionHandler(boost::exception_ptr eptr, const std::ve } } - wxTreeItemId selectedId = m_TypesTree->GetSelection(); - wxString typeName = m_TypesTree->GetItemText(selectedId); - ApiType::Ptr type = m_Types[typeName.ToStdString()]; - - String nameAttr = type->Name.ToLower() + ".__name"; - std::vector sortedObjects = objects; - std::sort(sortedObjects.begin(), sortedObjects.end(), boost::bind(ApiObjectLessComparer, nameAttr, _2, _1)); + std::sort(sortedObjects.begin(), sortedObjects.end(), ApiObjectLessComparer); BOOST_FOREACH(const ApiObject::Ptr& object, sortedObjects) { - std::map::const_iterator it = object->Attrs.find(nameAttr); - if (it == object->Attrs.end()) - continue; - String name = it->second; - m_ObjectsList->InsertItem(0, name.GetData()); + std::string name = object->Name; + m_ObjectsList->InsertItem(0, name); } } @@ -190,7 +173,8 @@ void MainForm::OnObjectSelected(wxListEvent& event) std::vector names; names.push_back(objectName); - m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectDetailsCompletionHandler, this, _1, _2, true), names); + m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectDetailsCompletionHandler, this, _1, _2, true), + names, std::vector(), std::vector(), true); } wxPGProperty *MainForm::ValueToProperty(const String& name, const Value& value) diff --git a/lib/remote/apiclient.cpp b/lib/remote/apiclient.cpp index af035b36f..73af8a8c6 100644 --- a/lib/remote/apiclient.cpp +++ b/lib/remote/apiclient.cpp @@ -105,7 +105,7 @@ void ApiClient::TypesHttpCompletionCallback(HttpRequest& request, HttpResponse& } void ApiClient::GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback, - const std::vector& names, const std::vector& attrs) const + const std::vector& names, const std::vector& attrs, const std::vector& joins, bool all_joins) const { Url::Ptr url = new Url(); url->SetScheme("https"); @@ -128,6 +128,12 @@ void ApiClient::GetObjects(const String& pluralType, const ObjectsCompletionCall params["attrs"].push_back(attr); } + BOOST_FOREACH(const String& join, joins) { + params["joins"].push_back(join); + } + + params["all_joins"].push_back(all_joins ? "1" : "0"); + url->SetQuery(params); try { @@ -169,26 +175,42 @@ void ApiClient::ObjectsHttpCompletionCallback(HttpRequest& request, if (results) { ObjectLock olock(results); - BOOST_FOREACH(const Dictionary::Ptr objectInfo, results) - { + BOOST_FOREACH(const Dictionary::Ptr objectInfo, results) { ApiObject::Ptr object = new ApiObject(); + object->Name = objectInfo->Get("name"); + object->Type = objectInfo->Get("type"); + Dictionary::Ptr attrs = objectInfo->Get("attrs"); - { + if (attrs) { ObjectLock olock(attrs); - BOOST_FOREACH(const Dictionary::Pair& kv, attrs) - { - object->Attrs[kv.first] = kv.second; + BOOST_FOREACH(const Dictionary::Pair& kv, attrs) { + object->Attrs[object->Type.ToLower() + "." + kv.first] = kv.second; + } + } + + Dictionary::Ptr joins = objectInfo->Get("joins"); + + if (joins) { + ObjectLock olock(joins); + BOOST_FOREACH(const Dictionary::Pair& kv, joins) { + Dictionary::Ptr attrs = kv.second; + + if (attrs) { + ObjectLock olock(attrs); + BOOST_FOREACH(const Dictionary::Pair& kv2, attrs) { + object->Attrs[kv.first + "." + kv2.first] = kv2.second; + } + } } } Array::Ptr used_by = objectInfo->Get("used_by"); - { + if (used_by) { ObjectLock olock(used_by); - BOOST_FOREACH(const Dictionary::Ptr& refInfo, used_by) - { + BOOST_FOREACH(const Dictionary::Ptr& refInfo, used_by) { ApiObjectReference ref; ref.Name = refInfo->Get("name"); ref.Type = refInfo->Get("type"); diff --git a/lib/remote/apiclient.hpp b/lib/remote/apiclient.hpp index 72eee2647..ae88decc5 100644 --- a/lib/remote/apiclient.hpp +++ b/lib/remote/apiclient.hpp @@ -32,7 +32,9 @@ struct ApiFieldAttributes { public: bool Config; - bool Internal; + bool Navigation; + bool NoUserModify; + bool NouserView; bool Required; bool State; }; @@ -76,6 +78,8 @@ struct I2_REMOTE_API ApiObject : public Object public: DECLARE_PTR_TYPEDEFS(ApiObject); + String Name; + String Type; std::map Attrs; std::vector UsedBy; }; @@ -94,7 +98,8 @@ public: typedef boost::function&)> ObjectsCompletionCallback; void GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback, const std::vector& names = std::vector(), - const std::vector& attrs = std::vector()) const; + const std::vector& attrs = std::vector(), + const std::vector& joins = std::vector(), bool all_joins = false) const; typedef boost::function ExecuteScriptCompletionCallback; void ExecuteScript(const String& session, const String& command, bool sandboxed, diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index 3e821cb17..4a29f11ee 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -153,7 +153,7 @@ void HttpServerConnection::ProcessMessageAsync(HttpRequest& request) user.reset(); } - String requestUrl = request.RequestUrl->Format(); + String requestUrl = request.RequestUrl->Format(); Log(LogInformation, "HttpServerConnection") << "Request: " << request.RequestMethod << " " << requestUrl diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index ad85955ed..a7a608436 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -30,6 +30,84 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/objects", ObjectQueryHandler); +Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& object, + const String& attrPrefix, const Array::Ptr& attrs, bool isJoin, bool allAttrs) +{ + Type::Ptr type = object->GetReflectionType(); + + std::vector fids; + + if (isJoin && attrs) { + ObjectLock olock(attrs); + BOOST_FOREACH(const String& attr, attrs) { + if (attr == attrPrefix) { + allAttrs = true; + break; + } + } + } + + if (!isJoin && (!attrs || attrs->GetLength() == 0)) + allAttrs = true; + + if (allAttrs) { + for (int fid = 0; fid < type->GetFieldCount(); fid++) { + fids.push_back(fid); + } + } else if (attrs) { + ObjectLock olock(attrs); + BOOST_FOREACH(const String& attr, attrs) { + String userAttr; + + if (isJoin) { + String::SizeType dpos = attr.FindFirstOf("."); + if (dpos == String::NPos) + continue; + + String userJoinAttr = attr.SubStr(0, dpos); + if (userJoinAttr != attrPrefix) + continue; + + userAttr = attr.SubStr(dpos + 1); + } else + userAttr = attr; + + int fid = type->GetFieldId(userAttr); + + if (fid < 0) + BOOST_THROW_EXCEPTION(ScriptError("Invalid field specified: " + userAttr)); + + fids.push_back(fid); + } + } + + Dictionary::Ptr resultAttrs = new Dictionary(); + + BOOST_FOREACH(int& fid, fids) + { + Field field = type->GetFieldInfo(fid); + + Value val = object->GetField(fid); + + /* hide attributes which shouldn't be user-visible */ + if (field.Attributes & FANoUserView) + continue; + + /* hide internal navigation fields */ + if (field.Attributes & FANavigation) { + Value nval = object->NavigateField(fid); + + if (val == nval) + continue; + } + + Value sval = Serialize(val, FAConfig | FAState); + resultAttrs->Set(field.Name, sval); + } + + return resultAttrs; +} + bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) { if (request.RequestUrl->GetPath().size() < 3 || request.RequestUrl->GetPath().size() > 4) @@ -51,44 +129,10 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request); - std::set userJoinAttrs; - std::set attrs; Array::Ptr uattrs = params->Get("attrs"); - - if (uattrs) { - ObjectLock olock(uattrs); - BOOST_FOREACH(const String& uattr, uattrs) { - attrs.insert(uattr); - - String::SizeType dpos = uattr.FindFirstOf("."); - if (dpos == String::NPos) { - HttpUtility::SendJsonError(response, 400, "Attribute name must contain '.'."); - return true; - } - - String userJoinAttr = uattr.SubStr(0, dpos); - - if (userJoinAttr == type->GetName().ToLower()) - userJoinAttr = ""; - - userJoinAttrs.insert(userJoinAttr); - } - } - - std::vector joinAttrs; - joinAttrs.push_back(""); - - for (int fid = 0; fid < type->GetFieldCount(); fid++) { - Field field = type->GetFieldInfo(fid); - - if (!(field.Attributes & FANavigation)) - continue; - - if (!userJoinAttrs.empty() && userJoinAttrs.find(field.Name) == userJoinAttrs.end()) - continue; - - joinAttrs.push_back(field.Name); - } + Array::Ptr ujoins = params->Get("joins"); + Array::Ptr umetas = params->Get("meta"); + bool allJoins = HttpUtility::GetLastParameter(params, "all_joins"); params->Set("type", type->GetName()); @@ -103,93 +147,99 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re Array::Ptr results = new Array(); results->Reserve(objs.size()); + std::set joinAttrs; + + if (allJoins) { + for (int fid = 0; fid < type->GetFieldCount(); fid++) { + Field field = type->GetFieldInfo(fid); + if (field.Attributes & FANavigation) + joinAttrs.insert(field.Name); + } + } else if (ujoins) { + ObjectLock olock(ujoins); + BOOST_FOREACH(const String& ujoin, ujoins) { + joinAttrs.insert(ujoin.SubStr(0, ujoin.FindFirstOf("."))); + } + } + BOOST_FOREACH(const ConfigObject::Ptr& obj, objs) { Dictionary::Ptr result1 = new Dictionary(); results->Add(result1); - Dictionary::Ptr resultAttrs = new Dictionary(); - result1->Set("attrs", resultAttrs); + result1->Set("name", obj->GetName()); + result1->Set("type", obj->GetReflectionType()->GetName()); + + Dictionary::Ptr metaAttrs = new Dictionary(); + result1->Set("meta", metaAttrs); + + if (umetas) { + ObjectLock olock(umetas); + BOOST_FOREACH(const String& meta, umetas) { + if (meta == "used_by") { + Array::Ptr used_by = new Array(); + metaAttrs->Set("used_by", used_by); + + BOOST_FOREACH(const Object::Ptr& pobj, DependencyGraph::GetParents((obj))) + { + ConfigObject::Ptr configObj = dynamic_pointer_cast(pobj); + + if (!configObj) + continue; + + Dictionary::Ptr refInfo = new Dictionary(); + refInfo->Set("type", configObj->GetType()->GetName()); + refInfo->Set("name", configObj->GetName()); + used_by->Add(refInfo); + } + } else { + HttpUtility::SendJsonError(response, 400, "Invalid field specified for meta: " + meta); + return true; + } + } + } + + try { + result1->Set("attrs", SerializeObjectAttrs(obj, String(), uattrs, false, false)); + } catch (const ScriptError& ex) { + HttpUtility::SendJsonError(response, 400, ex.what()); + return true; + } + + Dictionary::Ptr joins = new Dictionary(); + result1->Set("joins", joins); BOOST_FOREACH(const String& joinAttr, joinAttrs) { Object::Ptr joinedObj; String prefix; - if (joinAttr.IsEmpty()) { - joinedObj = obj; - prefix = type->GetName(); - } else { - int fid = type->GetFieldId(joinAttr); - joinedObj = obj->NavigateField(fid); + int fid = type->GetFieldId(joinAttr); - if (!joinedObj) - continue; - - Field field = type->GetFieldInfo(fid); - prefix = field.NavigationName; + if (fid < 0) { + HttpUtility::SendJsonError(response, 400, "Invalid field specified for join: " + joinAttr); + return true; } - boost::algorithm::to_lower(prefix); + Field field = type->GetFieldInfo(fid); - Type::Ptr joinedType = joinedObj->GetReflectionType(); - - std::vector fids; - - if (attrs.empty()) { - for (int fid = 0; fid < joinedType->GetFieldCount(); fid++) { - fids.push_back(fid); - } - } else { - BOOST_FOREACH(const String& aname, attrs) { - String::SizeType dpos = aname.FindFirstOf("."); - ASSERT(dpos != String::NPos); - - String userJoinAttr = aname.SubStr(0, dpos); - if (userJoinAttr != prefix) - continue; - - String userAttr = aname.SubStr(dpos + 1); - - int fid = joinedType->GetFieldId(userAttr); - fids.push_back(fid); - } + if (!(field.Attributes & FANavigation)) { + HttpUtility::SendJsonError(response, 400, "Not a joinable field: " + joinAttr); + return true; } - BOOST_FOREACH(int& fid, fids) { - Field field = joinedType->GetFieldInfo(fid); - String aname = prefix + "." + field.Name; + joinedObj = obj->NavigateField(fid); - Value val = joinedObj->GetField(fid); - - /* hide attributes which shouldn't be user-visible */ - if (field.Attributes & FANoUserView) - continue; - - /* hide internal navigation fields */ - if (field.Attributes & FANavigation) { - Value nval = joinedObj->NavigateField(fid); - - if (val == nval) - continue; - } - - Value sval = Serialize(val, FAConfig | FAState); - resultAttrs->Set(aname, sval); - } - } - - Array::Ptr used_by = new Array(); - result1->Set("used_by", used_by); - - BOOST_FOREACH(const Object::Ptr& pobj, DependencyGraph::GetParents((obj))) { - ConfigObject::Ptr configObj = dynamic_pointer_cast(pobj); - - if (!configObj) + if (!joinedObj) continue; - Dictionary::Ptr refInfo = new Dictionary(); - refInfo->Set("type", configObj->GetType()->GetName()); - refInfo->Set("name", configObj->GetName()); - used_by->Add(refInfo); + prefix = field.NavigationName; + boost::algorithm::to_lower(prefix); + + try { + joins->Set(prefix, SerializeObjectAttrs(joinedObj, prefix, ujoins, true, allJoins)); + } catch (const ScriptError& ex) { + HttpUtility::SendJsonError(response, 400, ex.what()); + return true; + } } } @@ -201,4 +251,3 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re return true; } - diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp index be4749fcc..b86d71fe1 100644 --- a/lib/remote/objectqueryhandler.hpp +++ b/lib/remote/objectqueryhandler.hpp @@ -31,6 +31,10 @@ public: DECLARE_PTR_TYPEDEFS(ObjectQueryHandler); virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) override; + +private: + static Dictionary::Ptr SerializeObjectAttrs(const Object::Ptr& object, const String& attrPrefix, + const Array::Ptr& attrs, bool isJoin, bool allAttrs); }; }